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

druid extension for OpenID Connect auth using pac4j lib #8992

Merged
merged 55 commits into from
Mar 24, 2020

Conversation

himanshug
Copy link
Contributor

@himanshug himanshug commented Dec 5, 2019

Fixes #8984

Description

Druid security extension to support authentication based on OpenID Connect spec. Please see added file druid-pac4j.md for further information.

This patch...

  1. adds a new security extension, druid-pac4j, with Authenticator implementation using pac4j library
  2. removes /unified-console.html from default unsecured paths , and introduces UnautorizedResourceFilter similar to existing UnsecuredResourceFilter to avoid "authentication done" check made by PreResponseAuthorizationCheckFilter . This is done so that redirect to authentication server (e.g. Okta) is correctly handled by browser when user visits the web console.

I have tested the it using Okta dev server . Also, a version of this code is running on our internal Druid clusters in prod with enterprise Okta auth server.


This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • added or updated version, license, or notice information in licenses.yaml
  • been tested in a test Druid cluster.

@himanshug
Copy link
Contributor Author

@soumyajose0784 that sounds like that the ssl cert returned from server configured in druid.auth.pac4j.oidc.discoveryURI has either expired or is "self signed" or something else is wrong with it.
if you visited that url on browser, does that successfully validate the cert ?

@soumyajose0784
Copy link

yes, I am able to visit the the url in browser successfully. I have a trust-store created as well in linux machine with same certificate. But how do we specify the truststore parameter in common.runtime.properties. For eg, for ldap auth, we used to give the truststore path for ldap server as follows
druid.auth.basic.ssl.trustStorePath=/etc/security/truststore_ldap.jks
druid.auth.basic.ssl.trustStorePassword=
druid.auth.basic.ssl.protocol=TLS

@himanshug
Copy link
Contributor Author

@soumyajose0784 as a workaround, can you try adding following properties in jvm args of Druid process ...

-Djavax.net.ssl.trustStrore=/etc/security/truststore_ldap.jks -Djavax.net.ssl.trustStorePassword=password

and see if that works

@averma111
Copy link

Thank you @himanshug that should be helpful we will try these setting and let you know. Do we need to pass the above java param to all the run time properties file or any specific node.

@himanshug
Copy link
Contributor Author

himanshug commented Apr 6, 2020

Do we need to pass the above java param to all the run time properties file or any specific node.

they wouldn't go in runtime.properties file but must be added as jvm args to druid process , they should be added to only router node presuming that is one web console which admins would visit.

@himanshug
Copy link
Contributor Author

@soumyajose0784 @averma111 also please add this commit ( #9627 ) to your 0.18.0 custom build.

@averma111
Copy link

Thanks @himanshug for all the help and support.We will rebuild 0.18 again, I have also raised on more issue #9628.
See if you can comment something on it.

@himanshug
Copy link
Contributor Author

@averma111 can you remove the kerberos authenticator and let us try to make this auth working first , then we could add kerberos back in so that you have one set of problems at a time.

@averma111
Copy link

@himanshug I am working on two druid instance simultaneously/separately , I have a tight deadline and need to provide solution for SSO by either way. hence exploring all the possible options.

@himanshug
Copy link
Contributor Author

@averma111 @soumyajose0784 can you add the commit from https://github.com/apache/druid/pull/9637/files in your build then you should be able to specify custom ssl trustStore via following configuration in runtime.properties file...

druid.extensions.loadList=["druid-pac4j","simple-client-sslcontext",...]
druid.auth.authenticatorChain=["pac4j"]
druid.auth.pac4j.cookiePassphrase=whatever
druid.auth.pac4j.enableCustomSslContext=true

druid.auth.pac4j.oidc.clientID=asdafwqerteg
druid.auth.pac4j.oidc.clientSecret=asadfewgtewr
druid.auth.pac4j.oidc.discoveryURI=https://dev-960309.okta.com/oauth2/default/.well-known/openid-configuration

druid.client.https.protocol=TLS
druid.client.https.trustStorePassword=p@ssword
druid.client.https.trustStorePath=/etc/security/truststore_ldap.jks

@averma111
Copy link

Sure, we will rebuild again and follow the instructions.

@averma111
Copy link

@himanshug do we have any redirect url setting for Druid SSO, they wanted to know if we have any.They need for authorization code flow.

@himanshug
Copy link
Contributor Author

yes in the auth server side you would need to add something like https://druid_router_url:port/druid-ext/druid-pac4j/callback as redirect uri in the auth server client app configuration. this is the url where auth server redirects browser to, after successful login.

@averma111
Copy link

@himanshug my team is asking what we want to return such as user id, email address, roles, etc. what should we provide ?

@himanshug
Copy link
Contributor Author

pac4j asks for scope=openid+profile+email so I guess that would mean all of above.

@averma111
Copy link

@himanshug thank you

@soumyajose0784
Copy link

I could bring up Druid services after making the changes highlighted above and its able to hit the ping federate url, but now its giving SSL error accessing the callback url https://:/druid-ext/druid-pac4j/callback. Could you please advise how can this be fixed?Our Druid endpoints are ssl enabled.

Properties set are as follows
druid.extensions.loadList=[...,"druid-pac4j","simple-client-sslcontext"]
#TLS
druid.enablePlaintextPort=false
druid.enableTlsPort=true
druid.server.https.keyStorePath=/etc/security/keystore.jks
druid.server.https.keyStoreType=JKS
druid.server.https.certAlias=
druid.server.https.keyStorePassword=
druid.server.https.requireClientCertificate=false
druid.server.https.requestClientCertificate=false
druid.client.https.trustStorePath=/etc/security/truststore_sso.jks #this truststore contains valid certs for both Ping federate Server and our own Druid servers
druid.client.https.trustStoreType=JKS
druid.client.https.trustStorePassword=
druid.client.https.protocol=TLS
#Pac4j
druid.auth.authenticatorChain=["pac4j"]
druid.auth.authenticator.pac4j.type=pac4j
druid.auth.pac4j.cookiePassphrase=
druid.auth.pac4j.enableCustomSslContext=true
druid.auth.pac4j.oidc.clientID=
druid.auth.pac4j.oidc.clientSecret=
druid.auth.pac4j.oidc.discoveryURI=https://<pingfederate_url>/.well-known/openid-configuration

Error details


HTTP ERROR 500
Problem accessing /druid-ext/druid-pac4j/callback. Reason: Server Error
Caused by:
org.pac4j.core.exception.TechnicalException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at org.pac4j.oidc.credentials.authenticator.OidcAuthenticator.validate(OidcAuthenticator.java:159)
at org.pac4j.oidc.credentials.authenticator.OidcAuthenticator.validate(OidcAuthenticator.java:35)
at org.pac4j.core.client.BaseClient.retrieveCredentials(BaseClient.java:71)
at org.pac4j.core.client.IndirectClient.getCredentials(IndirectClient.java:140)
at org.pac4j.core.engine.DefaultCallbackLogic.perform(DefaultCallbackLogic.java:89)
at org.apache.druid.security.pac4j.Pac4jFilter.doFilter(Pac4jFilter.java:94)
at org.apache.druid.server.security.AuthenticationWrappingFilter.doFilter(AuthenticationWrappingFilter.java:59)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
at org.apache.druid.server.security.SecuritySanityCheckFilter.doFilter(SecuritySanityCheckFilter.java:86)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1642)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:533)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1595)
at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:255)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1340)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:203)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:473)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1564)
at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:201)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1242)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:144)
at org.eclipse.jetty.server.handler.gzip.GzipHandler.handle(GzipHandler.java:740)
at org.eclipse.jetty.server.handler.HandlerList.handle(HandlerList.java:61)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:132)
at org.eclipse.jetty.server.Server.handle(Server.java:503)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:364)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:260)
at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:305)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:411)
at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:305)
at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:159)
at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:103)
at org.eclipse.jetty.io.ChannelEndPoint$2.run(ChannelEndPoint.java:118)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:333)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:310)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:168)
at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:126)
at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:366)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:765)
at org.eclipse.jetty.util.thread.QueuedThreadPool$2.run(QueuedThreadPool.java:683)
at java.lang.Thread.run(Thread.java:748)
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1946)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:316)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:310)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1639)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:223)
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.getOutputStream0(HttpURLConnection.java:1340)
at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1315)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.getOutputStream(HttpsURLConnectionImpl.java:264)
at com.nimbusds.oauth2.sdk.http.HTTPRequest.toHttpURLConnection(HTTPRequest.java:814)
at com.nimbusds.oauth2.sdk.http.HTTPRequest.send(HTTPRequest.java:882)
at org.pac4j.oidc.credentials.authenticator.OidcAuthenticator.validate(OidcAuthenticator.java:141)
... 41 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:450)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:317)
at sun.security.validator.Validator.validate(Validator.java:262)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:330)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:237)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:132)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
... 56 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:445)
... 62 more

@himanshug
Copy link
Contributor Author

@soumyajose0784 @averma111 thanks for trying. sorry to hear that, I am looking into it. unfortunately, I have access to 2 okta servers and both have standard ssl certs so custom truststore handling is being a bit difficult for me to verify fully. I think the problem is because pac4j lib is not using single mechanism to make HTTP requests as older issue reported in #8992 (comment) is not the problem anymore.

I will make some change soon and would ask you to test it. I will get that PR merged only after your confirmation of everything working.

@averma111
Copy link

@himanshug thank you for the taking this activity. Let us know when we can rebuild package again and test it.

@himanshug
Copy link
Contributor Author

@averma111 @soumyajose0784 can you please get the commit from #9695 to your build and try ?

@soumyajose0784
Copy link

