druid-pac4j: add ability to use custom ssl trust store while talking to auth server (#9637)

* druid-pac4j: add ability for custom ssl trust store for talking to auth
server

* fix nimbusds DefaultResourceRetriever name in comment
This commit is contained in:
Himanshu 2020-04-10 18:01:59 -07:00 committed by GitHub
parent 332ca19621
commit ca369e5768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 267 additions and 19 deletions

View File

@ -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|

View File

@ -44,11 +44,27 @@
<version>${project.parent.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.druid</groupId>
<artifactId>druid-processing</artifactId>
<version>${project.parent.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.pac4j</groupId>
<artifactId>pac4j-oidc</artifactId>
<version>${pac4j.version}</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>7.9</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>

View File

@ -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;
}
}

View File

@ -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 class OIDCConfig
{
return discoveryURI;
}
@JsonProperty
public PasswordProvider getCookiePassphrase()
{
return cookiePassphrase;
}
}

View File

@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
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.client.OidcClient;
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<Config> 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<SSLContext> 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 class Pac4jAuthenticator implements Authenticator
name,
authorizerName,
pac4jConfigSupplier.get(),
oidcConfig.getCookiePassphrase().getPassword()
pac4jCommonConfig.getCookiePassphrase().getPassword()
);
}
@ -117,6 +130,9 @@ public class Pac4jAuthenticator implements Authenticator
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));

View File

@ -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;
}
}

View File

@ -44,6 +44,7 @@ public class Pac4jDruidModule implements DruidModule
@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);

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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