mirror of https://github.com/apache/druid.git
druid extension for OpenID Connect auth using pac4j lib (#8992)
* druid pac4j security extension for OpenID Connect OAuth 2.0 authentication * update version in druid-pac4j pom * introducing unauthorized resource filter * authenticated but authorized /unified-webconsole.html * use httpReq.getRequestURI() for matching callback path * add documentation * minor doc addition * licesne file updates * make dependency analyze succeed * fix doc build * hopefully fixes doc build * hopefully fixes license check build * yet another try on fixing license build * revert unintentional changes to website folder * update version to 0.18.0-SNAPSHOT * check session and its expiry on each request * add crypto service * code for encrypting the cookie * update doc with cookiePassphrase * update license yaml * make sessionstore in Pac4jFilter private non static * make Pac4jFilter fields final * okta: use sha256 for hmac * remove incubating * add UTs for crypto util and session store impl * use standard charsets * add license header * remove unused file * add org.objenesis.objenesis to license.yaml * a bit of nit changes in CryptoService and embedding EncryptionResult for clarity * rename alg to cipherAlgName * take cipher alg name, mode and padding as input * add java doc for CryptoService and make it more understandable * another UT for CryptoService * cache pac4j Config * use generics clearly in Pac4jSessionStore * update cookiePassphrase doc to mention PasswordProvider * mark stuff Nullable where appropriate in Pac4jSessionStore * update doc to mention jdbc * add error log on reaching callback resource * javadoc for Pac4jCallbackResource * introduce NOOP_HTTP_ACTION_ADAPTER * add correct module name in license file * correct extensions folder name in licenses.yaml * replace druid-kubernetes-extensions to druid-pac4j * cache SecureRandom instance * rename UnauthorizedResourceFilter to AuthenticationOnlyResourceFilter
This commit is contained in:
parent
cdf4a26904
commit
5604ac7963
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* 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.crypto;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import org.apache.druid.java.util.common.StringUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.InvalidParameterSpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
|
||||
/**
|
||||
* Utility class for symmetric key encryption (i.e. same secret is used for encryption and decryption) of byte[]
|
||||
* using javax.crypto package.
|
||||
*
|
||||
* To learn about possible algorithms supported and their names,
|
||||
* See https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
|
||||
*/
|
||||
public class CryptoService
|
||||
{
|
||||
// Based on Javadocs on SecureRandom, It is threadsafe as well.
|
||||
private static final SecureRandom SECURE_RANDOM_INSTANCE = new SecureRandom();
|
||||
|
||||
// User provided secret phrase used for encrypting data
|
||||
private final char[] passPhrase;
|
||||
|
||||
// Variables for algorithm used to generate a SecretKey based on user provided passPhrase
|
||||
private final String secretKeyFactoryAlg;
|
||||
private final int saltSize;
|
||||
private final int iterationCount;
|
||||
private final int keyLength;
|
||||
|
||||
// Cipher algorithm information
|
||||
private final String cipherAlgName;
|
||||
private final String cipherAlgMode;
|
||||
private final String cipherAlgPadding;
|
||||
|
||||
// transformation = "cipherAlgName/cipherAlgMode/cipherAlgPadding" used in Cipher.getInstance(transformation)
|
||||
private final String transformation;
|
||||
|
||||
public CryptoService(
|
||||
String passPhrase,
|
||||
@Nullable String cipherAlgName,
|
||||
@Nullable String cipherAlgMode,
|
||||
@Nullable String cipherAlgPadding,
|
||||
@Nullable String secretKeyFactoryAlg,
|
||||
@Nullable Integer saltSize,
|
||||
@Nullable Integer iterationCount,
|
||||
@Nullable Integer keyLength
|
||||
)
|
||||
{
|
||||
Preconditions.checkArgument(
|
||||
passPhrase != null && !passPhrase.isEmpty(),
|
||||
"null/empty passPhrase"
|
||||
);
|
||||
this.passPhrase = passPhrase.toCharArray();
|
||||
|
||||
this.cipherAlgName = cipherAlgName == null ? "AES" : cipherAlgName;
|
||||
this.cipherAlgMode = cipherAlgMode == null ? "CBC" : cipherAlgMode;
|
||||
this.cipherAlgPadding = cipherAlgPadding == null ? "PKCS5Padding" : cipherAlgPadding;
|
||||
this.transformation = StringUtils.format("%s/%s/%s", this.cipherAlgName, this.cipherAlgMode, this.cipherAlgPadding);
|
||||
|
||||
this.secretKeyFactoryAlg = secretKeyFactoryAlg == null ? "PBKDF2WithHmacSHA256" : secretKeyFactoryAlg;
|
||||
this.saltSize = saltSize == null ? 8 : saltSize;
|
||||
this.iterationCount = iterationCount == null ? 65536 : iterationCount;
|
||||
this.keyLength = keyLength == null ? 128 : keyLength;
|
||||
|
||||
// encrypt/decrypt a test string to ensure all params are valid
|
||||
String testString = "duh! !! !!!";
|
||||
Preconditions.checkState(
|
||||
testString.equals(StringUtils.fromUtf8(decrypt(encrypt(StringUtils.toUtf8(testString))))),
|
||||
"decrypt(encrypt(testString)) failed"
|
||||
);
|
||||
}
|
||||
|
||||
public byte[] encrypt(byte[] plain)
|
||||
{
|
||||
try {
|
||||
byte[] salt = new byte[saltSize];
|
||||
SECURE_RANDOM_INSTANCE.nextBytes(salt);
|
||||
|
||||
SecretKey tmp = getKeyFromPassword(passPhrase, salt);
|
||||
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), cipherAlgName);
|
||||
Cipher ecipher = Cipher.getInstance(transformation);
|
||||
ecipher.init(Cipher.ENCRYPT_MODE, secret);
|
||||
return new EncryptedData(
|
||||
salt,
|
||||
ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(),
|
||||
ecipher.doFinal(plain)
|
||||
).toByteAray();
|
||||
}
|
||||
catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decrypt(byte[] data)
|
||||
{
|
||||
try {
|
||||
EncryptedData encryptedData = EncryptedData.fromByteArray(data);
|
||||
|
||||
SecretKey tmp = getKeyFromPassword(passPhrase, encryptedData.getSalt());
|
||||
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), cipherAlgName);
|
||||
|
||||
Cipher dcipher = Cipher.getInstance(transformation);
|
||||
dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(encryptedData.getIv()));
|
||||
return dcipher.doFinal(encryptedData.getCipher());
|
||||
}
|
||||
catch (InvalidKeySpecException | NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private SecretKey getKeyFromPassword(char[] passPhrase, byte[] salt)
|
||||
throws NoSuchAlgorithmException, InvalidKeySpecException
|
||||
{
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance(secretKeyFactoryAlg);
|
||||
KeySpec spec = new PBEKeySpec(passPhrase, salt, iterationCount, keyLength);
|
||||
return factory.generateSecret(spec);
|
||||
}
|
||||
|
||||
private static class EncryptedData
|
||||
{
|
||||
private final byte[] salt;
|
||||
private final byte[] iv;
|
||||
private final byte[] cipher;
|
||||
|
||||
public EncryptedData(byte[] salt, byte[] iv, byte[] cipher)
|
||||
{
|
||||
this.salt = salt;
|
||||
this.iv = iv;
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
public byte[] getSalt()
|
||||
{
|
||||
return salt;
|
||||
}
|
||||
|
||||
public byte[] getIv()
|
||||
{
|
||||
return iv;
|
||||
}
|
||||
|
||||
public byte[] getCipher()
|
||||
{
|
||||
return cipher;
|
||||
}
|
||||
|
||||
public byte[] toByteAray()
|
||||
{
|
||||
int headerLength = 12;
|
||||
ByteBuffer bb = ByteBuffer.allocate(salt.length + iv.length + cipher.length + headerLength);
|
||||
bb.putInt(salt.length)
|
||||
.putInt(iv.length)
|
||||
.putInt(cipher.length)
|
||||
.put(salt)
|
||||
.put(iv)
|
||||
.put(cipher);
|
||||
bb.flip();
|
||||
|
||||
return bb.array();
|
||||
}
|
||||
|
||||
public static EncryptedData fromByteArray(byte[] array)
|
||||
{
|
||||
ByteBuffer bb = ByteBuffer.wrap(array);
|
||||
|
||||
int saltSize = bb.getInt();
|
||||
int ivSize = bb.getInt();
|
||||
int cipherSize = bb.getInt();
|
||||
|
||||
byte[] salt = new byte[saltSize];
|
||||
bb.get(salt);
|
||||
|
||||
byte[] iv = new byte[ivSize];
|
||||
bb.get(iv);
|
||||
|
||||
byte[] cipher = new byte[cipherSize];
|
||||
bb.get(cipher);
|
||||
|
||||
return new EncryptedData(salt, iv, cipher);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.crypto;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class CryptoServiceTest
|
||||
{
|
||||
@Test
|
||||
public void testEncryptDecrypt()
|
||||
{
|
||||
CryptoService cryptoService = new CryptoService(
|
||||
"random-passphrase",
|
||||
"AES",
|
||||
"CBC",
|
||||
"PKCS5Padding",
|
||||
"PBKDF2WithHmacSHA256",
|
||||
8,
|
||||
65536,
|
||||
128
|
||||
);
|
||||
|
||||
byte[] original = "i am a test string".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
byte[] decrypted = cryptoService.decrypt(cryptoService.encrypt(original));
|
||||
|
||||
Assert.assertArrayEquals(original, decrypted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidParamsConstructorFailure()
|
||||
{
|
||||
try {
|
||||
new CryptoService(
|
||||
"random-passphrase",
|
||||
"ABCD",
|
||||
"EFGH",
|
||||
"PAXXDDING",
|
||||
"QWERTY",
|
||||
8,
|
||||
65536,
|
||||
128
|
||||
);
|
||||
Assert.fail("Must Fail!!!");
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
}
|
|
@ -250,6 +250,7 @@ def build_compatible_license_names():
|
|||
compatible_licenses['CDDL/GPLv2+CE'] = 'CDDL 1.1'
|
||||
compatible_licenses['CDDL + GPLv2 with classpath exception'] = 'CDDL 1.1'
|
||||
compatible_licenses['CDDL License'] = 'CDDL 1.1'
|
||||
compatible_licenses['COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0'] = 'CDDL 1.0'
|
||||
|
||||
compatible_licenses['Eclipse Public License 1.0'] = 'Eclipse Public License 1.0'
|
||||
compatible_licenses['The Eclipse Public License, Version 1.0'] = 'Eclipse Public License 1.0'
|
||||
|
|
|
@ -240,6 +240,8 @@
|
|||
<argument>org.apache.druid.extensions:simple-client-sslcontext</argument>
|
||||
<argument>-c</argument>
|
||||
<argument>org.apache.druid.extensions:druid-basic-security</argument>
|
||||
<argument>-c</argument>
|
||||
<argument>org.apache.druid.extensions:druid-pac4j</argument>
|
||||
<argument>${druid.distribution.pulldeps.opts}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
id: druid-pac4j
|
||||
title: "Druid pac4j based Security extension"
|
||||
---
|
||||
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
|
||||
Apache Druid Extension to enable [OpenID Connect](https://openid.net/connect/) based Authentication for Druid Processes using [pac4j](https://github.com/pac4j/pac4j) as the underlying client library.
|
||||
This can be used with any authentication server that supports same e.g. [Okta](https://developer.okta.com/).
|
||||
This extension should only be used at the router node to enable a group of users in existing authentication server to interact with Druid cluster, using the [Web Console](../../operations/druid-console.html). This extension does not support JDBC client authentication.
|
||||
|
||||
## Configuration
|
||||
|
||||
### Creating an Authenticator
|
||||
```
|
||||
druid.auth.authenticatorChain=["pac4j"]
|
||||
druid.auth.authenticator.pac4j.type=pac4j
|
||||
```
|
||||
|
||||
### Properties
|
||||
|Property|Description|Default|required|
|
||||
|--------|---------------|-----------|-------|--------|
|
||||
|`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|
|
||||
|
|
@ -60,6 +60,7 @@ Core extensions are maintained by Druid committers.
|
|||
|mysql-metadata-storage|MySQL metadata store.|[link](../development/extensions-core/mysql.md)|
|
||||
|postgresql-metadata-storage|PostgreSQL metadata store.|[link](../development/extensions-core/postgresql.md)|
|
||||
|simple-client-sslcontext|Simple SSLContext provider module to be used by Druid's internal HttpClient when talking to other Druid processes over HTTPS.|[link](../development/extensions-core/simple-client-sslcontext.md)|
|
||||
|druid-pac4j|OpenID Connect authentication for druid processes.|[link](../development/extensions-core/druid-pac4j.md)|
|
||||
|
||||
## Community extensions
|
||||
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.apache.druid.extensions</groupId>
|
||||
<artifactId>druid-pac4j</artifactId>
|
||||
<name>druid-pac4j</name>
|
||||
<description>druid-pac4j</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.druid</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>0.18.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<pac4j.version>3.8.3</pac4j.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.druid</groupId>
|
||||
<artifactId>druid-server</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.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.druid</groupId>
|
||||
<artifactId>druid-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.pac4j</groupId>
|
||||
<artifactId>pac4j-core</artifactId>
|
||||
<version>${pac4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>jsr311-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.easymock</groupId>
|
||||
<artifactId>easymock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
public class OIDCConfig
|
||||
{
|
||||
@JsonProperty
|
||||
private final String clientID;
|
||||
|
||||
@JsonProperty
|
||||
private final PasswordProvider clientSecret;
|
||||
|
||||
@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
|
||||
)
|
||||
{
|
||||
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
|
||||
public String getClientID()
|
||||
{
|
||||
return clientID;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public PasswordProvider getClientSecret()
|
||||
{
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public String getDiscoveryURI()
|
||||
{
|
||||
return discoveryURI;
|
||||
}
|
||||
|
||||
@JsonProperty
|
||||
public PasswordProvider getCookiePassphrase()
|
||||
{
|
||||
return cookiePassphrase;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* 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.JacksonInject;
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
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 org.apache.druid.server.security.AuthenticationResult;
|
||||
import org.apache.druid.server.security.Authenticator;
|
||||
import org.pac4j.core.config.Config;
|
||||
import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver;
|
||||
import org.pac4j.core.http.url.DefaultUrlResolver;
|
||||
import org.pac4j.oidc.client.OidcClient;
|
||||
import org.pac4j.oidc.config.OidcConfiguration;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Filter;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
|
||||
@JsonTypeName("pac4j")
|
||||
public class Pac4jAuthenticator implements Authenticator
|
||||
{
|
||||
private final String name;
|
||||
private final String authorizerName;
|
||||
private final Supplier<Config> pac4jConfigSupplier;
|
||||
private final OIDCConfig oidcConfig;
|
||||
|
||||
@JsonCreator
|
||||
public Pac4jAuthenticator(
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("authorizerName") String authorizerName,
|
||||
@JacksonInject OIDCConfig oidcConfig
|
||||
)
|
||||
{
|
||||
this.name = name;
|
||||
this.authorizerName = authorizerName;
|
||||
this.oidcConfig = oidcConfig;
|
||||
this.pac4jConfigSupplier = Suppliers.memoize(() -> createPac4jConfig(oidcConfig));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter()
|
||||
{
|
||||
return new Pac4jFilter(
|
||||
name,
|
||||
authorizerName,
|
||||
pac4jConfigSupplier.get(),
|
||||
oidcConfig.getCookiePassphrase().getPassword()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthChallengeHeader()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public AuthenticationResult authenticateJDBCContext(Map<String, Object> context)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends Filter> getFilterClass()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getInitParameters()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath()
|
||||
{
|
||||
return "/*";
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<DispatcherType> getDispatcherType()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private Config createPac4jConfig(OIDCConfig oidcConfig)
|
||||
{
|
||||
OidcConfiguration oidcConf = new OidcConfiguration();
|
||||
oidcConf.setClientId(oidcConfig.getClientID());
|
||||
oidcConf.setSecret(oidcConfig.getClientSecret().getPassword());
|
||||
oidcConf.setDiscoveryURI(oidcConfig.getDiscoveryURI());
|
||||
oidcConf.setExpireSessionWithToken(true);
|
||||
oidcConf.setUseNonce(true);
|
||||
|
||||
OidcClient oidcClient = new OidcClient(oidcConf);
|
||||
oidcClient.setUrlResolver(new DefaultUrlResolver(true));
|
||||
oidcClient.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
|
||||
|
||||
return new Config(Pac4jCallbackResource.SELF_URL, oidcClient);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.inject.Inject;
|
||||
import org.apache.druid.guice.LazySingleton;
|
||||
import org.apache.druid.java.util.common.logger.Logger;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
* Fixed Callback endpoint used after successful login with Identity Provider e.g. OAuth server.
|
||||
* See https://www.pac4j.org/blog/understanding-the-callback-endpoint.html
|
||||
*/
|
||||
@Path(Pac4jCallbackResource.SELF_URL)
|
||||
@LazySingleton
|
||||
public class Pac4jCallbackResource
|
||||
{
|
||||
public static final String SELF_URL = "/druid-ext/druid-pac4j/callback";
|
||||
|
||||
private static final Logger LOGGER = new Logger(Pac4jCallbackResource.class);
|
||||
|
||||
@Inject
|
||||
public Pac4jCallbackResource()
|
||||
{
|
||||
}
|
||||
|
||||
@GET
|
||||
public Response callback()
|
||||
{
|
||||
LOGGER.error(
|
||||
new RuntimeException(),
|
||||
"This endpoint is to be handled by the pac4j filter to redirect users, request should never reach here."
|
||||
);
|
||||
return Response.serverError().build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* 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.Module;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Binder;
|
||||
import org.apache.druid.guice.Jerseys;
|
||||
import org.apache.druid.guice.JsonConfigProvider;
|
||||
import org.apache.druid.initialization.DruidModule;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class Pac4jDruidModule implements DruidModule
|
||||
{
|
||||
@Override
|
||||
public List<? extends Module> getJacksonModules()
|
||||
{
|
||||
return ImmutableList.of(
|
||||
new SimpleModule("Pac4jDruidSecurity").registerSubtypes(
|
||||
Pac4jAuthenticator.class
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(Binder binder)
|
||||
{
|
||||
JsonConfigProvider.bind(binder, "druid.auth.pac4j.oidc", OIDCConfig.class);
|
||||
|
||||
Jerseys.addResource(binder, Pac4jCallbackResource.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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 org.apache.druid.java.util.common.logger.Logger;
|
||||
import org.apache.druid.server.security.AuthConfig;
|
||||
import org.apache.druid.server.security.AuthenticationResult;
|
||||
import org.pac4j.core.config.Config;
|
||||
import org.pac4j.core.context.J2EContext;
|
||||
import org.pac4j.core.context.session.SessionStore;
|
||||
import org.pac4j.core.engine.CallbackLogic;
|
||||
import org.pac4j.core.engine.DefaultCallbackLogic;
|
||||
import org.pac4j.core.engine.DefaultSecurityLogic;
|
||||
import org.pac4j.core.engine.SecurityLogic;
|
||||
import org.pac4j.core.http.adapter.HttpActionAdapter;
|
||||
import org.pac4j.core.profile.CommonProfile;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class Pac4jFilter implements Filter
|
||||
{
|
||||
private static final Logger LOGGER = new Logger(Pac4jFilter.class);
|
||||
|
||||
private static final HttpActionAdapter<String, J2EContext> NOOP_HTTP_ACTION_ADAPTER = (int code, J2EContext ctx) -> null;
|
||||
|
||||
private final Config pac4jConfig;
|
||||
private final SecurityLogic<String, J2EContext> securityLogic;
|
||||
private final CallbackLogic<String, J2EContext> callbackLogic;
|
||||
private final SessionStore<J2EContext> sessionStore;
|
||||
|
||||
private final String name;
|
||||
private final String authorizerName;
|
||||
|
||||
public Pac4jFilter(String name, String authorizerName, Config pac4jConfig, String cookiePassphrase)
|
||||
{
|
||||
this.pac4jConfig = pac4jConfig;
|
||||
this.securityLogic = new DefaultSecurityLogic<>();
|
||||
this.callbackLogic = new DefaultCallbackLogic<>();
|
||||
|
||||
this.name = name;
|
||||
this.authorizerName = authorizerName;
|
||||
|
||||
this.sessionStore = new Pac4jSessionStore<>(cookiePassphrase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
|
||||
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
|
||||
J2EContext context = new J2EContext(httpServletRequest, httpServletResponse, sessionStore);
|
||||
|
||||
if (Pac4jCallbackResource.SELF_URL.equals(httpServletRequest.getRequestURI())) {
|
||||
callbackLogic.perform(
|
||||
context,
|
||||
pac4jConfig,
|
||||
NOOP_HTTP_ACTION_ADAPTER,
|
||||
"/",
|
||||
true, false, false, null);
|
||||
} else {
|
||||
String uid = securityLogic.perform(
|
||||
context,
|
||||
pac4jConfig,
|
||||
(J2EContext ctx, Collection<CommonProfile> profiles, Object... parameters) -> {
|
||||
if (profiles.isEmpty()) {
|
||||
LOGGER.warn("No profiles found after OIDC auth.");
|
||||
return null;
|
||||
} else {
|
||||
return profiles.iterator().next().getId();
|
||||
}
|
||||
},
|
||||
NOOP_HTTP_ACTION_ADAPTER,
|
||||
null, null, null, null);
|
||||
|
||||
if (uid != null) {
|
||||
AuthenticationResult authenticationResult = new AuthenticationResult(uid, authorizerName, name, null);
|
||||
servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult);
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* 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 org.apache.commons.io.IOUtils;
|
||||
import org.apache.druid.crypto.CryptoService;
|
||||
import org.apache.druid.java.util.common.StringUtils;
|
||||
import org.apache.druid.java.util.common.logger.Logger;
|
||||
import org.pac4j.core.context.ContextHelper;
|
||||
import org.pac4j.core.context.Cookie;
|
||||
import org.pac4j.core.context.Pac4jConstants;
|
||||
import org.pac4j.core.context.WebContext;
|
||||
import org.pac4j.core.context.session.SessionStore;
|
||||
import org.pac4j.core.exception.TechnicalException;
|
||||
import org.pac4j.core.profile.CommonProfile;
|
||||
import org.pac4j.core.util.JavaSerializationHelper;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* Code here is slight adaptation from <a href="https://github.com/apache/knox/blob/master/gateway-provider-security-pac4j/src/main/java/org/apache/knox/gateway/pac4j/session/KnoxSessionStore.java">KnoxSessionStore</a>
|
||||
* for storing oauth session information in cookies.
|
||||
*/
|
||||
public class Pac4jSessionStore<T extends WebContext> implements SessionStore<T>
|
||||
{
|
||||
|
||||
private static final Logger LOGGER = new Logger(Pac4jSessionStore.class);
|
||||
|
||||
public static final String PAC4J_SESSION_PREFIX = "pac4j.session.";
|
||||
|
||||
private final JavaSerializationHelper javaSerializationHelper;
|
||||
private final CryptoService cryptoService;
|
||||
|
||||
public Pac4jSessionStore(String cookiePassphrase)
|
||||
{
|
||||
javaSerializationHelper = new JavaSerializationHelper();
|
||||
cryptoService = new CryptoService(
|
||||
cookiePassphrase,
|
||||
"AES",
|
||||
"CBC",
|
||||
"PKCS5Padding",
|
||||
"PBKDF2WithHmacSHA256",
|
||||
8,
|
||||
65536,
|
||||
128
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOrCreateSessionId(WebContext context)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object get(WebContext context, String key)
|
||||
{
|
||||
final Cookie cookie = ContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key);
|
||||
Object value = null;
|
||||
if (cookie != null) {
|
||||
value = uncompressDecryptBase64(cookie.getValue());
|
||||
}
|
||||
LOGGER.debug("Get from session: [%s] = [%s]", key, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(WebContext context, String key, @Nullable Object value)
|
||||
{
|
||||
Object profile = value;
|
||||
Cookie cookie;
|
||||
|
||||
if (value == null) {
|
||||
cookie = new Cookie(PAC4J_SESSION_PREFIX + key, null);
|
||||
} else {
|
||||
if (key.contentEquals(Pac4jConstants.USER_PROFILES)) {
|
||||
/* trim the profile object */
|
||||
profile = clearUserProfile(value);
|
||||
}
|
||||
LOGGER.debug("Save in session: [%s] = [%s]", key, profile);
|
||||
cookie = new Cookie(
|
||||
PAC4J_SESSION_PREFIX + key,
|
||||
compressEncryptBase64(profile)
|
||||
);
|
||||
}
|
||||
|
||||
cookie.setDomain("");
|
||||
cookie.setHttpOnly(true);
|
||||
cookie.setSecure(ContextHelper.isHttpsOrSecure(context));
|
||||
cookie.setPath("/");
|
||||
cookie.setMaxAge(900);
|
||||
|
||||
context.addResponseCookie(cookie);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String compressEncryptBase64(final Object o)
|
||||
{
|
||||
if (o == null || "".equals(o)
|
||||
|| (o instanceof Map<?, ?> && ((Map<?, ?>) o).isEmpty())) {
|
||||
return null;
|
||||
} else {
|
||||
byte[] bytes = javaSerializationHelper.serializeToBytes((Serializable) o);
|
||||
|
||||
bytes = compress(bytes);
|
||||
if (bytes.length > 3000) {
|
||||
LOGGER.warn("Cookie too big, it might not be properly set");
|
||||
}
|
||||
|
||||
return StringUtils.encodeBase64String(cryptoService.encrypt(bytes));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Serializable uncompressDecryptBase64(final String v)
|
||||
{
|
||||
if (v != null && !v.isEmpty()) {
|
||||
byte[] bytes = StringUtils.decodeBase64String(v);
|
||||
if (bytes != null) {
|
||||
return javaSerializationHelper.unserializeFromBytes(unCompress(cryptoService.decrypt(bytes)));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] compress(final byte[] data)
|
||||
{
|
||||
try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(data.length)) {
|
||||
try (GZIPOutputStream gzip = new GZIPOutputStream(byteStream)) {
|
||||
gzip.write(data);
|
||||
}
|
||||
return byteStream.toByteArray();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new TechnicalException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] unCompress(final byte[] data)
|
||||
{
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||
GZIPInputStream gzip = new GZIPInputStream(inputStream)) {
|
||||
return IOUtils.toByteArray(gzip);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new TechnicalException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private Object clearUserProfile(final Object value)
|
||||
{
|
||||
if (value instanceof Map<?, ?>) {
|
||||
final Map<String, CommonProfile> profiles = (Map<String, CommonProfile>) value;
|
||||
profiles.forEach((name, profile) -> profile.clearSensitiveData());
|
||||
return profiles;
|
||||
} else {
|
||||
final CommonProfile profile = (CommonProfile) value;
|
||||
profile.clearSensitiveData();
|
||||
return profile;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionStore buildFromTrackableSession(WebContext arg0, Object arg1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean destroySession(WebContext arg0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTrackableSession(WebContext arg0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean renewSession(final WebContext context)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
# 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.
|
||||
|
||||
org.apache.druid.security.pac4j.Pac4jDruidModule
|
|
@ -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 org.easymock.Capture;
|
||||
import org.easymock.EasyMock;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.pac4j.core.context.Cookie;
|
||||
import org.pac4j.core.context.WebContext;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class Pac4jSessionStoreTest
|
||||
{
|
||||
@Test
|
||||
public void testSetAndGet()
|
||||
{
|
||||
Pac4jSessionStore<WebContext> sessionStore = new Pac4jSessionStore("test-cookie-passphrase");
|
||||
|
||||
WebContext webContext1 = EasyMock.mock(WebContext.class);
|
||||
EasyMock.expect(webContext1.getScheme()).andReturn("https");
|
||||
Capture<Cookie> cookieCapture = EasyMock.newCapture();
|
||||
|
||||
webContext1.addResponseCookie(EasyMock.capture(cookieCapture));
|
||||
EasyMock.replay(webContext1);
|
||||
|
||||
sessionStore.set(webContext1, "key", "value");
|
||||
|
||||
Cookie cookie = cookieCapture.getValue();
|
||||
Assert.assertTrue(cookie.isSecure());
|
||||
Assert.assertTrue(cookie.isHttpOnly());
|
||||
Assert.assertTrue(cookie.isSecure());
|
||||
Assert.assertEquals(900, cookie.getMaxAge());
|
||||
|
||||
|
||||
WebContext webContext2 = EasyMock.mock(WebContext.class);
|
||||
EasyMock.expect(webContext2.getRequestCookies()).andReturn(Collections.singletonList(cookie));
|
||||
EasyMock.replay(webContext2);
|
||||
|
||||
Assert.assertEquals("value", sessionStore.get(webContext2, "key"));
|
||||
}
|
||||
}
|
120
licenses.yaml
120
licenses.yaml
|
@ -136,6 +136,16 @@ source_paths:
|
|||
|
||||
---
|
||||
|
||||
name: code adapted from Apache Knox KnoxSessionStore and ConfigurableEncryptor
|
||||
license_category: source
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
source_paths:
|
||||
- extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java
|
||||
- core/src/main/java/org/apache/druid/crypto/CryptoService.java
|
||||
|
||||
---
|
||||
|
||||
name: AWS SDK for Java
|
||||
license_category: binary
|
||||
module: java-core
|
||||
|
@ -654,6 +664,116 @@ libraries:
|
|||
|
||||
---
|
||||
|
||||
name: pac4j-oidc java security library
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 3.8.3
|
||||
libraries:
|
||||
- org.pac4j: pac4j-oidc
|
||||
|
||||
---
|
||||
|
||||
name: pac4j-core java security library
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 3.8.3
|
||||
libraries:
|
||||
- org.pac4j: pac4j-core
|
||||
|
||||
---
|
||||
|
||||
name: org.objenesis objenesis
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 3.0.1
|
||||
libraries:
|
||||
- org.objenesis: objenesis
|
||||
|
||||
---
|
||||
|
||||
name: com.nimbusds lang-tag
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 1.4.4
|
||||
libraries:
|
||||
- com.nimbusds: lang-tag
|
||||
|
||||
---
|
||||
|
||||
name: com.nimbusds nimbus-jose-jwt
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 7.9
|
||||
libraries:
|
||||
- com.nimbusds: nimbus-jose-jwt
|
||||
|
||||
---
|
||||
|
||||
name: com.nimbusds oauth2-oidc-sdk
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 6.5
|
||||
libraries:
|
||||
- com.nimbusds: oauth2-oidc-sdk
|
||||
|
||||
---
|
||||
|
||||
name: net.bytebuddy byte-buddy
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 1.9.10
|
||||
libraries:
|
||||
- net.bytebuddy: byte-buddy
|
||||
|
||||
---
|
||||
|
||||
name: net.bytebuddy byte-buddy-agent
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: Apache License version 2.0
|
||||
version: 1.9.10
|
||||
libraries:
|
||||
- net.bytebuddy: byte-buddy-agent
|
||||
|
||||
---
|
||||
|
||||
name: org.mockito mockito-core
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: MIT License
|
||||
version: 2.28.2
|
||||
libraries:
|
||||
- org.mockito: mockito-core
|
||||
|
||||
---
|
||||
|
||||
name: javax.activation activation
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
|
||||
version: 1.1.1
|
||||
libraries:
|
||||
- javax.activation: activation
|
||||
|
||||
---
|
||||
|
||||
name: com.sun.mail javax.mail
|
||||
license_category: binary
|
||||
module: extensions/druid-pac4j
|
||||
license_name: CDDL 1.1
|
||||
version: 1.6.1
|
||||
libraries:
|
||||
- com.sun.mail: javax.mail
|
||||
|
||||
---
|
||||
|
||||
name: Netty
|
||||
license_category: binary
|
||||
module: java-core
|
||||
|
|
1
pom.xml
1
pom.xml
|
@ -143,6 +143,7 @@
|
|||
<module>extensions-core/datasketches</module>
|
||||
<module>extensions-core/druid-bloom-filter</module>
|
||||
<module>extensions-core/druid-kerberos</module>
|
||||
<module>extensions-core/druid-pac4j</module>
|
||||
<module>extensions-core/hdfs-storage</module>
|
||||
<module>extensions-core/histogram</module>
|
||||
<module>extensions-core/stats</module>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.server.security;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Sets necessary request attributes for requests sent to endpoints that need authentication but no authorization.
|
||||
*/
|
||||
public class AuthenticationOnlyResourceFilter implements Filter
|
||||
{
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
// This request will not go to an Authorizer, so we need to set this for PreResponseAuthorizationCheckFilter
|
||||
servletRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
|
||||
|
||||
filterChain.doFilter(servletRequest, servletResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -64,6 +64,13 @@ public class AuthenticationUtils
|
|||
}
|
||||
}
|
||||
|
||||
public static void addNoopAuthorizationFilters(ServletContextHandler root, List<String> unsecuredPaths)
|
||||
{
|
||||
for (String unsecuredPath : unsecuredPaths) {
|
||||
root.addFilter(new FilterHolder(new AuthenticationOnlyResourceFilter()), unsecuredPath, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addSecuritySanityCheckFilter(
|
||||
ServletContextHandler root,
|
||||
ObjectMapper jsonMapper
|
||||
|
|
|
@ -39,10 +39,13 @@ class WebConsoleJettyServerInitializer
|
|||
"/favicon.png",
|
||||
"/assets/*",
|
||||
"/public/*",
|
||||
WEB_CONSOLE_ROOT,
|
||||
"/console-config.js"
|
||||
);
|
||||
|
||||
private static final List<String> UNAUTHORIZED_PATHS_FOR_UI = ImmutableList.of(
|
||||
WEB_CONSOLE_ROOT
|
||||
);
|
||||
|
||||
static void intializeServerForWebConsoleRoot(ServletContextHandler root)
|
||||
{
|
||||
root.setInitParameter("org.eclipse.jetty.servlet.Default.redirectWelcome", "true");
|
||||
|
@ -51,6 +54,7 @@ class WebConsoleJettyServerInitializer
|
|||
root.setBaseResource(Resource.newClassPathResource("org/apache/druid/console"));
|
||||
|
||||
AuthenticationUtils.addNoopAuthenticationAndAuthorizationFilters(root, UNSECURED_PATHS_FOR_UI);
|
||||
AuthenticationUtils.addNoopAuthorizationFilters(root, UNAUTHORIZED_PATHS_FOR_UI);
|
||||
}
|
||||
|
||||
static Handler createWebConsoleRewriteHandler()
|
||||
|
|
|
@ -225,6 +225,7 @@ deserialization
|
|||
deserialize
|
||||
deserialized
|
||||
downtimes
|
||||
druid
|
||||
e.g.
|
||||
encodings
|
||||
endian
|
||||
|
@ -645,6 +646,11 @@ maximumSize
|
|||
onHeapPolling
|
||||
pollPeriod
|
||||
reverseLoadingCacheSpec
|
||||
- ../docs/development/extensions-core/druid-pac4j.md
|
||||
OAuth
|
||||
Okta
|
||||
OpenID
|
||||
pac4j
|
||||
- ../docs/development/extensions-core/google.md
|
||||
GCS
|
||||
StaticGoogleBlobStoreFirehose
|
||||
|
|
Loading…
Reference in New Issue