@himanshug , we have built the commit from https://github.com/himanshug/druid/tree/pac4j_ssl,
and now able to open unified console without SSL error. But the console gives redirect error for coordinator urls, so datasources/supervisor/tasks tabs are not available from unified console. Any thought?
Error from router.log


org.apache.druid.server.router.CoordinatorRuleManager - Exception while polling for rules
org.apache.druid.java.util.common.ISE: Error while polling rules, status[302 Found] content[]
Error from DataSources tab:


Unknown exception / Unexpected response status [302] description [Found] from request url[https://:/druid/coordinator/v1/metadata/segments?includeOvershadowedStatus] / org.apache.druid.java.util.common.RE / on host

@himanshug
Copy link
Contributor Author

@soumyajose0784 did you enable pac4j auth on non-router nodes as well? One option would be to not have pac4j auth on non-router nodes. However if you added pac4j on those nodes to be able to have SSO on the web console at coordinator/overlord, then following information is relevant.

Router periodically talks to various other Druid nodes to gather some data that it displays. However, Router needs to be able to authenticate with those nodes to be able to talk to them successfully. That particular auth mechanism is enabled via configuring "Escalator", you can read about generic Druid auth stuff in https://druid.apache.org/docs/latest/design/auth.html#escalator .
Pac4j extension doesn't support programmatic clients, so Router doesn' t have an escalator to talk to other Druid nodes when they only allow pac4j authenticated users.
For that reason, in druid.auth.authenticatorChain=[..., "pac4j"] you should add at least one authenticator that supports programmatic clients e.g. ldap or kerberos or https://druid.apache.org/docs/latest/development/extensions-core/druid-basic-security.html and configure druid.escalator.xx properties at all nodes to be able to authenticate with that authenticator.

@soumyajose0784
Copy link

@himanshug , thank you for the great support. We are able to bring up the services (pac4j+basic auth enabled), and router console is now accessible with proper connection to other nodes

@himanshug
Copy link
Contributor Author

glad that it worked out, thanks for testing.

@shashisingh
Copy link

@himanshug, I want to setup router node with both basic authentication (for REST API access through python) and pac4j (browser access) and non-router nodes with basic authentication. Could you help me with the sample runtime.properties to achieve this kind of setup? I have tried what you had suggested (enable pac4j for router console and disable authentication at non-router nodes) and that setup works fine for browser based access. But I want to expose router and other nodes REST APIs for some automations. Thanks

@himanshug
Copy link
Contributor Author

himanshug commented Sep 11, 2020

@shashisingh you can have something like below and that should work...

druid.extensions.loadList=[...,"druid-pac4j","druid-basic-security",...]

druid.auth.authenticatorChain=["MyBasicMetadataAuthenticator", "pac4j"]

druid.auth.pac4j.* properties... that you already have and working

druid.auth.authenticator.pac4j.authorizerName=MyBasicMetadataAuthorizer
druid.auth.authenticator.MyBasicMetadataAuthenticator.type=basic
druid.auth.authenticator.MyBasicMetadataAuthenticator.authorizerName=allowAll

druid.auth.authorizers=["MyBasicMetadataAuthorizer", "allowAll"]
druid.auth.authorizer.allowAll.type=allowAll
druid.auth.authorizer.MyBasicMetadataAuthorizer.type=basic
druid.auth.authorizer.MyBasicMetadataAuthorizer.enableCacheNotifications=true
druid.auth.authorizer.MyBasicMetadataAuthorizer.roleProvider.type=context
druid.auth.authorizer.MyBasicMetadataAuthorizer.initialAdminRole=admin

druid.escalator.type=basic
druid.escalator.internalClientUsername=druid_system
druid.escalator.internalClientPassword=desired_password_xx
druid.escalator.authorizerName=allowAll
druid.auth.authenticator.MyBasicMetadataAuthenticator.initialInternalClientPassword=desired_password_xx

also please read the docs in https://druid.apache.org/docs/latest/development/extensions-core/druid-basic-security.html

@bitsofdave
Copy link

@himanshug For configuring authorizations using the MyBasicMetadataAuthorizer in your example above, do you have to use the sub attribute to identify a user?

For example, my OIDC provider returns something like the following profile:

profile: #OidcProfile# | id: <id> | attributes: {..., sub=<sub>, ..., preferred_username=<email_address>, ..., given_name=<name>, ..., name=<name>, ..., email=<email>, ...} | roles: [] | permissions: [] | isRemembered: false 

I can create a druid basic user with name <sub> and assign a druid basic security role to it. This seems to work fine.

What I would like to do is create the druid basic user using another attribute such as <email> and assign a druid basic security role to it. When I've tried this, the user I'm logged in as doesn't get any permissions since I'm being identified by <sub>. I'm not sure how to change it to use another field.

Another option, even preferred, is to use roles in the response and assign permissions to them, similar to LDAP group mappings. Is this possible with this extension?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Proposal] Authenticator impl for OpenID Connect OAuth2.0 protocol
7 participants