data-api-builder icon indicating copy to clipboard operation
data-api-builder copied to clipboard

Support for User Assigned Managed identity - CosmosDB NoSQL / Container Apps

Open jmkelljr opened this issue 2 years ago • 3 comments

What happened?

A bug happened!

Version

Microsoft.DataApiBuilder 0.9.7+e560142426d1c080b9fd7b7fabff51a276f6bf61

What database are you using?

CosmosDB NoSQL

What hosting model are you using?

Container Apps

Which API approach are you accessing DAB through?

GraphQL

Relevant log output

Please support RBAC Authentication for CosmosDB via a ManagedIdentity.  My company's security policies do not allow local authentication via keys.

I get the following error when trying to connect with the master key
{
  "errors": [
    {
      "message": "Response status code does not indicate success: Unauthorized (401); Substatus: 5202; ActivityId: ccfad820-3641-48e2-8808-8b0c3993f8f8; Reason: (Local Authorization is disabled. Use an AAD token to authorize all requests.\r\nActivityId: ccfad820-3641-48e2-8808-8b0c3993f8f8, Microsoft.Azure.Documents.Common/2.14.0, Linux/2.0 cosmos-netstandard-sdk/3.19.3);",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ],
      "path": [
        "books"
      ]
    }
  ]
}

For more info see... 

https://joonasaijala.com/2021/07/01/how-to-using-managed-identities-to-access-cosmos-db-data-via-rbac-and-disabling-authentication-via-keys/

Code of Conduct

  • [X] I agree to follow this project's Code of Conduct

jmkelljr avatar Dec 20 '23 19:12 jmkelljr

I realized that if I didn't specify the "AccountKey" parameter in the connection string that the code would drop into trying to connect with a managed identity. However, I am using a user-assigned managed identity and now have this issue:

