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

Smallrye JWT isn't compatible with native image #3496

Closed
aesteve opened this issue Aug 13, 2019 · 13 comments
Closed

Smallrye JWT isn't compatible with native image #3496

aesteve opened this issue Aug 13, 2019 · 13 comments
Labels
kind/question Further information is requested

Comments

@aesteve
Copy link

aesteve commented Aug 13, 2019

Describe the bug
Securing APIs through the smallrye-jwt extension works fine in dev mode (and I'm guessing jar mode is OK too) but fails with an exception when used in a native image.

Expected behavior
It should work the same way in native mode than in dev mode, and not fail with an exception

Actual behavior

2019-08-13 07:47:06,443 ERROR [io.und.req.io] (executor-thread-1) Exception handling request 73085ca2-c294-444a-b71d-e616a31e0e73-1 to <<<endpoint>>>>: java.lang.UnsatisfiedLinkError: sun.security.ec.ECKeyPairGenerator.isCurveSupported([B)Z [symbol: Java_sun_security_ec_ECKeyPairGenerator_isCurveSupported or Java_sun_security_ec_ECKeyPairGenerator_isCurveSupported___3B]
	at com.oracle.svm.jni.access.JNINativeLinkage.getOrFindEntryPoint(JNINativeLinkage.java:145)
	at com.oracle.svm.jni.JNIGeneratedMethodSupport.nativeCallAddress(JNIGeneratedMethodSupport.java:57)
	at sun.security.ec.ECKeyPairGenerator.isCurveSupported(ECKeyPairGenerator.java)
	at sun.security.ec.ECKeyPairGenerator.ensureCurveIsSupported(ECKeyPairGenerator.java:135)
	at sun.security.ec.ECKeyPairGenerator.initialize(ECKeyPairGenerator.java:114)
	at java.security.KeyPairGenerator$Delegate.initialize(KeyPairGenerator.java:674)
	at sun.security.ssl.ECDHCrypt.<init>(ECDHCrypt.java:77)
	at sun.security.ssl.ClientHandshaker.serverKeyExchange(ClientHandshaker.java:783)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:302)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
	at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:347)
	at org.jose4j.http.Get.get(Get.java:80)
	at org.jose4j.jwk.HttpsJwks.refresh(HttpsJwks.java:204)
	at org.jose4j.jwk.HttpsJwks.getJsonWebKeys(HttpsJwks.java:161)
	at io.smallrye.jwt.auth.principal.KeyLocationResolver.tryAsJWK(KeyLocationResolver.java:116)
	at io.smallrye.jwt.auth.principal.KeyLocationResolver.resolveKey(KeyLocationResolver.java:80)
	at org.jose4j.jwt.consumer.JwtConsumer.processContext(JwtConsumer.java:213)
	at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:433)
	at io.smallrye.jwt.auth.principal.DefaultJWTTokenParser.parse(DefaultJWTTokenParser.java:87)
	at io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator.validateClaimsSet(MpJwtValidator.java:41)
	at io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator.validate(MpJwtValidator.java:35)
	at io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator_ClientProxy.validate(MpJwtValidator_ClientProxy.zig:164)
	at org.wildfly.security.auth.realm.token.TokenSecurityRealm$TokenRealmIdentity.validateToken(TokenSecurityRealm.java:207)
	at org.wildfly.security.auth.realm.token.TokenSecurityRealm$TokenRealmIdentity.getClaims(TokenSecurityRealm.java:195)
	at org.wildfly.security.auth.realm.token.TokenSecurityRealm$TokenRealmIdentity.exists(TokenSecurityRealm.java:157)
	at org.wildfly.security.auth.realm.token.TokenSecurityRealm$TokenRealmIdentity.getEvidenceVerifySupport(TokenSecurityRealm.java:186)
	at org.wildfly.security.auth.server.ServerAuthenticationContext$UnassignedState.verifyEvidence(ServerAuthenticationContext.java:1659)
	at org.wildfly.security.auth.server.ServerAuthenticationContext$InactiveState.verifyEvidence(ServerAuthenticationContext.java:1391)
	at org.wildfly.security.auth.server.ServerAuthenticationContext.verifyEvidence(ServerAuthenticationContext.java:759)
	at org.wildfly.security.auth.server.SecurityDomain.authenticate(SecurityDomain.java:309)
	at org.wildfly.security.auth.server.SecurityDomain.authenticate(SecurityDomain.java:270)
	at io.quarkus.smallrye.jwt.runtime.auth.JwtIdentityManager.verify(JwtIdentityManager.java:36)
	at io.quarkus.smallrye.jwt.runtime.auth.JWTAuthMechanism.authenticate(JWTAuthMechanism.java:53)
	at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.transition(SecurityContextImpl.java:245)
	at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.transition(SecurityContextImpl.java:268)
	at io.undertow.security.impl.SecurityContextImpl$AuthAttempter.access$100(SecurityContextImpl.java:231)
	at io.undertow.security.impl.SecurityContextImpl.attemptAuthentication(SecurityContextImpl.java:125)
	at io.undertow.security.impl.SecurityContextImpl.authTransition(SecurityContextImpl.java:99)
	at io.undertow.security.impl.SecurityContextImpl.authenticate(SecurityContextImpl.java:92)
	at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:55)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
	at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
	at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
	at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
	at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
	at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
	at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
	at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
	at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
	at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
	at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$8$1$1.call(UndertowDeploymentRecorder.java:489)
	at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
	at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
	at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:364)
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1426)
	at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
	at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
	at java.lang.Thread.run(Thread.java:748)
	at org.jboss.threads.JBossThread.run(JBossThread.java:479)
	at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:460)
	at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)

