functions-authorize icon indicating copy to clipboard operation
functions-authorize copied to clipboard

Azure broke auth

Open helgeu opened this issue 1 year ago • 11 comments
trafficstars

We have been using Darkloop and FunctionAuthorize for a long time. A few days back this just stopped working totally when we are deploying new versions of our code (no changes to Darkloop or auth), and the azure functions are dead upon deployment. Fortunately we have these in slot so prod was not affected.

image

The function app keys are also totally dead.

image

Removing Darkloop nuget package its possible to get the Azure function running again, but of course the authorization is gone.

Reporting this to Microsoft first since we was rather clear something had changed in Azure. Microsoft admits this but blames the way Darkloop is implemented.

image

We are using f# for Azure functions and have this code:

namespace AzureFunctions.Startup

open ServiceSetup
open AzureFunctionsDependencyInjectionExtensions.Config
open Microsoft.Azure.Functions.Extensions.DependencyInjection
open Microsoft.Extensions.DependencyInjection
open Microsoft.AspNetCore.Authentication.JwtBearer
open System
open Microsoft.AspNetCore.Authentication
open Microsoft.Extensions.Configuration

[<System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage>]
module DependencyInjectionStartup =
    let setupAuthenticationScheme (options: AuthenticationOptions) =
        options.DefaultAuthenticateScheme <- JwtBearerDefaults.AuthenticationScheme
        options.DefaultChallengeScheme <- JwtBearerDefaults.AuthenticationScheme

    let setupJwtAuthorization authority audience (options: JwtBearerOptions) =
        options.Authority <- authority
        options.Audience <- audience

    type HealthCheckSetupFunctionStartup() =
        inherit FunctionsStartup()

        override this.Configure(builder: IFunctionsHostBuilder) : unit =
            let services = builder.Services
            let sp = services.BuildServiceProvider()
            let conf = sp.GetService<IConfiguration>()

            let authIssuer = conf["Authorization:Issuer"]
            let authAudience = conf["Authorization:Audience"]

            let setupJwtAuth = setupJwtAuthorization authIssuer authAudience

            // For some reason, authentication setup needs to be called here
            // there is some problem with doing it withing AddDependencyInjection
            // reason unknown, needs further investigation
            builder.Services
                .AddFunctionsAuthentication(Action<AuthenticationOptions>(setupAuthenticationScheme))
                .AddJwtBearer(Action<JwtBearerOptions>(setupJwtAuth), true)
            |> ignore

            builder.Services.AddFunctionsAuthorization() |> ignore

            builder.AddDependencyInjection(fun s -> s |> setupServices conf) |> ignore

    [<assembly: FunctionsStartup(typeof<HealthCheckSetupFunctionStartup>)>]
    do ()

See also caveats from Microsoft related to auth*:

https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#caveats

image

I do suspect we might have some bad code after all, and any pointers for helping out would be highly appreciated.

Thanks in advance.

helgeu avatar May 03 '24 19:05 helgeu

I can confirm this. I just checked the status of two function apps, one running on runtime 3.x (deprecated) and the other on 4.x. both show the error status, but the apps still respond as expected. In the log stream errors show that the portal is unable to authenticate, hence the error.

Note: we did not upgrade to the 4.x nuget package of this project yet.

Last deploy of the 3.x app was on April 18 and the 4.x app was on May 3

josbol avatar May 03 '24 22:05 josbol

I can confirm this in in-process functions as well. After some debugging, I found out that if I set ’removeBuiltInConfig’ parameter in ’AddJwtBearer’ to ’false’, the function seems to work as it used to be. Haven’t done further validations yet, but wanted to share my findings asap if others are struggling with this too.

Edit: This was a bit too premature. I’m using the Jwt tokens for the function authentication as well, and those don’t work anymore with the ’false’ value. So in my case it seems that either my frontend can auhtenticate towards the az funtions, or the portal can.

Wotee avatar May 04 '24 12:05 Wotee

EDIT: More tests

set ’removeBuiltInConfig’ parameter in ’AddJwtBearer’ to ’false’

I tested your suggestion on both an 3.x app and a 4.x app, but unfortunately it didn't work :(

4.x Function app Darkloop 4.0.1 Functions SDK 4.3.0 Only uses one authentication scheme: Bearer Error does not go away and functions with the Authorize attribute stop working.

