Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
45e01b3
druid pac4j security extension for OpenID Connect OAuth 2.0 authentic…
himanshug Dec 4, 2019
7090b75
update version in druid-pac4j pom
himanshug Dec 4, 2019
7a7eb69
introducing unauthorized resource filter
himanshug Dec 4, 2019
1368bf4
authenticated but authorized /unified-webconsole.html
himanshug Dec 4, 2019
9e4e084
use httpReq.getRequestURI() for matching callback path
himanshug Dec 4, 2019
faf402e
add documentation
himanshug Dec 4, 2019
95594fa
minor doc addition
himanshug Dec 5, 2019
111d474
licesne file updates
himanshug Dec 5, 2019
dcfe56a
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Dec 9, 2019
315d480
make dependency analyze succeed
himanshug Dec 10, 2019
cbf3500
fix doc build
himanshug Dec 10, 2019
10679e8
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Dec 10, 2019
ddbdf72
hopefully fixes doc build
himanshug Dec 10, 2019
5000ab2
hopefully fixes license check build
himanshug Dec 10, 2019
230c85c
yet another try on fixing license build
himanshug Dec 11, 2019
1e515ef
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Dec 11, 2019
44eb2b5
revert unintentional changes to website folder
himanshug Dec 11, 2019
4076497
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Dec 11, 2019
212e477
update version to 0.18.0-SNAPSHOT
himanshug Dec 11, 2019
2f2fd60
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Dec 12, 2019
78512c6
check session and its expiry on each request
himanshug Dec 18, 2019
145cfc3
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Dec 18, 2019
4ccbdcc
add crypto service
himanshug Dec 19, 2019
eb3969a
code for encrypting the cookie
himanshug Dec 19, 2019
16609f7
update doc with cookiePassphrase
himanshug Dec 19, 2019
ba21d4c
update license yaml
himanshug Dec 19, 2019
64f9954
make sessionstore in Pac4jFilter private non static
himanshug Dec 20, 2019
929ab5d
make Pac4jFilter fields final
himanshug Dec 20, 2019
3fb747c
okta: use sha256 for hmac
himanshug Dec 20, 2019
9ab87fa
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Feb 14, 2020
ae9e823
remove incubating
himanshug Feb 14, 2020
35c4244
Merge remote-tracking branch 'apache/master' into pac4j_master
himanshug Mar 16, 2020
dbe8330
add UTs for crypto util and session store impl
himanshug Mar 16, 2020
a3fd86b
use standard charsets
himanshug Mar 16, 2020
cd8a9f8
add license header
himanshug Mar 17, 2020
455dcba
remove unused file
himanshug Mar 17, 2020
5bcc6da
add org.objenesis.objenesis to license.yaml
himanshug Mar 17, 2020
0d93789
a bit of nit changes in CryptoService and embedding EncryptionResul…
himanshug Mar 23, 2020
d1d128d
rename alg to cipherAlgName
himanshug Mar 23, 2020
b6ead6f
take cipher alg name, mode and padding as input
himanshug Mar 23, 2020
c357169
add java doc for CryptoService and make it more understandable
himanshug Mar 23, 2020
db99f2a
another UT for CryptoService
himanshug Mar 23, 2020
ab6cdbe
cache pac4j Config
himanshug Mar 23, 2020
1f6712e
use generics clearly in Pac4jSessionStore
himanshug Mar 23, 2020
8c13af1
update cookiePassphrase doc to mention PasswordProvider
himanshug Mar 23, 2020
4ade873
mark stuff Nullable where appropriate in Pac4jSessionStore
himanshug Mar 23, 2020
07a8257
update doc to mention jdbc
himanshug Mar 23, 2020
1a4ab02
add error log on reaching callback resource
himanshug Mar 23, 2020
318b2d6
javadoc for Pac4jCallbackResource
himanshug Mar 23, 2020
101a53b
introduce NOOP_HTTP_ACTION_ADAPTER
himanshug Mar 23, 2020
0bc2e05
add correct module name in license file
himanshug Mar 23, 2020
335b439
correct extensions folder name in licenses.yaml
himanshug Mar 23, 2020
53010c7
replace druid-kubernetes-extensions to druid-pac4j
himanshug Mar 23, 2020
027accf
cache SecureRandom instance
himanshug Mar 23, 2020
1a1f2a0
rename UnauthorizedResourceFilter to AuthenticationOnlyResourceFilter
himanshug Mar 23, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions core/src/main/java/org/apache/druid/crypto/CryptoService.java
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is added in the druid-core module even though it's used in only the druid-pac4j extension. I guess you want to use this class in other places in the future. In that case, some detailed Javadoc would help.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, added

{
// 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);
}
}
}
70 changes: 70 additions & 0 deletions core/src/test/java/org/apache/druid/crypto/CryptoServiceTest.java
Original file line number Diff line number Diff line change
@@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: it would be nice to have more unit tests such as failures in the constructor, encrypt(), and decrypt().

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added another UT for constructor failure due to invalid params, however there can be many different invalid combination of algo names, modes etc .. generic check in constructor caches them all.

{
@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
}
}
}
1 change: 1 addition & 0 deletions distribution/bin/check-licenses.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
45 changes: 45 additions & 0 deletions docs/development/extensions-core/druid-pac4j.md
Original file line number Diff line number Diff line change
@@ -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|

1 change: 1 addition & 0 deletions docs/development/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading