1package auth
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"bytes"
19	"crypto/rsa"
20	"crypto/x509"
21	"encoding/binary"
22	"encoding/json"
23	"errors"
24	"fmt"
25	"io/ioutil"
26	"log"
27	"os"
28	"strings"
29	"unicode/utf16"
30
31	"github.com/Azure/go-autorest/autorest"
32	"github.com/Azure/go-autorest/autorest/adal"
33	"github.com/Azure/go-autorest/autorest/azure"
34	"github.com/dimchansky/utfbom"
35	"golang.org/x/crypto/pkcs12"
36)
37
38// NewAuthorizerFromEnvironment creates an Authorizer configured from environment variables in the order:
39// 1. Client credentials
40// 2. Client certificate
41// 3. Username password
42// 4. MSI
43func NewAuthorizerFromEnvironment() (autorest.Authorizer, error) {
44	settings, err := getAuthenticationSettings()
45	if err != nil {
46		return nil, err
47	}
48
49	if settings.resource == "" {
50		settings.resource = settings.environment.ResourceManagerEndpoint
51	}
52
53	return settings.getAuthorizer()
54}
55
56// NewAuthorizerFromEnvironmentWithResource creates an Authorizer configured from environment variables in the order:
57// 1. Client credentials
58// 2. Client certificate
59// 3. Username password
60// 4. MSI
61func NewAuthorizerFromEnvironmentWithResource(resource string) (autorest.Authorizer, error) {
62	settings, err := getAuthenticationSettings()
63	if err != nil {
64		return nil, err
65	}
66	settings.resource = resource
67	return settings.getAuthorizer()
68}
69
70type settings struct {
71	tenantID            string
72	clientID            string
73	clientSecret        string
74	certificatePath     string
75	certificatePassword string
76	username            string
77	password            string
78	envName             string
79	resource            string
80	environment         azure.Environment
81}
82
83func getAuthenticationSettings() (s settings, err error) {
84	s = settings{
85		tenantID:            os.Getenv("AZURE_TENANT_ID"),
86		clientID:            os.Getenv("AZURE_CLIENT_ID"),
87		clientSecret:        os.Getenv("AZURE_CLIENT_SECRET"),
88		certificatePath:     os.Getenv("AZURE_CERTIFICATE_PATH"),
89		certificatePassword: os.Getenv("AZURE_CERTIFICATE_PASSWORD"),
90		username:            os.Getenv("AZURE_USERNAME"),
91		password:            os.Getenv("AZURE_PASSWORD"),
92		envName:             os.Getenv("AZURE_ENVIRONMENT"),
93		resource:            os.Getenv("AZURE_AD_RESOURCE"),
94	}
95
96	if s.envName == "" {
97		s.environment = azure.PublicCloud
98	} else {
99		s.environment, err = azure.EnvironmentFromName(s.envName)
100	}
101	return
102}
103
104func (settings settings) getAuthorizer() (autorest.Authorizer, error) {
105	//1.Client Credentials
106	if settings.clientSecret != "" {
107		config := NewClientCredentialsConfig(settings.clientID, settings.clientSecret, settings.tenantID)
108		config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint
109		config.Resource = settings.resource
110		return config.Authorizer()
111	}
112
113	//2. Client Certificate
114	if settings.certificatePath != "" {
115		config := NewClientCertificateConfig(settings.certificatePath, settings.certificatePassword, settings.clientID, settings.tenantID)
116		config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint
117		config.Resource = settings.resource
118		return config.Authorizer()
119	}
120
121	//3. Username Password
122	if settings.username != "" && settings.password != "" {
123		config := NewUsernamePasswordConfig(settings.username, settings.password, settings.clientID, settings.tenantID)
124		config.AADEndpoint = settings.environment.ActiveDirectoryEndpoint
125		config.Resource = settings.resource
126		return config.Authorizer()
127	}
128
129	// 4. MSI
130	config := NewMSIConfig()
131	config.Resource = settings.resource
132	config.ClientID = settings.clientID
133	return config.Authorizer()
134}
135
136// NewAuthorizerFromFile creates an Authorizer configured from a configuration file.
137func NewAuthorizerFromFile(baseURI string) (autorest.Authorizer, error) {
138	fileLocation := os.Getenv("AZURE_AUTH_LOCATION")
139	if fileLocation == "" {
140		return nil, errors.New("auth file not found. Environment variable AZURE_AUTH_LOCATION is not set")
141	}
142
143	contents, err := ioutil.ReadFile(fileLocation)
144	if err != nil {
145		return nil, err
146	}
147
148	// Auth file might be encoded
149	decoded, err := decode(contents)
150	if err != nil {
151		return nil, err
152	}
153
154	file := file{}
155	err = json.Unmarshal(decoded, &file)
156	if err != nil {
157		return nil, err
158	}
159
160	resource, err := getResourceForToken(file, baseURI)
161	if err != nil {
162		return nil, err
163	}
164
165	config, err := adal.NewOAuthConfig(file.ActiveDirectoryEndpoint, file.TenantID)
166	if err != nil {
167		return nil, err
168	}
169
170	spToken, err := adal.NewServicePrincipalToken(*config, file.ClientID, file.ClientSecret, resource)
171	if err != nil {
172		return nil, err
173	}
174
175	return autorest.NewBearerAuthorizer(spToken), nil
176}
177
178// File represents the authentication file
179type file struct {
180	ClientID                string `json:"clientId,omitempty"`
181	ClientSecret            string `json:"clientSecret,omitempty"`
182	SubscriptionID          string `json:"subscriptionId,omitempty"`
183	TenantID                string `json:"tenantId,omitempty"`
184	ActiveDirectoryEndpoint string `json:"activeDirectoryEndpointUrl,omitempty"`
185	ResourceManagerEndpoint string `json:"resourceManagerEndpointUrl,omitempty"`
186	GraphResourceID         string `json:"activeDirectoryGraphResourceId,omitempty"`
187	SQLManagementEndpoint   string `json:"sqlManagementEndpointUrl,omitempty"`
188	GalleryEndpoint         string `json:"galleryEndpointUrl,omitempty"`
189	ManagementEndpoint      string `json:"managementEndpointUrl,omitempty"`
190}
191
192func decode(b []byte) ([]byte, error) {
193	reader, enc := utfbom.Skip(bytes.NewReader(b))
194
195	switch enc {
196	case utfbom.UTF16LittleEndian:
197		u16 := make([]uint16, (len(b)/2)-1)
198		err := binary.Read(reader, binary.LittleEndian, &u16)
199		if err != nil {
200			return nil, err
201		}
202		return []byte(string(utf16.Decode(u16))), nil
203	case utfbom.UTF16BigEndian:
204		u16 := make([]uint16, (len(b)/2)-1)
205		err := binary.Read(reader, binary.BigEndian, &u16)
206		if err != nil {
207			return nil, err
208		}
209		return []byte(string(utf16.Decode(u16))), nil
210	}
211	return ioutil.ReadAll(reader)
212}
213
214func getResourceForToken(f file, baseURI string) (string, error) {
215	// Compare dafault base URI from the SDK to the endpoints from the public cloud
216	// Base URI and token resource are the same string. This func finds the authentication
217	// file field that matches the SDK base URI. The SDK defines the public cloud
218	// endpoint as its default base URI
219	if !strings.HasSuffix(baseURI, "/") {
220		baseURI += "/"
221	}
222	switch baseURI {
223	case azure.PublicCloud.ServiceManagementEndpoint:
224		return f.ManagementEndpoint, nil
225	case azure.PublicCloud.ResourceManagerEndpoint:
226		return f.ResourceManagerEndpoint, nil
227	case azure.PublicCloud.ActiveDirectoryEndpoint:
228		return f.ActiveDirectoryEndpoint, nil
229	case azure.PublicCloud.GalleryEndpoint:
230		return f.GalleryEndpoint, nil
231	case azure.PublicCloud.GraphEndpoint:
232		return f.GraphResourceID, nil
233	}
234	return "", fmt.Errorf("auth: base URI not found in endpoints")
235}
236
237// NewClientCredentialsConfig creates an AuthorizerConfig object configured to obtain an Authorizer through Client Credentials.
238// Defaults to Public Cloud and Resource Manager Endpoint.
239func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string) ClientCredentialsConfig {
240	return ClientCredentialsConfig{
241		ClientID:     clientID,
242		ClientSecret: clientSecret,
243		TenantID:     tenantID,
244		Resource:     azure.PublicCloud.ResourceManagerEndpoint,
245		AADEndpoint:  azure.PublicCloud.ActiveDirectoryEndpoint,
246	}
247}
248
249// NewClientCertificateConfig creates a ClientCertificateConfig object configured to obtain an Authorizer through client certificate.
250// Defaults to Public Cloud and Resource Manager Endpoint.
251func NewClientCertificateConfig(certificatePath string, certificatePassword string, clientID string, tenantID string) ClientCertificateConfig {
252	return ClientCertificateConfig{
253		CertificatePath:     certificatePath,
254		CertificatePassword: certificatePassword,
255		ClientID:            clientID,
256		TenantID:            tenantID,
257		Resource:            azure.PublicCloud.ResourceManagerEndpoint,
258		AADEndpoint:         azure.PublicCloud.ActiveDirectoryEndpoint,
259	}
260}
261
262// NewUsernamePasswordConfig creates an UsernamePasswordConfig object configured to obtain an Authorizer through username and password.
263// Defaults to Public Cloud and Resource Manager Endpoint.
264func NewUsernamePasswordConfig(username string, password string, clientID string, tenantID string) UsernamePasswordConfig {
265	return UsernamePasswordConfig{
266		Username:    username,
267		Password:    password,
268		ClientID:    clientID,
269		TenantID:    tenantID,
270		Resource:    azure.PublicCloud.ResourceManagerEndpoint,
271		AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
272	}
273}
274
275// NewMSIConfig creates an MSIConfig object configured to obtain an Authorizer through MSI.
276func NewMSIConfig() MSIConfig {
277	return MSIConfig{
278		Resource: azure.PublicCloud.ResourceManagerEndpoint,
279	}
280}
281
282// NewDeviceFlowConfig creates a DeviceFlowConfig object configured to obtain an Authorizer through device flow.
283// Defaults to Public Cloud and Resource Manager Endpoint.
284func NewDeviceFlowConfig(clientID string, tenantID string) DeviceFlowConfig {
285	return DeviceFlowConfig{
286		ClientID:    clientID,
287		TenantID:    tenantID,
288		Resource:    azure.PublicCloud.ResourceManagerEndpoint,
289		AADEndpoint: azure.PublicCloud.ActiveDirectoryEndpoint,
290	}
291}
292
293//AuthorizerConfig provides an authorizer from the configuration provided.
294type AuthorizerConfig interface {
295	Authorizer() (autorest.Authorizer, error)
296}
297
298// ClientCredentialsConfig provides the options to get a bearer authorizer from client credentials.
299type ClientCredentialsConfig struct {
300	ClientID     string
301	ClientSecret string
302	TenantID     string
303	AADEndpoint  string
304	Resource     string
305}
306
307// Authorizer gets the authorizer from client credentials.
308func (ccc ClientCredentialsConfig) Authorizer() (autorest.Authorizer, error) {
309	oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
310	if err != nil {
311		return nil, err
312	}
313
314	spToken, err := adal.NewServicePrincipalToken(*oauthConfig, ccc.ClientID, ccc.ClientSecret, ccc.Resource)
315	if err != nil {
316		return nil, fmt.Errorf("failed to get oauth token from client credentials: %v", err)
317	}
318
319	return autorest.NewBearerAuthorizer(spToken), nil
320}
321
322// ClientCertificateConfig provides the options to get a bearer authorizer from a client certificate.
323type ClientCertificateConfig struct {
324	ClientID            string
325	CertificatePath     string
326	CertificatePassword string
327	TenantID            string
328	AADEndpoint         string
329	Resource            string
330}
331
332// Authorizer gets an authorizer object from client certificate.
333func (ccc ClientCertificateConfig) Authorizer() (autorest.Authorizer, error) {
334	oauthConfig, err := adal.NewOAuthConfig(ccc.AADEndpoint, ccc.TenantID)
335
336	certData, err := ioutil.ReadFile(ccc.CertificatePath)
337	if err != nil {
338		return nil, fmt.Errorf("failed to read the certificate file (%s): %v", ccc.CertificatePath, err)
339	}
340
341	certificate, rsaPrivateKey, err := decodePkcs12(certData, ccc.CertificatePassword)
342	if err != nil {
343		return nil, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %v", err)
344	}
345
346	spToken, err := adal.NewServicePrincipalTokenFromCertificate(*oauthConfig, ccc.ClientID, certificate, rsaPrivateKey, ccc.Resource)
347
348	if err != nil {
349		return nil, fmt.Errorf("failed to get oauth token from certificate auth: %v", err)
350	}
351
352	return autorest.NewBearerAuthorizer(spToken), nil
353}
354
355// DeviceFlowConfig provides the options to get a bearer authorizer using device flow authentication.
356type DeviceFlowConfig struct {
357	ClientID    string
358	TenantID    string
359	AADEndpoint string
360	Resource    string
361}
362
363// Authorizer gets the authorizer from device flow.
364func (dfc DeviceFlowConfig) Authorizer() (autorest.Authorizer, error) {
365	oauthClient := &autorest.Client{}
366	oauthConfig, err := adal.NewOAuthConfig(dfc.AADEndpoint, dfc.TenantID)
367	deviceCode, err := adal.InitiateDeviceAuth(oauthClient, *oauthConfig, dfc.ClientID, dfc.Resource)
368	if err != nil {
369		return nil, fmt.Errorf("failed to start device auth flow: %s", err)
370	}
371
372	log.Println(*deviceCode.Message)
373
374	token, err := adal.WaitForUserCompletion(oauthClient, deviceCode)
375	if err != nil {
376		return nil, fmt.Errorf("failed to finish device auth flow: %s", err)
377	}
378
379	spToken, err := adal.NewServicePrincipalTokenFromManualToken(*oauthConfig, dfc.ClientID, dfc.Resource, *token)
380	if err != nil {
381		return nil, fmt.Errorf("failed to get oauth token from device flow: %v", err)
382	}
383
384	return autorest.NewBearerAuthorizer(spToken), nil
385}
386
387func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.PrivateKey, error) {
388	privateKey, certificate, err := pkcs12.Decode(pkcs, password)
389	if err != nil {
390		return nil, nil, err
391	}
392
393	rsaPrivateKey, isRsaKey := privateKey.(*rsa.PrivateKey)
394	if !isRsaKey {
395		return nil, nil, fmt.Errorf("PKCS#12 certificate must contain an RSA private key")
396	}
397
398	return certificate, rsaPrivateKey, nil
399}
400
401// UsernamePasswordConfig provides the options to get a bearer authorizer from a username and a password.
402type UsernamePasswordConfig struct {
403	ClientID    string
404	Username    string
405	Password    string
406	TenantID    string
407	AADEndpoint string
408	Resource    string
409}
410
411// Authorizer gets the authorizer from a username and a password.
412func (ups UsernamePasswordConfig) Authorizer() (autorest.Authorizer, error) {
413
414	oauthConfig, err := adal.NewOAuthConfig(ups.AADEndpoint, ups.TenantID)
415
416	spToken, err := adal.NewServicePrincipalTokenFromUsernamePassword(*oauthConfig, ups.ClientID, ups.Username, ups.Password, ups.Resource)
417
418	if err != nil {
419		return nil, fmt.Errorf("failed to get oauth token from username and password auth: %v", err)
420	}
421
422	return autorest.NewBearerAuthorizer(spToken), nil
423}
424
425// MSIConfig provides the options to get a bearer authorizer through MSI.
426type MSIConfig struct {
427	Resource string
428	ClientID string
429}
430
431// Authorizer gets the authorizer from MSI.
432func (mc MSIConfig) Authorizer() (autorest.Authorizer, error) {
433	msiEndpoint, err := adal.GetMSIVMEndpoint()
434	if err != nil {
435		return nil, err
436	}
437
438	spToken, err := adal.NewServicePrincipalTokenFromMSI(msiEndpoint, mc.Resource)
439	if err != nil {
440		return nil, fmt.Errorf("failed to get oauth token from MSI: %v", err)
441	}
442
443	return autorest.NewBearerAuthorizer(spToken), nil
444}
445