quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.58k stars 2.63k forks source link

OIDC - NullPointerException #26236

Closed beniaminp closed 2 years ago

beniaminp commented 2 years ago

Hello!

I am using quarkus:2.9.2.Final and configured oidc with the following properties:

quarkus.oidc.enabled=true
quarkus.oidc.discovery-enabled=false
quarkus.oidc.authentication.user-info-required=true
quarkus.oidc.roles.role-claim-path=roles
quarkus.oidc.proxy.host=my-proxy-host
quarkus.oidc.proxy.port=my-proxy-port
quarkus.oidc.auth-server-url=my-server-url
quarkus.oidc.client-id=my-client-id
quarkus.oidc.credentials.secret=my-client-secret
quarkus.oidc.token-path=/oauth/token
quarkus.oidc.authorization-path=/oauth/authorize
quarkus.oidc.introspection-path=/introspect
quarkus.oidc.user-info-path=/userinfo
quarkus.oidc.authentication.response_type=token
quarkus.oidc.authentication.add-openid-scope=false
quarkus.log.min-level=DEBUG
quarkus.log.category."io.quarkus.oidc".level=DEBUG

with all the variables for url, client and secret configured.

When I am trying to start it I get the following error:


- ERROR [io.qua.run.Application] (Quarkus Main Thread) Failed to start application (with profile dev): java.lang.NullPointerException
-   at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_Bean.create(Unknown Source)
-   at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_Bean.create(Unknown Source)
-   at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:111)
-   at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:35)
-   at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:32)
-   at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
-   at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
-   at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:32)
-   at io.quarkus.arc.impl.ClientProxies.getApplicationScopedDelegate(ClientProxies.java:19)
-   at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_ClientProxy.arc$delegate(Unknown Source)
-   at io.quarkus.oidc.runtime.DefaultTenantConfigResolver_ClientProxy.setSecurityEventObserved(Unknown Source)
-   at io.quarkus.oidc.runtime.OidcRecorder.setSecurityEventObserved(OidcRecorder.java:227)
-   at io.quarkus.deployment.steps.OidcBuildStep$findSecurityEventObservers1053450247.deploy_0(Unknown Source)
-   at io.quarkus.deployment.steps.OidcBuildStep$findSecurityEventObservers1053450247.deploy(Unknown Source)
-   at io.quarkus.runner.ApplicationImpl.doStart(Unknown Source)
-   at io.quarkus.runtime.Application.start(Application.java:101)
-   at io.quarkus.runtime.ApplicationLifecycleManager.run(ApplicationLifecycleManager.java:103)
-   at io.quarkus.runtime.Quarkus.run(Quarkus.java:67)
-   at io.quarkus.runtime.Quarkus.run(Quarkus.java:41)
-   at io.quarkus.runtime.Quarkus.run(Quarkus.java:120)
-   at com.company.Main.main(Main.java:20)
-   at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
-   at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
-   at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
-   at java.base/java.lang.reflect.Method.invoke(Method.java:566)
-   at io.quarkus.runner.bootstrap.StartupActionImpl$1.run(StartupActionImpl.java:103)

I do not event know where to start to investigate it. One thing that I noticed was that if I disabled the oidc, the application starts without error (if I set quarkus.oidc.enabled=false)

And clue from where to start?

Thank you!

quarkus-bot[bot] commented 2 years ago

/cc @pedroigor, @sberyozkin

sberyozkin commented 2 years ago

@beniaminp Hi, does it start in JVM mode, with java -jar target/quarkus-app/quarkus-run.jar ?

sberyozkin commented 2 years ago

@mkouba Hi Martin, can you please check the above stacktrace, may be you can spot something, the trace includes https://github.com/quarkusio/quarkus/blob/2.9.2.Final/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java#L225 which goes to https://github.com/quarkusio/quarkus/blob/2.9.2.Final/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java#L164

beniaminp commented 2 years ago

@sberyozkin I am using the idea intelij quarkus plugin to start it. Thanks!

sberyozkin commented 2 years ago

@beniaminp I'd like to understand if it is specific to dev mode or not, since there is Failed to start application (with profile dev):...