To Reproduce
Steps to reproduce the behavior:
Follow the tutorial and build a native image.
Then execute it and try to access any secured endpoint. You'll get the stacktrace above.

Configuration

# HTTP Server config
quarkus.http.port=9090
# DataSource
# Hibernate
quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect
#quarkus.hibernate-orm.log.sql=true
# OpenAPI / Swagger
quarkus.smallrye-openapi.path=/swagger
quarkus.swagger-ui.always-include=true
quarkus.swagger-ui.path=/swagger-ui
# JWT / Security
## Issuer
%dev.mp.jwt.verify.issuer=#hidden
%preproduction.mp.jwt.verify.issuer=#hidden
%production.mp.jwt.verify.issuer=#hidden
## Public Key
%dev.mp.jwt.verify.publickey.location=https://com.company/JWKS
%preproduction.mp.jwt.verify.publickey.location=https://com.company/JWKS
%production.mp.jwt.verify.publickey.location=https://com.company/JWKS

Basically the same as in the tutorial, but adapted to a real-life use-case I'm not able to share.

Environment (please complete the following information):

@aesteve aesteve added the kind/bug Something isn't working label Aug 13, 2019
@sberyozkin
Copy link
Member

@aesteve Hi, not sure yet if the JNI issue is only the first one, please see this guide, but can you try to enable JNI and see how it goes ?

@gsmet
Copy link
Member

gsmet commented Aug 13, 2019

In fact, it's the whole quarkus.ssl.native = true that should be added to your application.properties.

@sberyozkin I think the SmallRye JWT extension should produce a ExtensionSslNativeSupportBuildItem as in real life it will always point to an HTTPS resource.

@sberyozkin
Copy link
Member

sberyozkin commented Aug 13, 2019

Hi @gsmet thanks for the advice, yes if mp.jwt.verify.publickey.location points to a remote JWK set then it will always be HTTPS. But one thing I'm not sure about is, mp.jwt.verify.publickey.location can also point to the collocated public keys/ or JWK sets, in which case the native SSL won't be needed.
So may be quarkus.ssl.native = true is the way to go given that mp.jwt.verify.publickey.location does not always point to the remote HTTPS endpoint ? In this case I can create a PR to the smallrye-jwt docs to recommend setting this property for HTTPS locations to resolve in the native mode

@gsmet
Copy link
Member

gsmet commented Aug 13, 2019

@sberyozkin is it a runtime config or build time config?

@gsmet
Copy link
Member

gsmet commented Aug 13, 2019

Well, in any case, it should be runtime to be easily overridable. So maybe we should leave it at that.

@sberyozkin could you add a note in the documentation just after the config property table?

@sberyozkin
Copy link
Member

@gsmet will do and post to this issue once I'm done, finalizing the other PR right now, thanks

@aesteve
Copy link
Author

aesteve commented Aug 13, 2019

Hi and thanks for your answers.

So if I understand correctly, since the endpoint where JWKS is located is using https, thus JNI, which is not configured in my application.

I'll try to add it and see how it goes.

@gsmet
Copy link
Member

gsmet commented Aug 13, 2019

To be clearer: you need SSL support as you are connecting to a https URL, it's not enabled by default as it makes the native images significantly larger.

You need to define the following property in your application.properties:

quarkus.ssl.native=true

It will enable JNI and more.

Note that if you want to create a Docker container supporting SSL, you should refer to https://quarkus.io/guides/native-and-ssl-guide for more details.

@aesteve
Copy link
Author

aesteve commented Aug 13, 2019

Thanks for the link, so I have other things to add in addition to the properties option. I'm gonna try.

A small note in addition, I have been surprised to face that issue when a request hits the server and not at startup.

It makes sense if the JWT check is lazy, and therefore JWKS are not read when application starts. Why not, but I'd like to be sure that those JWKS are stored in a cache and not accessed every time a request header needs to be checked. I didn't find the source on smallrye-jwt could someone point me at this ?

@gsmet
Copy link
Member

gsmet commented Aug 13, 2019

The sources are here: https://github.com/smallrye/smallrye-jwt/ .

Not familiar with this project so I can't help you on that.

@aesteve
Copy link
Author

aesteve commented Aug 13, 2019

quarkus.ssl.native=true + adding the configuration mentioned here fixes the issue perfectly.

Sorry for the report :\

As for the caching thing, I may ask for help on the repo you linked right above this comment.

Thanks a lot.

@aesteve aesteve closed this as completed Aug 13, 2019
@gsmet
Copy link
Member

gsmet commented Aug 13, 2019

@aesteve we definitely need a pointer in the JWT doc though so we will add one.

@gsmet gsmet added kind/question Further information is requested and removed kind/bug Something isn't working labels Aug 13, 2019
@sberyozkin
Copy link
Member

sberyozkin commented Aug 13, 2019

@aesteve ping me please if you have any questions re smallrye-jwt. The key resolution is typically done once, inside Jose4J key location resolver. HTTPS located keys are managed by Jose4J HTTPS cache. I've provided the details at smallrye/smallrye-jwt#118, cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants