quarkusio / quarkus

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

MSSQL JDBC driver does not support integration authentication in native image #28913

Open Ntung029 opened 2 years ago

Ntung029 commented 2 years ago

Description

I have an issue when connecting the service to SQL DB from a native image. Because of security requirement, I need to use integrated security by specifying these additional jdbc properties bellow:

quarkus.datasource.jdbc.additional-jdbc-properties.integratedsecurity=true
quarkus.datasource.jdbc.additional-jdbc-properties.trustservercertificate=true
quarkus.datasource.jdbc.additional-jdbc-properties.encrypt=true

Then I start the application in the quarkus-distroless-image:1.0. The application cannot find the connection to SQL DB and the error message shows as bellow. I have verified the connection setting using SQL credentials which can connect to SQL DB. Could anyone advise the setting or driver that can support integrated authentication to SQL DB.


2022-10-29 00:46:52,795 INFO  [io.qua.sma.ope.run.OpenApiRecorder] (main) Default CORS properties will be used, please use 'quarkus.http.cors' properties instead
2022-10-29 00:46:57,799 WARN  [org.hib.eng.jdb.env.int.JdbcEnvironmentInitiator] (JPA Startup Thread: <default>) HHH000342: Could not obtain connection to query metadata: java.sql.SQLException: Acquisition timeout while waiting for new connection
        at io.agroal.pool.ConnectionPool.handlerFromSharedCache(ConnectionPool.java:320)
        at io.agroal.pool.ConnectionPool.getConnection(ConnectionPool.java:248)
        at io.agroal.pool.DataSource.getConnection(DataSource.java:86)
        at io.quarkus.hibernate.orm.runtime.customized.QuarkusConnectionProvider.getConnection(QuarkusConnectionProvider.java:23)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:181)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:68)
        at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35)
        at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:101)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
        at org.hibernate.engine.jdbc.internal.JdbcServicesImpl.configure(JdbcServicesImpl.java:51)
        at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:107)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:246)
        at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214)
        at org.hibernate.boot.internal.SessionFactoryOptionsBuilder.<init>(SessionFactoryOptionsBuilder.java:272)
        at io.quarkus.hibernate.orm.runtime.recording.PrevalidatedQuarkusMetadata.buildSessionFactoryOptionsBuilder(PrevalidatedQuarkusMetadata.java:68)
        at io.quarkus.hibernate.orm.runtime.boot.FastBootEntityManagerFactoryBuilder.build(FastBootEntityManagerFactoryBuilder.java:72)
        at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:71)
        at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
        at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
        at io.quarkus.hibernate.orm.runtime.JPAConfig$LazyPersistenceUnit.get(JPAConfig.java:138)
        at io.quarkus.hibernate.orm.runtime.JPAConfig$1.run(JPAConfig.java:54)
        at java.lang.Thread.run(Thread.java:833)
        at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:704)
        at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:202)
Caused by: java.util.concurrent.TimeoutException
        at java.util.concurrent.FutureTask.get(FutureTask.java:204)
        at io.agroal.pool.ConnectionPool.handlerFromSharedCache(ConnectionPool.java:296)
        ... 25 more

2022-10-29 00:46:57,859 INFO  [io.quarkus] (main) apaas-unpersist-service 1.0-SNAPSHOT native (powered by Quarkus 2.11.2.Final) started in 5.102s. Listening on: http://0.0.0.0:8080
2022-10-29 00:46:57,859 INFO  [io.quarkus] (main) Profile prod activated. 
2022-10-29 00:46:57,859 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, hibernate-orm, hibernate-orm-panache, hibernate-validator, jdbc-mssql, narayana-jta, opentelemetry, opentelemetry-otlp-exporter, resteasy-reactive, resteasy-reactive-jackson, smallrye-context-propagation, smallrye-health, smallrye-openapi, swagger-ui, vertx]
2022-10-29 00:47:06,808 WARN  [io.agr.pool] (agroal-11) Datasource '<default>': This driver is not configured for integrated authentication. ClientConnectionId:9ccec68f-303f-41ed-854e-db243170bf23

Implementation ideas

No response

quarkus-bot[bot] commented 2 years ago

/cc @DavideD, @Sanne, @barreiro, @gavinking, @gsmet, @mswatosh

yrodiere commented 2 years ago

2022-10-29 00:47:06,808 WARN [io.agr.pool] (agroal-11) Datasource '': This driver is not configured for integrated authentication. ClientConnectionId:9ccec68f-303f-41ed-854e-db243170bf23

Hey @barreiro , this looks like an unsupported feature, maybe an incompatibility? Can you shed some light? I personally have no idea what this feature even is...

gsmet commented 2 years ago

My guess is that we probably need to register something for reflection in the driver (maybe). But now what...

Sanne commented 2 years ago

The MS SQL JDBC driver supports a whole host of non-standard authentication methods; among these there's integration with Kerberos, possibly needing to load native windows DDL libraries getting loaded, or integrate with the Azure cloud SDK dependencies (dozens of them).

It might well be that with some luck you just need a couple additional reflections for this particular user, but be aware of the potential pandora box.

@Ntung029 we can try helping for your specific case - could you elaborate on your security setup please? And ideally if we could reproduce it in some way, that might be essential to be able to help you.

Sanne commented 2 years ago

Also - is this actually running in native-image mode? Or is it failing in JVM mode too?

In case it's JVM, you might be missing some of the optional dependencies.

Ntung029 commented 2 years ago

Thanks everyone for your response.

The same error happens with JVM image in integrated authentication mode. The SQL server has sql encryption enforce sets to true and enables both SQL Server and Windows Authentication mode.

Do you have a document about properties in quarkus.datasource.jdbc.additional-jdbc-properties?

`2022-11-01 18:09:08,624 INFO [io.qua.sma.ope.run.OpenApiRecorder] (main) CORS filtering is disabled and cross-origin resource sharing is allowed without restriction, which is not recommended in production. Please configure the CORS filter through 'quarkus.http.cors.*' properties. For more information, see Quarkus HTTP CORS documentation demo-mssql-connection-fruit-service-1 | 2022-11-01 18:09:13,656 WARN [org.hib.eng.jdb.env.int.JdbcEnvironmentInitiator] (JPA Startup Thread: ) HHH000342: Could not obtain connection to query metadata: java.sql.SQLExceptio n: Acquisition timeout while waiting for new connection demo-mssql-connection-fruit-service-1

... 2022-11-01 18:09:13,923 INFO [io.quarkus] (main) demo-mssql-connection 1.0-SNAPSHOT on JVM (powered by Quarkus 2.13.3.Final) started in 6.333s. Listening on: http://0.0.0.0:8080 demo-mssql-connection-fruit-service-1

2022-11-01 18:09:37,840 WARN [io.agr.pool] (agroal-11) Datasource '': This driver is not configured for integrated authentication. ClientConnectionId:0bb46ead-45bf-4501-9428-2b472c2 4e2a0 ...

`

mswatosh commented 2 years ago

I believe this is how properties are specified with additional-jdbc-properties and these should be the two needed for Integrated Authentication with SQL Server on Windows:

quarkus.datasource.jdbc.additional-jdbc-properties.authenticationScheme=JavaKerberos
quarkus.datasource.jdbc.additional-jdbc-properties.integratedSecurity=true

This is assuming you're using the Krb5LoginModule, and not the .dll option, in which case see this document for the required properties: https://learn.microsoft.com/en-us/sql/connect/jdbc/using-kerberos-integrated-authentication-to-connect-to-sql-server?view=sql-server-ver16

Hopefully those get you past the "This driver is not configured for integrated authentication" error

Ntung029 commented 2 years ago

Thanks @mvwatosh for your suggestion. I have tried the authenticationScheme JavaKerberos and got this error in the native image.

demo-mssql-connection-fruit-service-1  | 2022-11-01 19:48:54,427 INFO  [io.qua.sma.ope.run.OpenApiRecorder] (main) CORS filtering is disabled and cross-origin resource sharing is allowed without restriction, which is not recommended
 in production. Please configure the CORS filter through 'quarkus.http.cors.*' properties. For more information, see Quarkus HTTP CORS documentation
demo-mssql-connection-fruit-service-1  | 2022-11-01 19:48:54,625 WARN  [io.agr.pool] (agroal-11) Datasource '<default>': Failed to create connection due to ExceptionInInitializerError
demo-mssql-connection-fruit-service-1  | 2022-11-01 19:48:54,629 ERROR [io.qua.run.Application] (main) Failed to start application (with profile prod): java.lang.ClassNotFoundException: sun.security.provider.ConfigFile
demo-mssql-connection-fruit-service-1  |        at java.lang.Class.forName(DynamicHub.java:1136)
demo-mssql-connection-fruit-service-1  |        at javax.security.auth.login.Configuration$2.run(Configuration.java:253)
demo-mssql-connection-fruit-service-1  |        at javax.security.auth.login.Configuration$2.run(Configuration.java:249)
demo-mssql-connection-fruit-service-1  |        at java.security.AccessController.executePrivileged(AccessController.java:145)
demo-mssql-connection-fruit-service-1  |        at java.security.AccessController.doPrivileged(AccessController.java:569)
demo-mssql-connection-fruit-service-1  |        at javax.security.auth.login.Configuration.getConfiguration(Configuration.java:248)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.KerbAuthentication.<clinit>(KerbAuthentication.java:47)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection.logon(SQLServerConnection.java:4862)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection$LogonCommand.doExecute(SQLServerConnection.java:4845)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7627)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:3912)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectHelper(SQLServerConnection.java:3358)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection.login(SQLServerConnection.java:2950)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection.connectInternal(SQLServerConnection.java:2790)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerConnection.connect(SQLServerConnection.java:1663)
demo-mssql-connection-fruit-service-1  |        at com.microsoft.sqlserver.jdbc.SQLServerDriver.connect(SQLServerDriver.java:1064)
demo-mssql-connection-fruit-service-1  |        at io.agroal.pool.ConnectionFactory.createConnection(ConnectionFactory.java:226)
demo-mssql-connection-fruit-service-1  |        at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:535)
demo-mssql-connection-fruit-service-1  |        at io.agroal.pool.ConnectionPool$CreateConnectionTask.call(ConnectionPool.java:516)
demo-mssql-connection-fruit-service-1  |        at java.util.concurrent.FutureTask.run(FutureTask.java:264)
demo-mssql-connection-fruit-service-1  |        at io.agroal.pool.util.PriorityScheduledExecutor.beforeExecute(PriorityScheduledExecutor.java:75)
demo-mssql-connection-fruit-service-1  |        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
demo-mssql-connection-fruit-service-1  |        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
demo-mssql-connection-fruit-service-1  |        at java.lang.Thread.run(Thread.java:833)
demo-mssql-connection-fruit-service-1  |        at com.oracle.svm.core.thread.PlatformThreads.threadStartRoutine(PlatformThreads.java:705)
demo-mssql-connection-fruit-service-1  |        at com.oracle.svm.core.posix.thread.PosixPlatformThreads.pthreadStartRoutine(PosixPlatformThreads.java:202)
demo-mssql-connection-fruit-service-1  | 
demo-mssql-connection-fruit-service-1 exited with code 1
mswatosh commented 2 years ago

That looks like it's getting farther than before, can you try it in JVM mode? sun.security.provider.ConfigFile is a JDK class, so missing that seems likely to be caused by the native compilation.

Ntung029 commented 1 year ago

Thanks @mswatosh for your input. I think the right way is to use JDBC with JavaKerberos authentication scheme but it needs to config the Kerberos setting for microservice and SQL service.

It turns out that the microservice does not have integrated authentication config. The current setup uses JTDS driver which can use the AD credential to log in to SQL server.

When I switch to the JTDS driver, the JVM image can login to SQL server using AD credentials. However, the quarkus-smallrye-health cannot get the data connection for the Readiness healthcheck.

apaas-apaas-service-1      |    at io.smallrye.mutiny.operators.uni.UniAndCombination.subscribe(UniAndCombination.java:54)
apaas-apaas-service-1      |    at io.smallrye.mutiny.operators.AbstractUni.subscribe(AbstractUni.java:36)
apaas-apaas-service-1      |    at io.smallrye.mutiny.operators.uni.UniBlockingAwait.await(UniBlockingAwait.java:60)
apaas-apaas-service-1      |    at io.smallrye.mutiny.groups.UniAwait.atMost(UniAwait.java:65)
apaas-apaas-service-1      |    at io.smallrye.health.SmallRyeHealthReporter.getHealth(SmallRyeHealthReporter.java:217)
apaas-apaas-service-1      |    at io.smallrye.health.SmallRyeHealthReporter_ClientProxy.getHealth(Unknown Source)
apaas-apaas-service-1      |    at io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler.getHealth(SmallRyeHealthHandler.java:11)
apaas-apaas-service-1      |    at io.quarkus.smallrye.health.runtime.SmallRyeHealthHandlerBase.doHandle(SmallRyeHealthHandlerBase.java:44)
apaas-apaas-service-1      |    at io.quarkus.smallrye.health.runtime.SmallRyeHealthHandlerBase.handle(SmallRyeHealthHandlerBase.java:31)
apaas-apaas-service-1      |    at io.quarkus.smallrye.health.runtime.SmallRyeHealthHandler.handle(SmallRyeHealthHandler.java:7)
apaas-apaas-service-1      |    at io.quarkus.smallrye.health.runtime.SmallRyeHealthHandlerBase.handle(SmallRyeHealthHandlerBase.java:19)
apaas-apaas-service-1      |    at io.vertx.ext.web.impl.BlockingHandlerDecorator.lambda$handle$0(BlockingHandlerDecorator.java:48)
apaas-apaas-service-1      |    at io.vertx.core.impl.ContextBase.lambda$null$0(ContextBase.java:137)
apaas-apaas-service-1      |    at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:264)
apaas-apaas-service-1      |    at io.vertx.core.impl.ContextBase.lambda$executeBlocking$1(ContextBase.java:135)
apaas-apaas-service-1      |    at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:555)
apaas-apaas-service-1      |    at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2449)
apaas-apaas-service-1      |    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1478)
apaas-apaas-service-1      |    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
apaas-apaas-service-1      |    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
apaas-apaas-service-1      |    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
apaas-apaas-service-1      |    at java.base/java.lang.Thread.run(Thread.java:829)
apaas-apaas-service-1      | 
apaas-apaas-service-1      | 2022-11-07 16:53:12,618 INFO  [io.sma.health] (executor-thread-0) SRHCK01001: Reporting health down status: {"status":"DOWN","checks":[{"name":"Service simple health check","status":"UP"},{"name":"io.qua
rkus.agroal.runtime.health.DataSourceHealthCheck_ClientProxy","status":"DOWN","data":{}}]}

Another issue is I cannot create the native image with JTDS driver because of the initiation of Inet4Address class in the JTDS driver.

`Caused by: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of java.net.Inet4Address are allowed in the image heap as this class should be initialized at image runtime. To see how this object got instantiated use --trace-object-instantiation=java.net.Inet4Address.
`
mswatosh commented 1 year ago

Using integratedSecurity=true and authenticationScheme=JavaKerberos should be able to pick up the AD credential off the system (it uses the default ccache on the system to look for credentials). Are you getting an error message when running in JVM mode with those properties and the SQLServer JDBC driver?

Ntung029 commented 1 year ago

It turns out that we don't have the integrated authentication config for microservice. It is working with JTDS driver which can log in to SQL server using AD credentials by providing both username and password.

JesperBerggren commented 9 months ago

Hey everybody,

Sorry to comment on such an old issue, but I concur. MSSQL JDBC driver does not support integration authentication in native image.

It works flawlessly in JVM mode but it doesn't seem to pick up the login config file I've provided in the system property: java.security.auth.login.config.

Of course I've also set the following system properties: -Dsun.security.krb5.debug=true -Dsun.security.spnego.debug=true but there's no debugging at all, so it would appear that the driver doesn't pick up on the fact that it's suppose to use Kerberos.

I guess it could be something with registering some classes for reflection, but I've not been able to figure out which ones. I've tried to register com.sun.security.auth.module.Krb5LoginModule but that didn't do anything.

Btw, to make it run (not build) I needed to set these parameters:

quarkus.native.additional-build-args =\
  -H:+UnlockExperimentalVMOptions,\
  -H:ReflectionConfigurationFiles=reflection-config.json,\
  -H:IncludeResourceBundles=sun.security.util.AuthResources,\
  -H:-UnlockExperimentalVMOptions

and in the reflection-config.json I have:

[
  {
    "name" : "sun.security.provider.ConfigFile",
    "allDeclaredConstructors" : true,
    "allPublicConstructors" : true,
    "allDeclaredMethods" : true,
    "allPublicMethods" : true,
    "allDeclaredFields" : true,
    "allPublicFields" : true
  }
]

I hope somebody can share some insights :-)

sebagdev commented 7 months ago

@JesperBerggren Have you managed to progress with the issue or have you workaround it somehow?

JesperBerggren commented 7 months ago

Unfortunately no. Our "workaround" is not to run in native mode.

sebagdev commented 5 months ago

Unfortunately no. Our "workaround" is not to run in native mode.

@JesperBerggren perhaps this will be of some use to you Okay, I have managed to overcome this. I am not sure how stable this is (yet to be checked) but.

  1. I've run the app on graalvm as a jar with agent and dumped the reflection-config
  2. I've copied away most of the sun.security configuration from the json.
  3. The final reflection config file looked like this: reflection-config.json
maxandersen commented 1 month ago

is this more/different auth than active directory as discussed in https://github.com/quarkusio/quarkus/issues/34580 ?

yrodiere commented 1 month ago

Right @maxandersen , it's possibly different from #34580. From Sanne's message above:

The MS SQL JDBC driver supports a whole host of non-standard authentication methods; among these there's integration with Kerberos, possibly needing to load native windows DDL libraries getting loaded, or integrate with the Azure cloud SDK dependencies (dozens of them).

sebagdev commented 1 month ago

@maxandersen yes, these are different auth methods. In this thread kerberos is discussed: https://learn.microsoft.com/en-us/sql/connect/jdbc/using-kerberos-integrated-authentication-to-connect-to-sql-server?view=sql-server-ver16 vs AD Auth in the other: https://learn.microsoft.com/en-us/sql/connect/jdbc/connecting-using-azure-active-directory-authentication?view=sql-server-ver16

gsmet commented 1 month ago

@sebagdev maybe we should work on having a configuration property to enable this automatically?

I'm not sure exactly which configuration triggers the authentication you are using but it seems like something we could integrate to Quarkus directly (and automatically register for reflection).

The only thing is that we need a build time property for it because reflection is handled at build time.

I think with a good name for the config property and your reflection config above, the patch is almost written.

sebagdev commented 1 month ago

@gsmet I think that's great idea. It would not be a hassle to use if this configuration property would be described in quarkus-jdbc-mssql or the guide.

The authentication is triggered by authenticationScheme of the jdbc connection string. My full config looks like this:

quarkus.datasource.jdbc.url = jdbc:sqlserver://${DB_HOST};databasename=${DB_NAME};integratedsecurity=true;trustServerCertificate=true;authenticationScheme=JavaKerberos;multiSubnetFailover=true;encrypt=true;userName=${DB_USERNAME};password=${DB_PASSWORD};sslProtocol=TLSv1.2
quarkus.hibernate-orm.database.generation=none
quarkus.native.resources.includes=*.properties,*.json
quarkus.native.additional-build-args =\
  -H:+UnlockExperimentalVMOptions, \
  -H:ReflectionConfigurationFiles=reflection-config.json, \
  -H:IncludeResourceBundles=sun.security.util.AuthResources, \
  -H:AdditionalSecurityProviders=sun.security.jgss.SunProvider, \
  -H:+TraceSecurityServices
quarkus.naming.enable-jndi=true

The issue for this is that my code most likely will only fix Kerberos, I am not sure about other authentication schemes (AD auth variants). Also keeping this up to date might be challenging - I had an issue to properly setup locally DB with Kerberos so I ended up on verifying this on dev environment.

But in the long run.. I think it might be worth it, as it can draw to quarkus more enterprise developers who are bound to corporate policies and are hesitant or simply cannot easily run JDBC MSSQL connections.