So please try to build the application from the command line and check if it starts in the prod profile. Or create a reproducer - it is hard to advise anything meaningful otherwise

beniaminp commented 2 years ago

Hi @sberyozkin. It is happening in debug mode. Unfortunately I cannot reproduce it in a simple project, it is happening on a project with closed sources and in dev mode. After some debugging, I am getting the following exception: Error injecting boolean io.quarkus.oidc.runtime.DefaultTenantConfigResolver.enableHttpForwardedPrefix and same NullPointerException. I have set the enableHttpForwardedPrefix to true / false but there is no difference, the same error.

I will run it in production mode and let you know how is going.

Thank you!

beniaminp commented 2 years ago

It looks like that in the prod mode I am having the same issue. If I disable the oidc everything works (except the authentication / authorization)

Thanks!

mkouba commented 2 years ago

@mkouba Hi Martin, can you please check the above stacktrace, may be you can spot something, the trace includes https://github.com/quarkusio/quarkus/blob/2.9.2.Final/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcRecorder.java#L225 which goes to https://github.com/quarkusio/quarkus/blob/2.9.2.Final/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DefaultTenantConfigResolver.java#L164

I don't see anything special. The NPE comes from the DefaultTenantConfigResolver_Bean.create() which looks like:

public DefaultTenantConfigResolver create(final CreationalContext creationalContext) {
        final DefaultTenantConfigResolver defaultTenantConfigResolver = new DefaultTenantConfigResolver();
        try {
            final InjectableReferenceProvider value = this.injectProviderSupplier1.get();
            defaultTenantConfigResolver.enableHttpForwardedPrefix = (boolean) value
                    .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value,
                            creationalContext));
        } catch (RuntimeException ex) {
            throw new RuntimeException(
                    "Error injecting boolean io.quarkus.oidc.runtime.DefaultTenantConfigResolver.enableHttpForwardedPrefix",
                    ex);
        }
        try {
            final InjectableReferenceProvider value2 = this.injectProviderSupplier2.get();
            defaultTenantConfigResolver.securityEvent = (Event) value2.get((CreationalContext) CreationalContextImpl
                    .child((InjectableReferenceProvider) value2, creationalContext));
        } catch (RuntimeException ex2) {
            throw new RuntimeException(
                    "Error injecting javax.enterprise.event.Event<io.quarkus.oidc.SecurityEvent> io.quarkus.oidc.runtime.DefaultTenantConfigResolver.securityEvent",
                    ex2);
        }
        try {
            final InjectableReferenceProvider value3 = this.injectProviderSupplier3.get();
            defaultTenantConfigResolver.tenantConfigBean = (TenantConfigBean) value3
                    .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value3,
                            creationalContext));
        } catch (RuntimeException ex3) {
            throw new RuntimeException(
                    "Error injecting io.quarkus.oidc.runtime.TenantConfigBean io.quarkus.oidc.runtime.DefaultTenantConfigResolver.tenantConfigBean",
                    ex3);
        }
        try {
            final InjectableReferenceProvider value4 = this.injectProviderSupplier4.get();
            defaultTenantConfigResolver.tenantConfigResolver = (Instance) value4
                    .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value4,
                            creationalContext));
        } catch (RuntimeException ex4) {
            throw new RuntimeException(
                    "Error injecting javax.enterprise.inject.Instance<io.quarkus.oidc.TenantConfigResolver> io.quarkus.oidc.runtime.DefaultTenantConfigResolver.tenantConfigResolver",
                    ex4);
        }
        try {
            final InjectableReferenceProvider value5 = this.injectProviderSupplier5.get();
            defaultTenantConfigResolver.tenantResolver = (Instance) value5.get((CreationalContext) CreationalContextImpl
                    .child((InjectableReferenceProvider) value5, creationalContext));
        } catch (RuntimeException ex5) {
            throw new RuntimeException(
                    "Error injecting javax.enterprise.inject.Instance<io.quarkus.oidc.TenantResolver> io.quarkus.oidc.runtime.DefaultTenantConfigResolver.tenantResolver",
                    ex5);
        }
        try {
            final InjectableReferenceProvider value6 = this.injectProviderSupplier6.get();
            defaultTenantConfigResolver.tokenIntrospectionCache = (Instance) value6
                    .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value6,
                            creationalContext));
        } catch (RuntimeException ex6) {
            throw new RuntimeException(
                    "Error injecting javax.enterprise.inject.Instance<io.quarkus.oidc.TokenIntrospectionCache> io.quarkus.oidc.runtime.DefaultTenantConfigResolver.tokenIntrospectionCache",
                    ex6);
        }
        try {
            final InjectableReferenceProvider value7 = this.injectProviderSupplier7.get();
            defaultTenantConfigResolver.tokenStateManager = (Instance) value7
                    .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value7,
                            creationalContext));
        } catch (RuntimeException ex7) {
            throw new RuntimeException(
                    "Error injecting javax.enterprise.inject.Instance<io.quarkus.oidc.TokenStateManager> io.quarkus.oidc.runtime.DefaultTenantConfigResolver.tokenStateManager",
                    ex7);
        }
        try {
            final InjectableReferenceProvider value8 = this.injectProviderSupplier8.get();
            defaultTenantConfigResolver.userInfoCache = (Instance) value8.get((CreationalContext) CreationalContextImpl
                    .child((InjectableReferenceProvider) value8, creationalContext));
        } catch (RuntimeException ex8) {
            throw new RuntimeException(
                    "Error injecting javax.enterprise.inject.Instance<io.quarkus.oidc.UserInfoCache> io.quarkus.oidc.runtime.DefaultTenantConfigResolver.userInfoCache",
                    ex8);
        }
        defaultTenantConfigResolver.verifyResolvers();
        return defaultTenantConfigResolver;
    }
