// OAuthConfig returns an azure.OAuthConfig based on the given resource // manager endpoint and subscription ID. This will make a request to the // resource manager API to discover the Active Directory tenant ID. func OAuthConfig( client subscriptions.Client, resourceManagerEndpoint string, subscriptionId string, ) (*azure.OAuthConfig, string, error) { authURI, err := DiscoverAuthorizationURI(client, subscriptionId) if err != nil { return nil, "", errors.Annotate(err, "detecting auth URI") } logger.Debugf("discovered auth URI: %s", authURI) // The authorization URI scheme and host identifies the AD endpoint. // The authorization URI path identifies the AD tenant. tenantId, err := AuthorizationURITenantID(authURI) if err != nil { return nil, "", errors.Annotate(err, "getting tenant ID") } authURI.Path = "" adEndpoint := authURI.String() cloudEnv := azure.Environment{ActiveDirectoryEndpoint: adEndpoint} oauthConfig, err := cloudEnv.OAuthConfigForTenant(tenantId) if err != nil { return nil, "", errors.Annotate(err, "getting OAuth configuration") } return oauthConfig, tenantId, nil }
// AuthenticateServicePrincipal uses given service principal credentials to return a // service principal token. Generated token is not stored in a cache file or refreshed. func AuthenticateServicePrincipal(env azure.Environment, subscriptionID, spID, spPassword string) (*azure.ServicePrincipalToken, error) { tenantID, err := loadOrFindTenantID(env, subscriptionID) if err != nil { return nil, err } oauthCfg, err := env.OAuthConfigForTenant(tenantID) if err != nil { return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err) } spt, err := azure.NewServicePrincipalToken(*oauthCfg, spID, spPassword, getScope(env)) if err != nil { return nil, fmt.Errorf("Failed to create service principal token: %+v", err) } return spt, nil }
// Authenticate fetches a token from the local file cache or initiates a consent // flow and waits for token to be obtained. func Authenticate(env azure.Environment, subscriptionID, tenantID string, say func(string)) (*azure.ServicePrincipalToken, error) { clientID, ok := clientIDs[env.Name] if !ok { return nil, fmt.Errorf("packer-azure application not set up for Azure environment %q", env.Name) } oauthCfg, err := env.OAuthConfigForTenant(tenantID) if err != nil { return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err) } // for AzurePublicCloud (https://management.core.windows.net/), this old // Service Management scope covers both ASM and ARM. apiScope := env.ServiceManagementEndpoint tokenPath := tokenCachePath(tenantID) saveToken := mkTokenCallback(tokenPath) saveTokenCallback := func(t azure.Token) error { say("Azure token expired. Saving the refreshed token...") return saveToken(t) } // Lookup the token cache file for an existing token. spt, err := tokenFromFile(say, *oauthCfg, tokenPath, clientID, apiScope, saveTokenCallback) if err != nil { return nil, err } if spt != nil { say(fmt.Sprintf("Auth token found in file: %s", tokenPath)) // NOTE(ahmetalpbalkan): The token file we found may contain an // expired access_token. In that case, the first call to Azure SDK will // attempt to refresh the token using refresh_token, which might have // expired[1], in that case we will get an error and we shall remove the // token file and initiate token flow again so that the user would not // need removing the token cache file manually. // // [1]: expiration date of refresh_token is not returned in AAD /token // response, we just know it is 14 days. Therefore user’s token // will go stale every 14 days and we will delete the token file, // re-initiate the device flow. say("Validating the token.") if err := validateToken(env, spt); err != nil { say(fmt.Sprintf("Error: %v", err)) say("Stored Azure credentials expired. Please reauthenticate.") say(fmt.Sprintf("Deleting %s", tokenPath)) if err := os.RemoveAll(tokenPath); err != nil { return nil, fmt.Errorf("Error deleting stale token file: %v", err) } } else { say("Token works.") return spt, nil } } // Start an OAuth 2.0 device flow say(fmt.Sprintf("Initiating device flow: %s", tokenPath)) spt, err = tokenFromDeviceFlow(say, *oauthCfg, tokenPath, clientID, apiScope) if err != nil { return nil, err } say("Obtained service principal token.") if err := saveToken(spt.Token); err != nil { say("Error occurred saving token to cache file.") return nil, err } return spt, nil }
// AuthenticateDeviceFlow fetches a token from the local file cache or initiates a consent // flow and waits for token to be obtained. Obtained token is stored in a file cache for // future use and refreshing. func AuthenticateDeviceFlow(env azure.Environment, subscriptionID string) (*azure.ServicePrincipalToken, error) { // First we locate the tenant ID of the subscription as we store tokens per // tenant (which could have multiple subscriptions) tenantID, err := loadOrFindTenantID(env, subscriptionID) if err != nil { return nil, err } oauthCfg, err := env.OAuthConfigForTenant(tenantID) if err != nil { return nil, fmt.Errorf("Failed to obtain oauth config for azure environment: %v", err) } tokenPath := tokenCachePath(tenantID) saveToken := mkTokenCallback(tokenPath) saveTokenCallback := func(t azure.Token) error { log.Debug("Azure token expired. Saving the refreshed token...") return saveToken(t) } f := logutil.Fields{"path": tokenPath} appID, ok := appIDs[env.Name] if !ok { return nil, fmt.Errorf("docker-machine application not set up for Azure environment %q", env.Name) } scope := getScope(env) // Lookup the token cache file for an existing token. spt, err := tokenFromFile(*oauthCfg, tokenPath, appID, scope, saveTokenCallback) if err != nil { return nil, err } if spt != nil { log.Debug("Auth token found in file.", f) // NOTE(ahmetalpbalkan): The token file we found might be containing an // expired access_token. In that case, the first call to Azure SDK will // attempt to refresh the token using refresh_token –which might have // expired[1], in that case we will get an error and we shall remove the // token file and initiate token flow again so that the user would not // need removing the token cache file manually. // // [1]: for device flow auth, the expiration date of refresh_token is // not returned in AAD /token response, we just know it is 14 // days. Therefore user’s token will go stale every 14 days and we // will delete the token file, re-initiate the device flow. Service // Principal Account tokens are not subject to this limitation. log.Debug("Validating the token.") if err := validateToken(env, spt); err != nil { log.Debug(fmt.Sprintf("Error: %v", err)) log.Debug(fmt.Sprintf("Deleting %s", tokenPath)) if err := os.RemoveAll(tokenPath); err != nil { return nil, fmt.Errorf("Error deleting stale token file: %v", err) } } else { log.Debug("Token works.") return spt, nil } } log.Debug("Obtaining a token.", f) spt, err = deviceFlowAuth(*oauthCfg, appID, scope) if err != nil { return nil, err } log.Debug("Obtained a token.") if err := saveToken(spt.Token); err != nil { log.Error("Error occurred saving token to cache file.") return nil, err } return spt, nil }