// AuthToken returns a service principal token, suitable for authorizing // Resource Manager API requests, based on the supplied CloudSpec. func AuthToken(cloud environs.CloudSpec, sender autorest.Sender) (*azure.ServicePrincipalToken, error) { if authType := cloud.Credential.AuthType(); authType != clientCredentialsAuthType { // We currently only support a single auth-type for // non-interactive authentication. Interactive auth // is used only to generate a service-principal. return nil, errors.NotSupportedf("auth-type %q", authType) } credAttrs := cloud.Credential.Attributes() subscriptionId := credAttrs[credAttrSubscriptionId] appId := credAttrs[credAttrAppId] appPassword := credAttrs[credAttrAppPassword] client := subscriptions.Client{subscriptions.NewWithBaseURI(cloud.Endpoint)} client.Sender = sender oauthConfig, _, err := azureauth.OAuthConfig(client, cloud.Endpoint, subscriptionId) if err != nil { return nil, errors.Trace(err) } resource := azureauth.TokenResource(cloud.Endpoint) token, err := azure.NewServicePrincipalToken( *oauthConfig, appId, appPassword, resource, ) if err != nil { return nil, errors.Annotate(err, "constructing service principal token") } if sender != nil { token.SetSender(sender) } return token, nil }
// DiscoverAuthorizationID returns the OAuth authorization URI for the given // subscription ID. This can be used to determine the AD tenant ID. func DiscoverAuthorizationURI(client subscriptions.Client, subscriptionID string) (*url.URL, error) { // We make an unauthenticated request to the Azure API, which // responds with the authentication URL with the tenant ID in it. result, err := client.Get(subscriptionID) if err == nil { return nil, errors.New("expected unauthorized error response") } if result.Response.Response == nil { return nil, errors.Trace(err) } if result.StatusCode != http.StatusUnauthorized { return nil, errors.Annotatef(err, "expected unauthorized error response, got %v", result.StatusCode) } header := result.Header.Get(authenticateHeaderKey) if header == "" { return nil, errors.Errorf("%s header not found", authenticateHeaderKey) } match := authorizationUriRegexp.FindStringSubmatch(header) if match == nil { return nil, errors.Errorf( "authorization_uri not found in %s header (%q)", authenticateHeaderKey, header, ) } return url.Parse(match[1]) }
func (s *OAuthConfigSuite) TestOAuthConfig(c *gc.C) { client := subscriptions.Client{subscriptions.NewWithBaseURI("https://testing.invalid")} client.Sender = oauthConfigSender() cfg, tenantId, err := azureauth.OAuthConfig(client, "https://testing.invalid", "subscription-id") c.Assert(err, jc.ErrorIsNil) c.Assert(tenantId, gc.Equals, fakeTenantId) baseURL := url.URL{ Scheme: "https", Host: "testing.invalid", RawQuery: "api-version=1.0", } expectedCfg := &azure.OAuthConfig{ AuthorizeEndpoint: baseURL, TokenEndpoint: baseURL, DeviceCodeEndpoint: baseURL, } expectedCfg.AuthorizeEndpoint.Path = "/11111111-1111-1111-1111-111111111111/oauth2/authorize" expectedCfg.TokenEndpoint.Path = "/11111111-1111-1111-1111-111111111111/oauth2/token" expectedCfg.DeviceCodeEndpoint.Path = "/11111111-1111-1111-1111-111111111111/oauth2/devicecode" c.Assert(cfg, jc.DeepEquals, expectedCfg) }
// InteractiveCreateServicePrincipal interactively creates service // principals for a subscription. func InteractiveCreateServicePrincipal( stderr io.Writer, sender autorest.Sender, requestInspector autorest.PrepareDecorator, resourceManagerEndpoint string, graphEndpoint string, subscriptionId string, clock clock.Clock, newUUID func() (utils.UUID, error), ) (appId, password string, _ error) { subscriptionsClient := subscriptions.Client{ subscriptions.NewWithBaseURI(resourceManagerEndpoint), } subscriptionsClient.Sender = sender setClientInspectors(&subscriptionsClient.Client, requestInspector, "azure.subscriptions") oauthConfig, tenantId, err := OAuthConfig( subscriptionsClient, resourceManagerEndpoint, subscriptionId, ) if err != nil { return "", "", errors.Trace(err) } client := autorest.NewClientWithUserAgent("juju") client.Sender = sender setClientInspectors(&client, requestInspector, "azure.autorest") // Perform the interactive authentication. The user will be prompted to // open a URL and input a device code, after which they will have to // enter their username and password if they are not already // authenticated with Azure. fmt.Fprintln(stderr, "Initiating interactive authentication.") fmt.Fprintln(stderr) armResource := TokenResource(resourceManagerEndpoint) clientId := jujuApplicationId deviceCode, err := azure.InitiateDeviceAuth(&client, *oauthConfig, clientId, armResource) if err != nil { return "", "", errors.Annotate(err, "initiating interactive authentication") } fmt.Fprintln(stderr, to.String(deviceCode.Message)+"\n") token, err := azure.WaitForUserCompletion(&client, deviceCode) if err != nil { return "", "", errors.Annotate(err, "waiting for interactive authentication to completed") } // Create service principal tokens that we can use to authorize API // requests to Active Directory and Resource Manager. These tokens // are only valid for a short amount of time, so we must create a // service principal password that can be used to obtain new tokens. armSpt, err := azure.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, armResource, *token) if err != nil { return "", "", errors.Annotate(err, "creating temporary ARM service principal token") } if client.Sender != nil { armSpt.SetSender(client.Sender) } if err := armSpt.Refresh(); err != nil { return "", "", errors.Trace(err) } // The application requires permissions for both ARM and AD, so we // can use the token for both APIs. graphResource := TokenResource(graphEndpoint) graphToken := armSpt.Token graphToken.Resource = graphResource graphSpt, err := azure.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, graphResource, graphToken) if err != nil { return "", "", errors.Annotate(err, "creating temporary Graph service principal token") } if client.Sender != nil { graphSpt.SetSender(client.Sender) } if err := graphSpt.Refresh(); err != nil { return "", "", errors.Trace(err) } directoryURL, err := url.Parse(graphEndpoint) if err != nil { return "", "", errors.Annotate(err, "parsing identity endpoint") } directoryURL.Path = path.Join(directoryURL.Path, tenantId) directoryClient := ad.NewManagementClient(directoryURL.String()) authorizationClient := authorization.NewWithBaseURI(resourceManagerEndpoint, subscriptionId) directoryClient.Authorizer = graphSpt authorizationClient.Authorizer = armSpt authorizationClient.Sender = client.Sender directoryClient.Sender = client.Sender setClientInspectors(&directoryClient.Client, requestInspector, "azure.directory") setClientInspectors(&authorizationClient.Client, requestInspector, "azure.authorization") userObject, err := ad.UsersClient{directoryClient}.GetCurrentUser() if err != nil { return "", "", errors.Trace(err) } fmt.Fprintf(stderr, "Authenticated as %q.\n", userObject.DisplayName) fmt.Fprintln(stderr, "Creating/updating service principal.") servicePrincipalObjectId, password, err := createOrUpdateServicePrincipal( ad.ServicePrincipalsClient{directoryClient}, subscriptionId, clock, newUUID, ) if err != nil { return "", "", errors.Trace(err) } fmt.Fprintln(stderr, "Assigning Owner role to service principal.") if err := createRoleAssignment( authorizationClient, subscriptionId, servicePrincipalObjectId, newUUID, ); err != nil { return "", "", errors.Trace(err) } return jujuApplicationId, password, nil }