Azure / azure-sdk-for-net

This repository is for active development of the Azure SDK for .NET. For consumers of the SDK we recommend visiting our public developer docs at https://learn.microsoft.com/dotnet/azure/ or our versioned developer docs at https://azure.github.io/azure-sdk-for-net.
MIT License
5.37k stars 4.78k forks source link

[BUG] Handling all errors instead of just 403 in ManagedIdentitySource causes unexpected regression error #46709

Open nikolaia opened 1 day ago

nikolaia commented 1 day ago

Library name and version

Azure.Identity 1.13.0

Describe the bug

When updating from 1.12.1 to 1.13.0 I get a regression error with AzureDefaultCredential usage (in a .NET app) inside Github Actions using Federated Credentials for a UAI to migrate my database.

I'm relying on the AzureCliCredentials inside my action, but the following change seems to stop the credential chain because the github action has a endpoint that responds with Bad Request: https://github.com/Azure/azure-sdk-for-net/pull/45236/files#diff-72571e3cca761ecd73c5855b39621f8883c8ee115319a0ecbb629deb5b8c0513L85

Expected behavior

DefaultAzureCredentials() is not able to get a managed identity and proceeds through the default chain to AzureCliCredential

With 1.12.1 I get:

Build started...
Build succeeded.
No migrations were applied. The database is already up to date.
Done.

Actual behavior

The IMDS endpoint responds with 400 Bad Request and stops the chain.

With 1.13.0 I get:

Build started...
Build succeeded.
Npgsql.NpgsqlException (0x80004005): An exception was thrown from the periodic password provider
 ---> Azure.Identity.AuthenticationFailedException: ManagedIdentityCredential authentication failed: [Managed Identity] Authentication unavailable. Either the requested identity has not been assigned to this resource, or other errors could be present. Ensure the identity is correctly assigned and check the inner exception for more details. For more information, visit https://aka.ms/msal-managed-identity.
Status: BadRequest
Content:
{"error":"invalid_request","error_description":"Identity not found"}

Headers:
Server: IMDS/150.870.65.1475
x-ms-request-id: a366dbbd-5d47-405d-b636-dc4d8bca40d5
Date: Fri, 18 Oct 2024 14:21:55 GMT

[Managed Identity] Error Code: invalid_request Error Description: Identity not found 
See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/managedidentitycredential/troubleshoot
 ---> MSAL.NetCore.4.65.0.0.MsalServiceException:
    ErrorCode: managed_identity_request_failed
Microsoft.Identity.Client.MsalServiceException: [Managed Identity] Authentication unavailable. Either the requested identity has not been assigned to this resource, or other errors could be present. Ensure the identity is correctly assigned and check the inner exception for more details. For more information, visit https://aka.ms/msal-managed-identity.
Status: BadRequest
Content:
{"error":"invalid_request","error_description":"Identity not found"}

Headers:
Server: IMDS/150.870.65.1475
x-ms-request-id: a366dbbd-5d47-405d-b636-dc4d8bca40d5
Date: Fri, 18 Oct 2024 14:21:55 GMT

[Managed Identity] Error Code: invalid_request Error Description: Identity not found 
   at Microsoft.Identity.Client.ManagedIdentity.ImdsManagedIdentitySource.HandleResponseAsync(AcquireTokenForManagedIdentityParameters parameters, HttpResponse response, CancellationToken cancellationToken)
   at Microsoft.Identity.Client.ManagedIdentity.AbstractManagedIdentity.AuthenticateAsync(AcquireTokenForManagedIdentityParameters parameters, CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.ManagedIdentityAuthRequest.SendTokenRequestForManagedIdentityAsync(ILoggerAdapter logger, CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.ManagedIdentityAuthRequest.GetAccessTokenAsync(CancellationToken cancellationToken, ILoggerAdapter logger)
   at Microsoft.Identity.Client.Internal.Requests.ManagedIdentityAuthRequest.ExecuteAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.<>c__DisplayClass11_1.<<RunAsync>b__1>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.Identity.Client.Utils.StopwatchService.MeasureCodeBlockAsync(Func`1 codeBlock)
   at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
   at Microsoft.Identity.Client.ApiConfig.Executors.ManagedIdentityExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenForManagedIdentityParameters managedIdentityParameters, CancellationToken cancellationToken)
   at Azure.Identity.MsalManagedIdentityClient.AcquireTokenForManagedIdentityAsyncCore(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.MsalManagedIdentityClient.AcquireTokenForManagedIdentityAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.ImdsManagedIdentityProbeSource.AuthenticateAsync(Boolean async, TokenRequestContext context, CancellationToken cancellationToken)
   at Azure.Identity.ManagedIdentityClient.AuthenticateCoreAsync(Boolean async, TokenRequestContext context, CancellationToken cancellationToken)
   at Azure.Identity.ManagedIdentityClient.AuthenticateAsync(Boolean async, TokenRequestContext context, CancellationToken cancellationToken)
   at Azure.Identity.ManagedIdentityCredential.GetTokenImplAsync(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken)
    StatusCode: 0 
    ResponseBody:  
    Headers: 
   --- End of inner exception stack trace ---
   at Azure.Identity.CredentialDiagnosticScope.FailWrapAndThrow(Exception ex, String additionalMessage, Boolean isCredentialUnavailable)
   at Azure.Identity.ManagedIdentityCredential.GetTokenImplAsync(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.ManagedIdentityCredential.GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.DefaultAzureCredential.GetTokenFromSourcesAsync(TokenCredential[] sources, TokenRequestContext requestContext, Boolean async, CancellationToken cancellationToken)
   at Azure.Identity.DefaultAzureCredential.GetTokenImplAsync(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.CredentialDiagnosticScope.FailWrapAndThrow(Exception ex, String additionalMessage, Boolean isCredentialUnavailable)
   at Azure.Identity.DefaultAzureCredential.GetTokenImplAsync(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Azure.Identity.DefaultAzureCredential.GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
   at Infrastructure.DatabaseContext.<>c__DisplayClass6_0.<<OnConfiguring>b__1>d.MoveNext() in /home/runner/work/someproject/api/src/Infrastructure/DatabaseContext.cs:line 34
--- End of stack trace from previous location ---
   at Npgsql.NpgsqlDataSource.RefreshPassword()
   at Npgsql.NpgsqlDataSource.RefreshPassword()
   at Npgsql.NpgsqlDataSource.<GetPassword>g__GetInitialPeriodicPassword|83_0(Boolean async)
   at Npgsql.NpgsqlConnection.CloneWith(String connectionString)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlRelationalConnection.CloneWith(String connectionString)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlDatabaseCreator.Exists(Boolean async, CancellationToken cancellationToken)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlDatabaseCreator.Exists()
   at Microsoft.EntityFrameworkCore.Migrations.HistoryRepository.Exists()
   at Microsoft.EntityFrameworkCore.Migrations.HistoryRepository.GetAppliedMigrations()
   at Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Internal.NpgsqlMigrator.Migrate(String targetMigration)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.UpdateDatabase(String targetMigration, String connectionString, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabaseImpl(String targetMigration, String connectionString, String contextType)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.UpdateDatabase.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
An exception was thrown from the periodic password provider
Error: Process completed with exit code 1.

Reproduction Steps

This is the code I use with Npsql inside my DbContext to get a token:

TokenCredential tokenCredential = string.IsNullOrEmpty(config.Value.UserAssignedManagedIdentityClientId)
    ? new DefaultAzureCredential()
    : new ManagedIdentityCredential(config.Value.UserAssignedManagedIdentityClientId);

var accessToken = await tokenCredential.GetTokenAsync(
    new TokenRequestContext(["https://ossrdbms-aad.database.windows.net/.default"]),
    cancellationToken
);

return accessToken.Token;

The config value is set in my Container App, but in the github action it logs in with a seperate UAI (with Federated Credentials setup up) that can do migrations on the database:

  - name: Azure login
    uses: azure/login@v2
    with:
      client-id: ${{ secrets.AZURE_CLIENT_ID }}
      tenant-id: ${{ secrets.AZURE_TENANT_ID }}
      subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

Environment

This is inside a Github Action:

  deploy_api:
    name: deploy-api
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    needs: build_and_test
    env:
      DATABASE_SERVER: xxxxxxxx
      DATABASE_NAME: xxxxxxxx
      DATABASE_USER_ID: xxxxxxxxxxx
    steps:
      - uses: actions/checkout@v4

      - name: Setup .NET Core
        uses: actions/setup-dotnet@v3
        with:
          global-json-file: ./api/global.json

      - name: Azure login
        uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

      - name: Install EF tools
        working-directory: api
        run: dotnet tool install --global dotnet-ef

      - name: Get access token and run database migrations
        working-directory: api
        env:
          InfrastructureConfig__ConnectionString: Host=${{env.DATABASE_SERVER}};Database=${{env.DATABASE_NAME}};Username=${{env.DATABASE_USER_ID}};Ssl Mode=Require
        run: |
          echo "connectionString=$InfrastructureConfig__ConnectionString"
          dotnet ef database update --project src/Infrastructure/ --startup-project src/Api
github-actions[bot] commented 1 day ago

Thank you for your feedback. Tagging and routing to the team member best able to assist.