tnc1997 / azure-app-configuration-emulator

Please note that Emulator for Azure App Configuration is unofficial and not endorsed by Microsoft.
MIT License
3 stars 3 forks source link

Add support for Microsoft Entra ID authentication #76

Closed goldsam closed 2 months ago

goldsam commented 2 months ago

My organization has many applications which are configured/restricted to use only Azure Entra (Specifically client secret) authentication when accessing any Azure service. We strive to test our applications in as similar an environment as productions as is possible without creating test-only code paths. Thus, it would be beneficial to authenticate with your app configuration emulator either using Entra auth or to be able to disable authentication requirements entirely.

tnc1997 commented 2 months ago

Hi @goldsam, thank you for your feature request!

Could I ask you to clone the branch feat/76 and test the Microsoft Entra ID authentication scheme?

goldsam commented 2 months ago

I would be happy to, but I may not have time until this coming weekend.

goldsam commented 2 months ago

I've been testing branch feat/76, and I have some feedback:

tnc1997 commented 2 months ago

I would recommend removing the following two lines:

https://github.com/tnc1997/azure-app-configuration-emulator/blob/fa6514cec07d8a0a61eaf9c07cd5a86bc015e090/src/AzureAppConfigurationEmulator/Program.cs#L37-L38

My justification is:

  • ValidateAudience and ValidateIssuer are true by default.
  • Overriding them in this manner prevents a user from setting them to false should they want to.

NOTE: It was not initially obvious to me what the Audience and Issuer should be.

I would like to retain these two lines because the audience is a static value and the issuer is automatically determined from the discovery document that is based on the MetadataAddress for the Microsoft Entra tenant. That being said I have moved the ValidAudience into appsettings.json to allow it to optionally be overridden to e.g. https://contoso.azconfig.io if desired. It is also worth noting that ValidateAudience and ValidateIssuer are set by JwtBearerConfigureOptions based on the presence of one or more audiences and one or more issuers respectively.

tnc1997 commented 2 months ago
  • Using the official ConfigurationClient with Entra Id authentication turned out to be non-trivial. I had solve the following challenges:

Unfortunately this is one of the issues of using an emulated Azure resource with a genuine Microsoft Entra tenant. The genuine Microsoft Entra tenant will not have any knowledge of the emulated Azure resource. This is likely one of the reasons that the Azure Cosmos DB emulator for example only allows authentication using a key and not Microsoft Entra ID.

tnc1997 commented 2 months ago

I would love to find a better way to solve this issue.

This is not necessarily a better way to solve the issue, but if you are emulating a genuine Azure App Configuration resource, then you could alternatively configure the emulator as if it is the genuine Azure App Configuration resource:

openssl req -x509 -out ./emulator.crt -keyout ./emulator.key -newkey rsa:2048 -nodes -sha256 -subj '/CN=contoso.azconfig.io' -addext 'subjectAltName=DNS:contoso.azconfig.io'
using Azure.Data.AppConfiguration;
using Azure.Identity;

var tenantId = Environment.GetEnvironmentVariable("Authentication__Schemes__MicrosoftEntraId__TenantId")!;
var clientId = Environment.GetEnvironmentVariable("Authentication__Schemes__MicrosoftEntraId__ClientId")!;
var clientSecret = Environment.GetEnvironmentVariable("Authentication__Schemes__MicrosoftEntraId__ClientSecret")!;
var credential = new ClientSecretCredential(tenantId, clientId, clientSecret);

var endpoint = Environment.GetEnvironmentVariable("AzureAppConfiguration__Endpoint")!;
var client = new ConfigurationClient(new Uri(endpoint), credential);

var setting = new ConfigurationSetting("AzureAppConfigurationEmulator", "Hello World");
await client.SetConfigurationSettingAsync(setting);
services:
  azure-app-configuration-emulator:
    build:
      context: https://github.com/tnc1997/azure-app-configuration-emulator.git
      dockerfile: ./src/AzureAppConfigurationEmulator/Dockerfile
    environment:
      - ASPNETCORE_HTTP_PORTS=80
      - ASPNETCORE_HTTPS_PORTS=443
      - Authentication__Schemes__MicrosoftEntraId__MetadataAddress=https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/v2.0/.well-known/openid-configuration
      - Authentication__Schemes__MicrosoftEntraId__ValidAudience=https://contoso.azconfig.io
    networks:
      default:
        aliases:
          - contoso.azconfig.io
    volumes:
      - ./emulator.crt:/usr/local/share/azureappconfigurationemulator/emulator.crt:ro
      - ./emulator.key:/usr/local/share/azureappconfigurationemulator/emulator.key:ro
  console-application:
    build: .
    depends_on:
      - azure-app-configuration-emulator
    entrypoint: /bin/sh -c "update-ca-certificates && dotnet ConsoleApplication.dll"
    environment:
      - Authentication__Schemes__MicrosoftEntraId__ClientId
      - Authentication__Schemes__MicrosoftEntraId__ClientSecret
      - Authentication__Schemes__MicrosoftEntraId__TenantId=00000000-0000-0000-0000-000000000000
      - AzureAppConfiguration__Endpoint=https://contoso.azconfig.io
    volumes:
      - ./emulator.crt:/usr/local/share/ca-certificates/emulator.crt:ro