sberyozkin commented 2 years ago

Thanks @beniaminp @mkouba

@beniaminp I honestly don't know how I can help without a reproducer. Can you create a HelloWorld endpoint protected with quarkus-oidc with the same configuration ?

beniaminp commented 2 years ago

Hey @sberyozkin . I tried in a new project with the same settings and it apparently work. Probably is something about my specific project (it is quite big). I am trying to debug it and to find the root cause of NPE. I will update here all the progress, maybe it can help.

Thanks!

sberyozkin commented 2 years ago

@beniaminp Sounds good, thanks, yes, please try to narrow down

beniaminp commented 2 years ago

Hey @sberyozkin. I upgraded to java16 an run with -XX:+ShowCodeDetailsInExceptionMessages and I got an explanation for the NPE. Does this mean something for you? (Quarkus Main Thread) Failed to start application (with profile dev): java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because "<local6>" is null

I think that it is related to quarkus.http.proxy.enable-forwarded-prefix=false, but I am not 100% sure. Also, I cannot find the DefaultTenantConfigResolver_Bean.create() in quarkus repo.

Thanks!

mkouba commented 2 years ago

Also, I cannot find the DefaultTenantConfigResolver_Bean.create() in quarkus repo.

It should be located in target/quarkus-app/quarkus/generated-bytecode.jar...

beniaminp commented 2 years ago

Thanks @mkouba !

Also, when the Arc container is trying to get the DefaultTenantConfigResolver it has an error: Method threw 'java.lang.RuntimeException' exception. Cannot evaluate io.quarkus.oidc.runtime.DefaultTenantConfigResolver_ClientProxy.toString(), on OidcRecorder.setSecurityEventObserved for call: DefaultTenantConfigResolver bean = Arc.container().instance(DefaultTenantConfigResolver.class).get(); and when it is trying to set the security event observed I get : Error injecting boolean io.quarkus.oidc.runtime.DefaultTenantConfigResolver.enableHttpForwardedPrefix.

Maybe you can see the problem faster then me :).

Thanks!

beniaminp commented 2 years ago

The problem looks to be in public DefaultTenantConfigResolver create(final CreationalContext creationalContext) at:

