Azure-sdk-for-go: Authentication example needed

Created on 9 Apr 2019  路  9Comments  路  Source: Azure/azure-sdk-for-go

I've seen a bunch of examples on how to authenticate but non of those worked for me. Maybe someone could look at my code and provide a hint on what I'm doing wrong:

func retrieveAllResources(appID string, tenantID string, subscription string, key string, request *http.Request) {

    log.Printf("Started background worker to retrieve data from Azure subscription %s", subscription)

    os.Setenv("AZURE_CLIENT_ID", appID)
    os.Setenv("AZURE_CLIENT_SECRET", key)
    os.Setenv("AZURE_TENANT_ID", tenantID)
    os.Setenv("AZURE_SUBSCRIPTION_ID", subscription)

    vmClient := compute.NewVirtualMachinesClient(subscription)
    authorizer, err := auth.NewAuthorizerFromEnvironment()
    if err != nil {
        log.Printf("Error trying to authenticate against Azure: %+v", err)
        return
    }

    vmClient.Authorizer = authorizer

    vmIterator, err := vmClient.ListAllComplete(request.Context())
    if err != nil {
        log.Printf("Error trying to retrieve all VMs from Azure. %+v", err)
        return
    }

This is the error I get:

Error trying to retrieve all VMs from Azure. azure.BearerAuthorizer#WithAuthorization: Failed to refresh the Token for request to https://management.azure.com/subscriptions/my_subscription/providers/Microsoft.Compute/virtualMachines?api-version=2018-10-01: StatusCode=0 -- Original Error: adal: Failed to execute the refresh request. Error = 'Post https://login.microsoftonline.com/my_tenatnId/oauth2/token?api-version=1.0: context canceled'

Thanks

customer question

Most helpful comment

The context is to control the lifetime of the request. If you don't want to explicitly place a deadline/timeout on it, i.e. you want to wait for it to complete with the default timeout, you want to pass context.Background().
Thanks for the explanation about your auth scenario, agreed that using auth.NewAuthorizerFromEnvironment() is not the right tool for this scenario (it's intended for apps to authorize using user-provided credentials via config file, env vars etc). Here's how you can create an autorest.Authorizer using client credentials.

func getAuthorizer() (autorest.Authorizer, error) {
    oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
    if err != nil {
        return nil, err
    }
    spToken, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, azure.PublicCloud.ResourceManagerEndpoint)
    if err != nil {
        return nil, err
    }
    return autorest.NewBearerAuthorizer(spToken), nil
}

Note that this assumes you are wanting the resource manager endpoint on the public cloud.

All 9 comments

From what I see here it looks to me that the context returned from request.Context() has been cancelled. What is the origin of this context?

There is also some documentation on authentication in the README.

From what I see here it looks to me that the context returned from request.Context() has been cancelled. What is the origin of this context?

This is the request context provided by the gorilla mux router when the client calls the REST API. Not sure if this is the right type of context that the Azure API expects?!

Yes, I've seen the examples on the README file and that is where I have my code from ;) I just had to (or think I have to) set those auth environment variables using golang's os package since I couldn't find a mechanism in the Azure API to set those params within the code instead of having it read from environment variables.

The context passed to the various APIs in the SDK is used for specifying a deadline/timeout for the API call. If a deadline/timeout isn't required I suggest passing context.Background() for the value (passing a context from an unrelated HTTP request seems dubious).
If you want to programmatically create an authorizer from client credentials have a look at ClientCredentialsConfig.ServicePrincipalToken() here. The reason we don't have samples for this method is it requires credentials to be embedded in source code which is not a good practice (i.e. it's too easy to inadvertently check in credentials to source control therefore leaking them).

Thanks @jhendrixMSFT. I need to spend more time on figuring out what "context" is in my Go app... the request.Context() from gorilla mux has not "Background" propertyfunction. I'm still new to Go.

Your hint regarding the ServicePrincipalToken() function didn't help me since it requires things like AADEndpoint and Resource and I have no clue on where to get those.

It is really unfortunate that the GO SDK makes this so complex. The Azure Java SDK is a bit easier to use:

ApplicationTokenCredentials credentials = new ApplicationTokenCredentials(
    appId, 
    tenantId,
        key, 
        AzureEnvironment.AZURE);

Azure.authenticate(...).withSubscription(...)

I agree that embedding credentials in the app is more work as we have not optimized for this (anti) pattern. Could you explain a little more why auth.NewAuthorizerFromEnvironment() doesn't work for you?

@jhendrixMSFT the auth.NewAuthorizerFromEnvironment() doesn't work for me because I cannot get it to work ;) which could be related to the context that I don't know how to properly provide. Any suggestions?

Maybe some more background on why I want to use that "anti-pattern": I'm writing a service that pulls resource data from any Azure account. The consumer of that service just needs to provide the auth creds. So I'm not intending to encode/store any Azure credentials wthin my service. It receives those during the http get request, uses them and then forgets them.

The context is to control the lifetime of the request. If you don't want to explicitly place a deadline/timeout on it, i.e. you want to wait for it to complete with the default timeout, you want to pass context.Background().
Thanks for the explanation about your auth scenario, agreed that using auth.NewAuthorizerFromEnvironment() is not the right tool for this scenario (it's intended for apps to authorize using user-provided credentials via config file, env vars etc). Here's how you can create an autorest.Authorizer using client credentials.

func getAuthorizer() (autorest.Authorizer, error) {
    oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
    if err != nil {
        return nil, err
    }
    spToken, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, azure.PublicCloud.ResourceManagerEndpoint)
    if err != nil {
        return nil, err
    }
    return autorest.NewBearerAuthorizer(spToken), nil
}

Note that this assumes you are wanting the resource manager endpoint on the public cloud.

It seems the problem is resolved, closing this one.
Feel free to reopen if any other question got raised

Was this page helpful?
0 / 5 - 0 ratings