Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Web App For Containers: Certificate Authentication Fails with 403 when Client certificate is forwarded #51819

Closed
jaliyaudagedara opened this issue Nov 2, 2023 · 3 comments
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions

Comments

@jaliyaudagedara
Copy link

jaliyaudagedara commented Nov 2, 2023

I have an ASP.NET Core Web API that uses a self-signed certificate. Locally everything is working fine, but when deployed to Azure Web App for Containers and while using the same self-signed certificate, I am getting 403.

StatusCode: 403
WWW-Authenticate: [Redacted]

By passing ClientCertificateValidation.

 Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.ConfigureKestrel(options =>
        {
            options.ConfigureHttpsDefaults(options =>
            {
                options.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
                options.ClientCertificateValidation = ClientCertificateValidation;
            });
        });
        
        // Other code omitted for brevity.
    });

static bool ClientCertificateValidation(X509Certificate2 certificate, X509Chain? chain, SslPolicyErrors errors)
{
    return true;
}    

And in the startup,

public override void ConfigureServices(IServiceCollection services)
{
    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
    });
    services.AddCertificateForwarding(options =>
    {
        options.CertificateHeader = "X-ARR-ClientCert";
    });

    services
        .AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
        .AddCertificate(options =>
        {
            options.AllowedCertificateTypes = CertificateTypes.SelfSigned;
            options.RevocationMode = X509RevocationMode.NoCheck;
            options.ValidateCertificateUse = false;

            options.Events = new CertificateAuthenticationEvents
            {
                OnCertificateValidated = context =>
                {
                    var claims = new[]
                    {
                        new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
                        new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
                    };

                    context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));

                    context.Success();

                    return Task.CompletedTask;
                },
                OnAuthenticationFailed = context =>
                {
                    context.Fail("Invalid certificate");

                    return Task.CompletedTask;
                }
            };
        });

    services.AddAuthorization();

    // Other code omitted for brevity.
}

public override void Configure(IApplicationBuilder app,IWebHostEnvironment environment)
{
    app.UseForwardedHeaders();
    app.UseCertificateForwarding();
    app.UseHttpLogging();
    
    if (environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseRouting();
    
    app.UseHttpsRedirection();

    app.UseAuthentication();
    app.UseAuthorization();

    // Other code omitted for brevity.

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

From Application Insights, I can see the X-ARR-ClientCert header is getting sent.

Request: Protocol: HTTP/1.1 Method: GET Scheme: http PathBase: Path: /api/v1/values Accept: */* Host: app-csms-api-dev-001.azurewebsites.net User-Agent: PostmanRuntime/7.34.0 Accept-Encoding: gzip, deflate, br Cookie: [Redacted] Max-Forwards: 10 Postman-Token: [Redacted] X-ARR-LOG-ID: [Redacted] CLIENT-IP: [Redacted] X-Client-IP: [Redacted] DISGUISED-HOST:
 [Redacted] X-SITE-DEPLOYMENT-ID: [Redacted] WAS-DEFAULT-HOSTNAME: [Redacted] X-Forwarded-Proto: [Redacted] X-AppService-Proto: [Redacted] X-ARR-SSL: [Redacted] X-Forwarded-TlsVersion: 
 [Redacted] X-ARR-ClientCert: [Redacted] X-Forwarded-For: [Redacted] X-Original-URL: [Redacted] X-WAWS-Unencoded-URL: [Redacted] X-Client-Port: [Redacted]

From Azure, Client Certificate Mode is set to Optional.
image

And the weird thing is, If I deploy the application to Azure App Service (not container), it works.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Nov 2, 2023
@jaliyaudagedara
Copy link
Author

jaliyaudagedara commented Nov 2, 2023

When I enabled debug logging, I am seeing this,

ConnectionId	0HMURA92GR7I4
EventId	{"Id":1}
MessageTemplate	Unknown proxy: {RemoteIpAndPort}
RemoteIpAndPort	[::ffff:169.254.131.1]:46989
RequestId	0HMURA92GR7I4:00000003
RequestPath	/api/v1/values
SourceContext	Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware
_MS.ProcessedByMetricExtractors	(Name:'Traces', Ver:'1.1')

Wondering whether this is something?
https://github.com/dotnet/aspnetcore/blob/main/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs

image

@jaliyaudagedara
Copy link
Author

jaliyaudagedara commented Nov 2, 2023

Added following 2 lines, and that did it.
https://learn.microsoft.com/en-us/azure/app-service/app-service-web-configure-tls-mutual-auth?tabs=azurecli#aspnet-5-aspnet-core-31-sample

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;

    // Below 2 lines seems to do the trick
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

image

@jaliyaudagedara
Copy link
Author

Closing the issue.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

No branches or pull requests

1 participant