goldsam commented 2 months ago

I would say this is a lot better! Documenting this strategy would be very helpful to new users.

Thank you for this wisdom, I will be tinkering with this todayπŸ˜‰

As I mentioned before, a lot of this work could even be captured in a Testcontainers library. Any interest in supporting this? If so, it would require that an image is published to a registry somewhere.

I can update you with my feedback on your most recent changes near the end of today (I'm EST time zone). However, with the exception of possible documentation enhancements, this looks pretty sound.

Thank your for addressing this so quickly and engaging with me. Your project is pretty awesome!

tnc1997 commented 2 months ago

I would say this is a lot better!

Thank you very much, it requires genuine resources for the emulator to emulate, but it reduces code complexity.

Documenting this strategy would be very helpful to new users.

Hopefully you agree that the latest update to the Microsoft Entra ID section document this approach well enough.

Any interest in supporting this?

This is definitely something that I will look at in more detail once an image has been published to Docker Hub.

goldsam commented 2 months ago

I ran into another snag - Entra still would need to trust https://contoso.azconfig.io. Is there some way to configure my Entra Id Application registration appropriately?

tnc1997 commented 2 months ago

Entra still would need to trust https://contoso.azconfig.io.

https://contoso.azconfig.io was just being used as an example endpoint. You would need to create an Azure App Configuration resource, then grant your registered Microsoft Entra application the appropriate role(s) for that resource.

goldsam commented 2 months ago

Hi again πŸ˜€After a few days of working with this, I am going to stand my ground and reiterate my suggestion to remove those two lines.

While I appreciate your security values, I think it is sufficient to choose safe default settings and allow users to override them should they want to (as I do). In my case, I struggled quite a bit with these in my particular use case where just disabling validation is fine (as my testing runs a completely isolated environment).

As I mentioned before, ValidateAudience and ValidateIssuer are true by default. Individuals who tinker are going to be technical chaps who accept the risk. I'm really hoping I can persuade you on this 😁

tnc1997 commented 2 months ago

Hi @goldsam, thank you very much for getting back to me!

As I mentioned before, ValidateAudience and ValidateIssuer are true by default.

I'm not sure if you saw my previous reply on Friday 7th June but this is not the case for the JwtBearerOptions.

    .AddJwtBearer("MicrosoftEntraId", options =>
    {
        // options.TokenValidationParameters.ValidateAudience = true;
        // options.TokenValidationParameters.ValidateIssuer = true;

        options.ForwardDefaultSelector = context =>
        {
            if (AuthenticationHeaderValue.TryParse(context.Request.Headers.Authorization, out var value))
            {
                if (value.Scheme.Equals("HMAC-SHA256", StringComparison.OrdinalIgnoreCase))
                {
                    return HmacDefaults.AuthenticationScheme;
                }
            }

            return null;
        };
    });

image

The metadata address is required and that implicitly provides the issuer to use for token validation whilst the ValidAudience is overridable depending on the scope of the role that is assigned to the service principal.

if (string.IsNullOrWhiteSpace(validationParameters.ValidIssuer) && validationParameters.ValidIssuers.IsNullOrEmpty<string>() && string.IsNullOrWhiteSpace(configuration?.Issuer))

If possible could I ask which parts of the configuration were difficult so that we might improve the documentation?

goldsam commented 2 months ago

I'm sorry - I must have been working way to late when I was last testing. I was able to get things working fine with your most recent commit. You can merge your code and call this one done.

For what it is worth, I was able to avoid the need to create a real app configuration instance as follows:

  1. Using the ScopesOverridingTokenCredential class above, I override the scope used when authenticating to be https://azconfig.io/.default (the original scope was https://{docker-network-address-for-emulator}/.default)

  2. Configure the appropriate Entra metadata address for my organization.

  3. Configure ValidIssuer as https://sts.windows.net/{tenantid}/ by setting the Authentication__Schemes__MicrosoftEntraId__ValidIssuer env-var.

BTW: Thank you for your patience on this πŸ˜‰

tnc1997 commented 2 months ago

For what it is worth, I was able to avoid the need to create a real app configuration instance as follows:

Yeah a genuine Azure App Configuration resource can be avoided by modifying the role from being resource level scoped https://<endpoint>.azconfig.io/.default to being subscription level scoped https://azconfig.io/.default.

BTW: Thank you for your patience on this πŸ˜‰

No problem at all, you're welcome, glad that we could get this working πŸ˜„