final DefaultTenantConfigResolver defaultTenantConfigResolver = new DefaultTenantConfigResolver(); try { final InjectableReferenceProvider value = this.injectProviderSupplier1.get(); defaultTenantConfigResolver.enableHttpForwardedPrefix = (boolean) value .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value, creationalContext)); } catch (RuntimeException ex) { throw new RuntimeException( "Error injecting boolean io.quarkus.oidc.runtime.DefaultTenantConfigResolver.enableHttpForwardedPrefix", ex); }

Somehow enableHttpForwardedPrefix cannot be injected and value .get((CreationalContext) CreationalContextImpl.child((InjectableReferenceProvider) value, creationalContext))

cannot be converted to boolean because some <local6> is null

mkouba commented 2 years ago

<local6> is null probaby means that there is a constructor which accepts a boolean parameter but a null argument is passed instead (i.e. autoboxing fails).

mkouba commented 2 years ago

@beniaminp just out of curiosity, could you try to replace @ConfigProperty(name = "quarkus.http.proxy.enable-forwarded-prefix") boolean enableHttpForwardedPrefix with @Inject HttpConfiguration httpConfig and httpConfig.proxy.enableForwardedPrefix?

mkouba commented 2 years ago

@sberyozkin I think that io.quarkus.oidc.deployment.OidcBuildStep.findSecurityEventObservers() should consume the RuntimeConfigSetupCompleteBuildItem to ensure the runtime config is set up (because DefaultTenantConfigResolver.enableHttpForwardedPrefix is a runtime config property). I'll send a PR shortly.

sberyozkin commented 2 years ago

Thanks, I can replace it as Martin suggested if it can help to fix it

mkouba commented 2 years ago

Thanks, I can replace it as Martin suggested if it can help to fix it

Actually, I don't think this would help. Testing my fix for https://github.com/quarkusio/quarkus/issues/26236#issuecomment-1163174888 right now...

beniaminp commented 2 years ago

I tested the change and unfortunately it did not work, I get the same error. The only way to pass above this step was to do some temporary changes in DefaultTenantConfigResolver: @Inject @ConfigProperty(name = "quarkus.http.proxy.enable-forwarded-prefix", defaultValue = "false") Optional<Boolean> enableHttpForwardedPrefix;

and boolean isEnableHttpForwardedPrefix() { return enableHttpForwardedPrefix.orElse(false); }

I am not sure why it cannot find the ConfigProperty yet.

Thanks!

mkouba commented 2 years ago

I tested the change and unfortunately it did not work, I get the same error.

That's not good :-(.

We really need a reproducer. @beniaminp What extensions does your app use?

beniaminp commented 2 years ago

I tried to reproduce it on an empty project, but everything works fine. In this project, I am migrating an old java ee project to quarkus. We did all the changes in order for quarkus to run and this is the only thing that is not working. If I disable the oidc, everything works fine. I did the changes on quarkus-oidc runtime on DefaultTenantConfigResolver and now it is working, but I have to check if it still takes into account the enableHttpForwardedPrefix property, because my guess is that it will always be false.

mkouba commented 2 years ago

I tried to reproduce it on an empty project, but everything works fine.

That's unfortunate. I'm afraid we can't do more without a reproducer...

I did the changes on quarkus-oidc runtime on DefaultTenantConfigResolver and now it is working, but I have to check if it still takes into account the enableHttpForwardedPrefix property, because my guess is that it will always be false.

Yes, you would have to use programmatic lookup and lazy injection, i.e. something like @ConfigProperty(name = "quarkus.http.proxy.enable-forwarded-prefix") Instance<Boolean> enableHttpForwardedPrefix and then enableHttpForwardedPrefix.get().

beniaminp commented 2 years ago

We finally manage to find the problem. After we remove the dependency: `

org.eclipse.microprofile.config
        <artifactId>microprofile-config-api</artifactId>
        <version>3.0.1</version>

` we got no more NPE.

It looks like quarkus does not play nice with it. At least, now we know :).

mkouba commented 2 years ago

Ah, that's some good news! Yes, quarkus is using org.eclipse.microprofile.config:microprofile-config-api:2.0.1 and you should not bring any MP dependencies manually but rely on the versions included by quarkus extensions instead.