3.x Function app Darkloop 3.1.3 Azure Functions SDK 3.1.2 Only uses one authentication scheme: Bearer Error goes away, but functions with the Authorize attribute stop working.

In either case (true/false) both apps respond to http calls, so the function app is running, however validating JWT tokens fail when setting the removeBuiltInConfig to false.

I have other apps that use multiple authentication schemes and/or Microsoft AAD which I haven't tested yet. Especially the AAD auth scheme is very finicky when it comes to compatibility with certain other nuget packages, so I guess it's better to stick with testing the simpler setups first?

josbol avatar May 04 '24 14:05 josbol

Specifiying a scheme name when using a AddJwtBearer() seems to be solving the problem:

.AddJwtBearer("YourSchemeName",options => { .... });

Note that in this case the removeBuiltInConfig parameter has to be removed.

This causes authentication of the functions to work and I don't see any errors in the azure portal.

josbol avatar May 06 '24 16:05 josbol

I will take a look at this soon.

artmasa avatar May 07 '24 01:05 artmasa

The named variant did the trick for us too. So it might just be a matter of updating the documentation part of the readme a bit @artmasa and include this "trick" as a fix for the change in Azure.

helgeu avatar May 07 '24 07:05 helgeu

The variant with the scheme name seems to fix our problems as well

Wotee avatar May 07 '24 09:05 Wotee

Yes, any custom scheme should work. That's the reason I created AddJwtBearer with removeBuiltInConfig, for specifically using the standard Bearer scheme. In the past this scheme was only added by the functions host framework but not really used by the host. Under the hood, this method was removing anything injected to services by the host framework as IConfigureOptions<JwtBearerOptions>("Bearer", ...) and then adding what you specify in startup.cs for In-Process or program.cs for Isolated.

It seems now, and I think because of the company wide security push (which I think it's great), the portal is enforcing using this scheme to communicate with the host to retrieve runtime and functions information. I wish they had used a custom scheme for this purpose and leave the general one for consumers. Although this is not officially supported.

It's also breaking to some extent the Isolated functions. At least you are able to see the functions in the portal, but runtime information shows Error like in the image above.

Thanks for reporting this. I will adjust the library documentation and mark the default scheme method obsolete with a link to documentation.

artmasa avatar May 08 '24 20:05 artmasa

Knowing "Bearer" can't be used anymore without breaking stuff on azure, what about creating a overloading method that instead of clearing the previous config, simply registers it by default with "BearerCustom" and have the FunctionAuthorize attribute default to that scheme as well? This possibly breaks backward compatibility, but even so I think it's preferable over having to specify the custom scheme with every use of the authorizefunction attribute.

josbol avatar May 09 '24 13:05 josbol

@josbol, my fear is that this can cause confusion, by just leaving it as AddJwtBearer with no parameter for name, communicates to the user "Bearer" scheme is being configured. This is what I'm proposing

[Obsolete("<deprecation-reason>. Use AddJwtFunctionsBearer instead")]
public static AuthenticationBuilder AddJwtBearer(
    this FunctionsAuthenticationBuilder builder, Action<JwtBearerOptions> configure, bool removeBuiltInConfig = true)
{
    ...
}

public static AuthenticationBuilder AddJwtFunctionsBearer(
    this FunctionsAuthenticationBuilder builder, Action<JwtBearerOptions> configure)
{
    return builder.AddJwtBearer("FunctionsBearer", configure);
}

Also, we can add an analyzer library, so that when users use even the ASPNET Core provided AddJwtBearer(Action<JwtBearerOptions> configure) against FunctionsAuthenticationBuilder it will produce a warning telling them this configuration conflicts with the one used by the functions host.

WDYT?

artmasa avatar May 09 '24 13:05 artmasa

Sounds good to me!

josbol avatar May 09 '24 13:05 josbol

Here is the PR #50 for the proposed changes. Can you guys help me review. I would like to get feedback on the readme docs?

artmasa avatar May 21 '24 14:05 artmasa

I reviewed the PR and left some comments, but after it was merged. It's just some comments for the docs though, nothing critical.

josbol avatar May 21 '24 18:05 josbol

Thanks @josbol, taking a look

artmasa avatar May 21 '24 19:05 artmasa

I will go ahead and close this issue.

artmasa avatar May 22 '24 20:05 artmasa