fail: Azure.DataApiBuilder.Service.Startup[0] A GraphQL request execution error occurred. Azure.Identity.AuthenticationFailedException: ManagedIdentityCredential authentication failed: Service request failed. Status: 400 (Bad Request)

  Content:
  {"statusCode":400,"message":"Unable to load the proper Managed Identity.","correlationId":"c4d8cdec-5a73-4a90-88f9-8b6fcef97dac"}
  
  Headers:
  Date: Wed, 20 Dec 2023 21:58:57 GMT
  Server: Kestrel
  Transfer-Encoding: chunked
  X-CORRELATION-ID: REDACTED
  Content-Type: application/json; charset=utf-8
  
  See the troubleshooting guide for more information. https://aka.ms/azsdk/net/identity/managedidentitycredential/troubleshoot
   ---> Azure.RequestFailedException: Service request failed.
  Status: 400 (Bad Request)
  
  Content:
  {"statusCode":400,"message":"Unable to load the proper Managed Identity.","correlationId":"c4d8cdec-5a73-4a90-88f9-8b6fcef97dac"}
  
  Headers:
  Date: Wed, 20 Dec 2023 21:58:57 GMT
  Server: Kestrel
  Transfer-Encoding: chunked
  X-CORRELATION-ID: REDACTED
  Content-Type: application/json; charset=utf-8
  
     at Azure.Identity.ManagedIdentitySource.HandleResponseAsync(Boolean async, TokenRequestContext context, Response response, CancellationToken cancellationToken)
     at Azure.Identity.ManagedIdentitySource.AuthenticateAsync(Boolean async, TokenRequestContext context, CancellationToken cancellationToken)
     at Azure.Identity.ManagedIdentityClient.AuthenticateCoreAsync(Boolean async, TokenRequestContext context, CancellationToken cancellationToken)
     at Azure.Identity.ManagedIdentityClient.AppTokenProviderImpl(AppTokenProviderParameters parameters)
     at Microsoft.Identity.Client.Internal.Requests.ClientCredentialRequest.SendTokenRequestToAppTokenProviderAsync(ILoggerAdapter logger, CancellationToken cancellationToken)
     at Microsoft.Identity.Client.Internal.Requests.ClientCredentialRequest.GetAccessTokenAsync(CancellationToken cancellationToken, ILoggerAdapter logger)
     at Microsoft.Identity.Client.Internal.Requests.ClientCredentialRequest.ExecuteAsync(CancellationToken cancellationToken)
     at Microsoft.Identity.Client.Internal.Requests.RequestBase.RunAsync(CancellationToken cancellationToken)
     at Microsoft.Identity.Client.ApiConfig.Executors.ConfidentialClientExecutor.ExecuteAsync(AcquireTokenCommonParameters commonParameters, AcquireTokenForClientParameters clientParameters, CancellationToken cancellationToken)
     at Azure.Identity.AbstractAcquireTokenParameterBuilderExtensions.ExecuteAsync[T](AbstractAcquireTokenParameterBuilder`1 builder, Boolean async, CancellationToken cancellationToken)
     at Azure.Identity.MsalConfidentialClient.AcquireTokenForClientCoreAsync(String[] scopes, String tenantId, Boolean enableCae, Boolean async, CancellationToken cancellationToken)
     at Azure.Identity.MsalConfidentialClient.AcquireTokenForClientAsync(String[] scopes, String tenantId, Boolean enableCae, Boolean async, CancellationToken cancellationToken)
     at Azure.Identity.ManagedIdentityClient.AuthenticateAsync(Boolean async, TokenRequestContext context, CancellationToken cancellationToken)
     at Azure.Identity.ManagedIdentityCredential.GetTokenImplAsync(Boolean async, TokenRequestContext requestContext, CancellationToken cancellationToken)
     --- End of inner exception stack trace ---
     at Microsoft.Azure.Cosmos.Routing.GlobalEndpointManager.GetAccountPropertiesHelper.GetAccountPropertiesAsync()
     at Microsoft.Azure.Cosmos.GatewayAccountReader.InitializeReaderAsync()
     at Microsoft.Azure.Cosmos.CosmosAccountServiceConfiguration.InitializeAsync()
     at Microsoft.Azure.Cosmos.DocumentClient.InitializeGatewayConfigurationReaderAsync()
     at Microsoft.Azure.Cosmos.DocumentClient.GetInitializationTaskAsync(IStoreClientFactory storeClientFactory)
     at Microsoft.Azure.Cosmos.DocumentClient.EnsureValidClientAsync(ITrace trace)
     at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.EnsureValidClientAsync(RequestMessage request, ITrace trace)
     at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.SendAsync(RequestMessage request, CancellationToken cancellationToken)
     at Microsoft.Azure.Cosmos.Handlers.RequestInvokerHandler.SendAsync(String resourceUriString, ResourceType resourceType, OperationType operationType, RequestOptions requestOptions, ContainerInternal cosmosContainerCore, FeedRange feedRange, Stream streamPayload, Action`1 requestEnricher, ITrace trace, CancellationToken cancellationToken)
     at Microsoft.Azure.Cosmos.ContainerCore.ReadContainerAsync(ITrace trace, ContainerRequestOptions requestOptions, CancellationToken cancellationToken)
     at Microsoft.Azure.Cosmos.ClientContextCore.RunWithDiagnosticsHelperAsync[TResult](ITrace trace, Func`2 task)
     at Microsoft.Azure.Cosmos.ClientContextCore.OperationHelperWithRootTraceAsync[TResult](String operationName, RequestOptions requestOptions, Func`2 task, TraceComponent traceComponent, TraceLevel traceLevel)
     at Azure.DataApiBuilder.Core.Resolvers.CosmosQueryEngine.GetPartitionKeyPath(Container container, ISqlMetadataProvider metadataStoreProvider) in /src/src/Core/Resolvers/CosmosQueryEngine.cs:line 267
     at Azure.DataApiBuilder.Core.Resolvers.CosmosQueryEngine.GetIdAndPartitionKey(IDictionary`2 parameters, Container container, CosmosQueryStructure structure, ISqlMetadataProvider metadataStoreProvider) in /src/src/Core/Resolvers/CosmosQueryEngine.cs:line 278
     at Azure.DataApiBuilder.Core.Resolvers.CosmosQueryEngine.ExecuteAsync(IMiddlewareContext context, IDictionary`2 parameters, String dataSourceName) in /src/src/Core/Resolvers/CosmosQueryEngine.cs:line 72
     at Azure.DataApiBuilder.Core.Services.ResolverMiddleware.InvokeAsync(IMiddlewareContext context) in /src/src/Core/Services/ResolverMiddleware.cs:line 106
     at HotChocolate.Utilities.MiddlewareCompiler`1.ExpressionHelper.AwaitTaskHelper(Task task)
     at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)
     at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)

info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'Hot Chocolate GraphQL Pipeline' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished HTTP/1.1 POST http://graphqlapp.nicesky-e371e381.eastus2.azurecontainerapps.io/graphql application/json 117 - 500 - application/json;+charset=utf-8 68.6274ms

It appears this is a known issue with SDK's in Azure Container Apps as referenced here:

https://github.com/microsoft/azure-container-apps/issues/442

You may need to modify how you are getting the credential to allow for the config file to specify the AZURE_CLIENT_ID

jmkelljr avatar Dec 20 '23 22:12 jmkelljr

Thank you for raising this issue. We are tracking this ask via #1944. Currently, only system assigned managed identities are supported.

seantleonard avatar Jan 02 '24 19:01 seantleonard

we are actually using a user assigened managed identity and this does work. you need to specify the clientid in an environment variable called AZURE_CLIENT_ID in the container apps app.

dgcaron avatar Sep 27 '24 14:09 dgcaron

Specifying AZURE_CLIENT_ID is the typical behavior for DefaultAzureCredential and this "trick" should work on many different Azure hosts where you want to use a user-assigned managed identity instead of system-assigned.

Thanks @dgcaron for sharing!

seesharprun avatar Jan 22 '25 13:01 seesharprun

@dgcaron, @jmkelljr

You can see it implemented in https://github.com/Azure-Samples/dab-azure-cosmos-db-nosql-quickstart

Here's some examples:

  • Bicep: https://github.com/Azure-Samples/dab-azure-cosmos-db-nosql-quickstart/blob/main/infra/main.bicep#L216-L219
  • Config: https://github.com/Azure-Samples/dab-azure-cosmos-db-nosql-quickstart/blob/main/src/api/dab-config.json#L5

To make it happen, the connection string should be of syntax: AccountEndpoint=<cosmos-db-nosql-account-endpoint>; and you must specify the AZURE_CLIENT_ID environment variable or specify it in the TokenCredential in code.

CC: @seantleonard, @sajeetharan

seesharprun avatar Jan 24 '25 03:01 seesharprun