Azure/azure-sdk-for-go

Appcomplianceautomation report list pager returns 401

Opened this issue · 9 comments

Bug Report

  • import path of package in question: azure-sdk-for-go/sdk/resourcemanager/appcomplianceautomation/armappcomplianceautomation
  • SDK version: 1.0.0
  • output of go version: go version go1.22.4 darwin/arm64
  • What happened?
  1. Creating the Report client using armappcomplianceautomation.NewReportClient
  2. Creating the ListPager using NewListPager (with nil, empty, partially filled options)
  3. Using the Pager to fetch the Reports
  4. Receiving an error: "{"error":{"code":"invalid_access_token","message":"Invalid access token"}}"
  • What did you expect or want to happen?
    Expected to receive the reports, as az acat report list is working correctly using the same credentials
  • How can we reproduce it?
    Try to use the armappcomplianceautomation sdk to receive the report list.
    I've also tried using the previous v.0.3.0 version, but that one resulted in 400 Bad Request errors.
  • Anything we should know about your environment.
    We're fetching multiple resources across a big amount of services, using the same configuration, I've checked that the provider is registered to the subscription we use, and there are existing reports.

Can you please provide which versions of azcore and azidentity you're using? And also which credential type from azidentity you're using?

We're using azcore v1.16.0 and azidentity v1.8.0. The credential type is DefaultAzureCredential

It will take some debugging to figure out what's going wrong here because there are several possible causes for a 401 from the resource. First thing to check is that your app gets tokens from the source you expect, which I take it is the Azure CLI. One way to do this is to enable logging as described in the README. Your listener will get a message like "DefaultAzureCredential authenticated with AzureCLICredential".

If AzureCLICredential provided the token, the next step is to examine the service request and 401 response:

  • is the request authorized with an access token i.e., is the value of its Authorization header like Bearer eyJ0...?
  • the 401 should include a WWW-Authenticate header; what's that header's value?

You can also collect this information from logging, for example:

import (
	azlog "github.com/Azure/azure-sdk-for-go/sdk/azcore/log"
)

azlog.SetListener(func(event azlog.Event, msg string) {
	fmt.Println(msg)
})
azlog.SetEvents(azidentity.EventAuthentication, azlog.EventRequest, azlog.EventResponse)

client, err := armappcomplianceautomation.NewReportClient(todoCredential, &arm.ClientOptions{
	ClientOptions: policy.ClientOptions{
		Logging: policy.LogOptions{
			// note that the value of Authorization is secret. Allow logging
			// it only if you can ensure the security of your log output
			AllowedHeaders: []string{"Authorization", "WWW-Authenticate"},
		},
	},
})

Hi @blesniewski. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue.

@chlowell

  • the request indeed has an Authorization Header beginning like you mentioned eyJ0.
  • I don't see a WWW-Authenticate header on the 401 response

As to logging the credentials authentication, I've had some issues doing it, but by checking using a debugger I can say that the credential chain used for creating the DefaultAzureCredential has inside:

  • ManagedIdentityCredential, AzureCLICredential, AzureDeveloperCLICredential. There are no errors logged during their init that might suggest there's anything wrong.
  • EnvironmentCredential and WorkloadIdentityCredential, containing errors explaining why they couldn't be initialized.

The created Credential and BearerTokenPolicy has a field successfulCredential which is pointing to AzureCLICredential when initializing the Reports client

I've verified that I can fetch Operations using armappcomplianceautomation.NewOperationsClient and same credentials.

Are the Authorization headers of the failing report request and the successful operations request identical (they should be)?

We're fetching multiple resources across a big amount of services, using the same configuration

Are all these resources in the same tenant?

Hi @blesniewski. Thank you for opening this issue and giving us the opportunity to assist. To help our team better understand your issue and the details of your scenario please provide a response to the question asked above or the information requested above. This will help us more accurately address your issue.

Are the Authorization headers of the failing report request and the successful operations request identical (they should be)?

Auth headers are identical,

Are all these resources in the same tenant?

Yes

Thanks. I believe we can conclude that the credential and client are behaving correctly:

  • DefaultAzureCredential acquires a valid ARM token
    • we know this because OperationsClient requests succeed
  • the failing ReportClient request is authorized with a valid ARM token
    • we know this because its Authorization header is identical to that of the successful OperationsClient request (this is expected; same audience, same token)

I asked whether the resources are in the same tenant because there is a cross-tenant auth feature for ARM which I guess could produce this behavior if your ReportClient request involved resources in multiple tenants. If it did, the request would need an access token for each tenant in its x-ms-authorization-auxiliary header and you would need to set the ClientOptions.AuxiliaryTenants field when constructing the client. But this is irrelevant if the resources are all in the same tenant.

We've hit the limit of my knowledge here, so let me add @lirenhe for more guidance. The only thing I can think of that might be helpful at this point is to compare requests sent by az acat report list to those sent by your ReportClient. If they use the same REST API, the difference between success and failure should be in those requests.