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