diff --git a/docs/development/extensions-core/druid-pac4j.md b/docs/development/extensions-core/druid-pac4j.md index 57ec4c9c8ad7..e8fdcf1e56c2 100644 --- a/docs/development/extensions-core/druid-pac4j.md +++ b/docs/development/extensions-core/druid-pac4j.md @@ -38,8 +38,9 @@ druid.auth.authenticator.pac4j.type=pac4j ### Properties |Property|Description|Default|required| |--------|---------------|-----------|-------|--------| +|`druid.auth.pac4j.cookiePassphrase`|passphrase for encrypting the cookies used to manage authentication session with browser. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes| +|`druid.auth.pac4j.readTimeout`|Socket connect and read timeout duration used when communicating with authentication server|PT5S|No| +|`druid.auth.pac4j.enableCustomSslContext`|Whether to use custom SSLContext setup via [simple-client-sslcontext](simple-client-sslcontext.md) extension which must be added to extensions list when this property is set to true.|false|No| |`druid.auth.pac4j.oidc.clientID`|OAuth Client Application id.|none|Yes| |`druid.auth.pac4j.oidc.clientSecret`|OAuth Client Application secret. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes| |`druid.auth.pac4j.oidc.discoveryURI`|discovery URI for fetching OP metadata [see this](http://openid.net/specs/openid-connect-discovery-1_0.html).|none|Yes| -|`druid.auth.pac4j.oidc.cookiePassphrase`|passphrase for encrypting the cookies used to manage authentication session with browser. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes| - diff --git a/extensions-core/druid-pac4j/pom.xml b/extensions-core/druid-pac4j/pom.xml index 4a2e16d1fef1..c5a25ebf71d8 100644 --- a/extensions-core/druid-pac4j/pom.xml +++ b/extensions-core/druid-pac4j/pom.xml @@ -44,11 +44,27 @@ ${project.parent.version} provided + + joda-time + joda-time + provided + + + org.apache.druid + druid-processing + ${project.parent.version} + provided + org.pac4j pac4j-oidc ${pac4j.version} + + com.nimbusds + nimbus-jose-jwt + 7.9 + com.google.code.findbugs diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java new file mode 100644 index 000000000000..e3ba9eeda34b --- /dev/null +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.druid.security.pac4j; + + +import com.google.common.primitives.Ints; +import com.nimbusds.jose.util.DefaultResourceRetriever; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + + +/** + * This class exists only to enable use of custom SSLSocketFactory on top of builtin class. This could be removed + * when same functionality has been added to original class com.nimbusds.jose.util.DefaultResourceRetriever. + */ +public class CustomSSLResourceRetriever extends DefaultResourceRetriever +{ + private SSLSocketFactory sslSocketFactory; + + public CustomSSLResourceRetriever(long readTimeout, SSLSocketFactory sslSocketFactory) + { + // super(..) has to be the very first statement in constructor. + super(Ints.checkedCast(readTimeout), Ints.checkedCast(readTimeout)); + + this.sslSocketFactory = sslSocketFactory; + } + + @Override + protected HttpURLConnection openConnection(final URL url) throws IOException + { + HttpURLConnection con = super.openConnection(url); + + if (sslSocketFactory != null && con instanceof HttpsURLConnection) { + ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); + } + + return con; + } +} diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java index 72ef4657653e..1ddba0b7ebf9 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java @@ -35,21 +35,16 @@ public class OIDCConfig @JsonProperty private final String discoveryURI; - @JsonProperty - private final PasswordProvider cookiePassphrase; - @JsonCreator public OIDCConfig( @JsonProperty("clientID") String clientID, @JsonProperty("clientSecret") PasswordProvider clientSecret, - @JsonProperty("discoveryURI") String discoveryURI, - @JsonProperty("cookiePassphrase") PasswordProvider cookiePassphrase + @JsonProperty("discoveryURI") String discoveryURI ) { this.clientID = Preconditions.checkNotNull(clientID, "null clientID"); this.clientSecret = Preconditions.checkNotNull(clientSecret, "null clientSecret"); this.discoveryURI = Preconditions.checkNotNull(discoveryURI, "null discoveryURI"); - this.cookiePassphrase = Preconditions.checkNotNull(cookiePassphrase, "null cookiePassphrase"); } @JsonProperty @@ -69,10 +64,4 @@ public String getDiscoveryURI() { return discoveryURI; } - - @JsonProperty - public PasswordProvider getCookiePassphrase() - { - return cookiePassphrase; - } } diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java index a22de73a6888..c0473ce42542 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonTypeName; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import com.google.inject.Provider; import org.apache.druid.server.security.AuthenticationResult; import org.apache.druid.server.security.Authenticator; import org.pac4j.core.config.Config; @@ -34,6 +35,8 @@ import org.pac4j.oidc.config.OidcConfiguration; import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; import javax.servlet.DispatcherType; import javax.servlet.Filter; import java.util.EnumSet; @@ -45,18 +48,28 @@ public class Pac4jAuthenticator implements Authenticator private final String name; private final String authorizerName; private final Supplier pac4jConfigSupplier; - private final OIDCConfig oidcConfig; + private final Pac4jCommonConfig pac4jCommonConfig; + private final SSLSocketFactory sslSocketFactory; @JsonCreator public Pac4jAuthenticator( @JsonProperty("name") String name, @JsonProperty("authorizerName") String authorizerName, - @JacksonInject OIDCConfig oidcConfig + @JacksonInject Pac4jCommonConfig pac4jCommonConfig, + @JacksonInject OIDCConfig oidcConfig, + @JacksonInject Provider sslContextSupplier ) { this.name = name; this.authorizerName = authorizerName; - this.oidcConfig = oidcConfig; + this.pac4jCommonConfig = pac4jCommonConfig; + + if (pac4jCommonConfig.isEnableCustomSslContext()) { + this.sslSocketFactory = sslContextSupplier.get().getSocketFactory(); + } else { + this.sslSocketFactory = null; + } + this.pac4jConfigSupplier = Suppliers.memoize(() -> createPac4jConfig(oidcConfig)); } @@ -67,7 +80,7 @@ public Filter getFilter() name, authorizerName, pac4jConfigSupplier.get(), - oidcConfig.getCookiePassphrase().getPassword() + pac4jCommonConfig.getCookiePassphrase().getPassword() ); } @@ -117,6 +130,9 @@ private Config createPac4jConfig(OIDCConfig oidcConfig) oidcConf.setDiscoveryURI(oidcConfig.getDiscoveryURI()); oidcConf.setExpireSessionWithToken(true); oidcConf.setUseNonce(true); + oidcConf.setResourceRetriever( + new CustomSSLResourceRetriever(pac4jCommonConfig.getReadTimeout().getMillis(), sslSocketFactory) + ); OidcClient oidcClient = new OidcClient(oidcConf); oidcClient.setUrlResolver(new DefaultUrlResolver(true)); diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java new file mode 100644 index 000000000000..0a57869fb57e --- /dev/null +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.druid.security.pac4j; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import org.apache.druid.metadata.PasswordProvider; +import org.joda.time.Duration; + +public class Pac4jCommonConfig +{ + @JsonProperty + private final boolean enableCustomSslContext; + + @JsonProperty + private final PasswordProvider cookiePassphrase; + + @JsonProperty + private final Duration readTimeout; + + @JsonCreator + public Pac4jCommonConfig( + @JsonProperty("enableCustomSslContext") boolean enableCustomSslContext, + @JsonProperty("cookiePassphrase") PasswordProvider cookiePassphrase, + @JsonProperty("readTimeout") Duration readTimeout + ) + { + this.enableCustomSslContext = enableCustomSslContext; + this.cookiePassphrase = Preconditions.checkNotNull(cookiePassphrase, "null cookiePassphrase"); + this.readTimeout = readTimeout == null ? Duration.millis(5000) : readTimeout; + } + + @JsonProperty + public boolean isEnableCustomSslContext() + { + return enableCustomSslContext; + } + + @JsonProperty + public PasswordProvider getCookiePassphrase() + { + return cookiePassphrase; + } + + @JsonProperty + public Duration getReadTimeout() + { + return readTimeout; + } +} diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java index af9b40283bab..5ca0b93fdcc4 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java @@ -44,6 +44,7 @@ public List getJacksonModules() @Override public void configure(Binder binder) { + JsonConfigProvider.bind(binder, "druid.auth.pac4j", Pac4jCommonConfig.class); JsonConfigProvider.bind(binder, "druid.auth.pac4j.oidc", OIDCConfig.class); Jerseys.addResource(binder, Pac4jCallbackResource.class); diff --git a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java new file mode 100644 index 000000000000..409eb6c116d0 --- /dev/null +++ b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.druid.security.pac4j; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +public class OIDCConfigTest +{ + @Test + public void testSerde() throws Exception + { + ObjectMapper jsonMapper = new ObjectMapper(); + + String jsonStr = "{\n" + + " \"clientID\": \"testid\",\n" + + " \"clientSecret\": \"testsecret\",\n" + + " \"discoveryURI\": \"testdiscoveryuri\"\n" + + "}\n"; + + OIDCConfig conf = jsonMapper.readValue( + jsonMapper.writeValueAsString(jsonMapper.readValue(jsonStr, OIDCConfig.class)), + OIDCConfig.class + ); + + Assert.assertEquals("testid", conf.getClientID()); + Assert.assertEquals("testsecret", conf.getClientSecret().getPassword()); + Assert.assertEquals("testdiscoveryuri", conf.getDiscoveryURI()); + } +} diff --git a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java new file mode 100644 index 000000000000..00c77bac98c3 --- /dev/null +++ b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.druid.security.pac4j; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.druid.jackson.DefaultObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +public class Pac4jCommonConfigTest +{ + @Test + public void testSerde() throws Exception + { + ObjectMapper jsonMapper = new DefaultObjectMapper(); + + String jsonStr = "{\n" + + " \"cookiePassphrase\": \"testpass\",\n" + + " \"readTimeout\": \"PT10S\",\n" + + " \"enableCustomSslContext\": true\n" + + "}\n"; + + Pac4jCommonConfig conf = jsonMapper.readValue( + jsonMapper.writeValueAsString(jsonMapper.readValue(jsonStr, Pac4jCommonConfig.class)), + Pac4jCommonConfig.class + ); + + Assert.assertEquals("testpass", conf.getCookiePassphrase().getPassword()); + Assert.assertEquals(10_000L, conf.getReadTimeout().getMillis()); + Assert.assertTrue(conf.isEnableCustomSslContext()); + } +} diff --git a/website/.spelling b/website/.spelling index b3f1deae329f..70dbd958c85a 100644 --- a/website/.spelling +++ b/website/.spelling @@ -345,6 +345,7 @@ runtime schemas searchable servlet +simple-client-sslcontext sharded sharding skipHeaderRows @@ -829,7 +830,6 @@ jvm-global kafka-emitter org.apache.druid.extensions.contrib. pull-deps -simple-client-sslcontext sqlserver-metadata-storage statsd-emitter - ../docs/development/geo.md