Skip to content

Commit fc6c236

Browse files
committed
Add KeyStore based CredentialProvider for JDBC based connectors
1 parent 7db98e6 commit fc6c236

File tree

7 files changed

+300
-1
lines changed

7 files changed

+300
-1
lines changed

presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/credential/CredentialProviderModule.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,18 @@
1919
import io.airlift.configuration.ConfigurationFactory;
2020
import io.prestosql.plugin.jdbc.BaseJdbcConfig;
2121
import io.prestosql.plugin.jdbc.credential.file.ConfigFileBasedCredentialProviderConfig;
22+
import io.prestosql.plugin.jdbc.credential.keystore.KeyStoreBasedCredentialProviderConfig;
2223

2324
import javax.inject.Inject;
2425
import javax.inject.Provider;
2526

2627
import java.io.IOException;
28+
import java.security.KeyStore;
29+
import java.security.KeyStoreException;
30+
import java.security.NoSuchAlgorithmException;
31+
import java.security.UnrecoverableEntryException;
32+
import java.security.cert.CertificateException;
33+
import java.security.spec.InvalidKeySpecException;
2734
import java.util.Map;
2835

2936
import static com.google.inject.Scopes.SINGLETON;
@@ -32,6 +39,9 @@
3239
import static io.airlift.configuration.ConfigurationLoader.loadPropertiesFrom;
3340
import static io.prestosql.plugin.jdbc.credential.CredentialProviderType.FILE;
3441
import static io.prestosql.plugin.jdbc.credential.CredentialProviderType.INLINE;
42+
import static io.prestosql.plugin.jdbc.credential.CredentialProviderType.KEYSTORE;
43+
import static io.prestosql.plugin.jdbc.credential.keystore.KeyStoreUtils.loadKeyStore;
44+
import static io.prestosql.plugin.jdbc.credential.keystore.KeyStoreUtils.readEntity;
3545
import static java.util.Objects.requireNonNull;
3646

3747
public class CredentialProviderModule
@@ -52,6 +62,12 @@ protected void setup(Binder binder)
5262
configBinder(binder).bindConfig(ConfigFileBasedCredentialProviderConfig.class);
5363
internalBinder.bind(CredentialProvider.class).annotatedWith(ForExtraCredentialProvider.class).toProvider(ConfigFileBasedCredentialProviderFactory.class).in(SINGLETON);
5464
});
65+
bindCredentialProviderModule(
66+
KEYSTORE,
67+
internalBinder -> {
68+
configBinder(binder).bindConfig(KeyStoreBasedCredentialProviderConfig.class);
69+
internalBinder.bind(CredentialProvider.class).annotatedWith(ForExtraCredentialProvider.class).toProvider(KeyStoreBasedCredentialProviderFactory.class);
70+
});
5571
binder.bind(CredentialProvider.class).to(ExtraCredentialProvider.class).in(SINGLETON);
5672
}
5773

@@ -83,4 +99,28 @@ public CredentialProvider get()
8399
return new ConfigFileBasedCredentialProvider(credentialsConfig);
84100
}
85101
}
102+
103+
private static class KeyStoreBasedCredentialProviderFactory
104+
implements Provider<CredentialProvider>
105+
{
106+
private final CredentialConfig credentialsConfig;
107+
108+
@Inject
109+
public KeyStoreBasedCredentialProviderFactory(KeyStoreBasedCredentialProviderConfig config)
110+
throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, InvalidKeySpecException, UnrecoverableEntryException
111+
{
112+
requireNonNull(config, "config is null");
113+
KeyStore keyStore = loadKeyStore(config.getKeyStoreType(), config.getKeyStoreFilePath(), config.getKeyStorePassword());
114+
115+
credentialsConfig = new CredentialConfig()
116+
.setConnectionUser(readEntity(keyStore, config.getUserCredentialName(), config.getPasswordForUserCredentialName()))
117+
.setConnectionPassword(readEntity(keyStore, config.getPasswordCredentialName(), config.getPasswordForPasswordCredentialName()));
118+
}
119+
120+
@Override
121+
public CredentialProvider get()
122+
{
123+
return new ConfigFileBasedCredentialProvider(credentialsConfig);
124+
}
125+
}
86126
}

presto-base-jdbc/src/main/java/io/prestosql/plugin/jdbc/credential/CredentialProviderType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ public enum CredentialProviderType
1717
{
1818
INLINE,
1919
FILE,
20+
KEYSTORE,
2021
/**/
2122
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.prestosql.plugin.jdbc.credential.keystore;
15+
16+
import io.airlift.configuration.Config;
17+
import io.airlift.configuration.ConfigSecuritySensitive;
18+
19+
import javax.validation.constraints.NotNull;
20+
21+
public class KeyStoreBasedCredentialProviderConfig
22+
{
23+
private String keyStoreFilePath;
24+
private String keyStoreType;
25+
private String keyStorePassword;
26+
private String userCredentialName;
27+
private String passwordForUserCredentialName;
28+
private String passwordCredentialName;
29+
private String passwordForPasswordCredentialName;
30+
31+
@Config("keystore-file-path")
32+
public KeyStoreBasedCredentialProviderConfig setKeyStoreFilePath(String keyStoreFilePath)
33+
{
34+
this.keyStoreFilePath = keyStoreFilePath;
35+
return this;
36+
}
37+
38+
@NotNull
39+
public String getKeyStoreFilePath()
40+
{
41+
return keyStoreFilePath;
42+
}
43+
44+
@Config("keystore-type")
45+
public KeyStoreBasedCredentialProviderConfig setKeyStoreType(String keyStoreType)
46+
{
47+
this.keyStoreType = keyStoreType;
48+
return this;
49+
}
50+
51+
@NotNull
52+
public String getKeyStoreType()
53+
{
54+
return keyStoreType;
55+
}
56+
57+
@Config("keystore-password")
58+
@ConfigSecuritySensitive
59+
public KeyStoreBasedCredentialProviderConfig setKeyStorePassword(String keyStorePassword)
60+
{
61+
this.keyStorePassword = keyStorePassword;
62+
return this;
63+
}
64+
65+
@Config("keystore-user-credential-password")
66+
public KeyStoreBasedCredentialProviderConfig setPasswordForUserCredentialName(String passwordForUserCredentialName)
67+
{
68+
this.passwordForUserCredentialName = passwordForUserCredentialName;
69+
return this;
70+
}
71+
72+
@NotNull
73+
public String getPasswordForUserCredentialName()
74+
{
75+
return passwordForUserCredentialName;
76+
}
77+
78+
@Config("keystore-password-credential-password")
79+
public KeyStoreBasedCredentialProviderConfig setPasswordForPasswordCredentialName(String passwordForPasswordCredentialName)
80+
{
81+
this.passwordForPasswordCredentialName = passwordForPasswordCredentialName;
82+
return this;
83+
}
84+
85+
public String getPasswordForPasswordCredentialName()
86+
{
87+
return passwordForPasswordCredentialName;
88+
}
89+
90+
@NotNull
91+
public String getKeyStorePassword()
92+
{
93+
return keyStorePassword;
94+
}
95+
96+
@Config("keystore-user-credential-name")
97+
public KeyStoreBasedCredentialProviderConfig setUserCredentialName(String userCredntialName)
98+
{
99+
this.userCredentialName = userCredntialName;
100+
return this;
101+
}
102+
103+
@NotNull
104+
public String getUserCredentialName()
105+
{
106+
return userCredentialName;
107+
}
108+
109+
@Config("keystore-password-credential-name")
110+
public KeyStoreBasedCredentialProviderConfig setPasswordCredentialName(String passwordCredentialName)
111+
{
112+
this.passwordCredentialName = passwordCredentialName;
113+
return this;
114+
}
115+
116+
@NotNull
117+
public String getPasswordCredentialName()
118+
{
119+
return passwordCredentialName;
120+
}
121+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.prestosql.plugin.jdbc.credential.keystore;
15+
16+
import javax.crypto.SecretKeyFactory;
17+
import javax.crypto.spec.PBEKeySpec;
18+
19+
import java.io.FileInputStream;
20+
import java.io.IOException;
21+
import java.security.KeyStore;
22+
import java.security.KeyStore.PasswordProtection;
23+
import java.security.KeyStore.SecretKeyEntry;
24+
import java.security.KeyStoreException;
25+
import java.security.NoSuchAlgorithmException;
26+
import java.security.UnrecoverableEntryException;
27+
import java.security.cert.CertificateException;
28+
import java.security.spec.InvalidKeySpecException;
29+
30+
public class KeyStoreUtils
31+
{
32+
private KeyStoreUtils()
33+
{}
34+
35+
public static KeyStore loadKeyStore(String keyStoreType, String keyStorePath, String keyStorePassword)
36+
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException
37+
{
38+
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
39+
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray());
40+
return keyStore;
41+
}
42+
43+
public static String readEntity(KeyStore keyStore, String entityAlias, String entityPassword)
44+
throws UnrecoverableEntryException, NoSuchAlgorithmException, KeyStoreException, InvalidKeySpecException
45+
{
46+
SecretKeyEntry secretKeyEntry = (SecretKeyEntry) keyStore.getEntry(entityAlias, new PasswordProtection(entityPassword.toCharArray()));
47+
48+
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBE");
49+
PBEKeySpec keySpec = (PBEKeySpec) factory.getKeySpec(secretKeyEntry.getSecretKey(), PBEKeySpec.class);
50+
51+
return new String(keySpec.getPassword());
52+
}
53+
}

presto-base-jdbc/src/test/java/io/prestosql/plugin/jdbc/credential/TestCredentialProvider.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,27 @@ public void testFileCredentialProvider()
5151
assertEquals(credentialProvider.getConnectionPassword(Optional.empty()).get(), "password_for_user_from_file");
5252
}
5353

54-
private static CredentialProvider getCredentialProvider(Map<String, String> properties)
54+
@Test
55+
public void testKeyStoreBasedCredentialProvider()
56+
{
57+
Map<String, String> properties = new ImmutableMap.Builder<String, String>()
58+
.put("connection-url", "jdbc:h2:mem:config")
59+
.put("credential-provider.type", "KEYSTORE")
60+
.put("keystore-file-path", getResourceFilePath("credentials.jceks"))
61+
.put("keystore-type", "JCEKS")
62+
.put("keystore-password", "keystore_password")
63+
.put("keystore-user-credential-name", "userName")
64+
.put("keystore-user-credential-password", "keystore_password_for_user_name")
65+
.put("keystore-password-credential-name", "password")
66+
.put("keystore-password-credential-password", "keystore_password_for_password")
67+
.build();
68+
69+
CredentialProvider credentialProvider = getCredentialProvider(properties);
70+
assertEquals(credentialProvider.getConnectionUser(Optional.empty()).get(), "user_from_keystore");
71+
assertEquals(credentialProvider.getConnectionPassword(Optional.empty()).get(), "password_from_keystore");
72+
}
73+
74+
private CredentialProvider getCredentialProvider(Map<String, String> properties)
5575
{
5676
return new Bootstrap(ImmutableList.of(new CredentialProviderModule()))
5777
.setOptionalConfigurationProperties(properties)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package io.prestosql.plugin.jdbc.credential.keystore;
15+
16+
import com.google.common.collect.ImmutableMap;
17+
import org.testng.annotations.Test;
18+
19+
import java.util.Map;
20+
21+
import static io.airlift.configuration.testing.ConfigAssertions.assertFullMapping;
22+
import static io.airlift.configuration.testing.ConfigAssertions.assertRecordedDefaults;
23+
import static io.airlift.configuration.testing.ConfigAssertions.recordDefaults;
24+
25+
public class TestKeyStoreBasedCredentialProviderConfig
26+
{
27+
@Test
28+
public void testDefaults()
29+
{
30+
assertRecordedDefaults(recordDefaults(KeyStoreBasedCredentialProviderConfig.class)
31+
.setKeyStoreFilePath(null)
32+
.setKeyStoreType(null)
33+
.setKeyStorePassword(null)
34+
.setUserCredentialName(null)
35+
.setPasswordForUserCredentialName(null)
36+
.setPasswordCredentialName(null)
37+
.setPasswordForPasswordCredentialName(null));
38+
}
39+
40+
@Test
41+
public void testExplicitPropertyMappings()
42+
{
43+
Map<String, String> properties = new ImmutableMap.Builder<String, String>()
44+
.put("keystore-file-path", "/presto/credentials.jceks")
45+
.put("keystore-type", "JCEKS")
46+
.put("keystore-password", "keystore_password")
47+
.put("keystore-user-credential-name", "userName")
48+
.put("keystore-user-credential-password", "keystore_password_for_user_name")
49+
.put("keystore-password-credential-name", "password")
50+
.put("keystore-password-credential-password", "keystore_password_for_password")
51+
.build();
52+
53+
KeyStoreBasedCredentialProviderConfig expected = new KeyStoreBasedCredentialProviderConfig()
54+
.setKeyStoreFilePath("/presto/credentials.jceks")
55+
.setKeyStoreType("JCEKS")
56+
.setKeyStorePassword("keystore_password")
57+
.setUserCredentialName("userName")
58+
.setPasswordForUserCredentialName("keystore_password_for_user_name")
59+
.setPasswordCredentialName("password")
60+
.setPasswordForPasswordCredentialName("keystore_password_for_password");
61+
62+
assertFullMapping(properties, expected);
63+
}
64+
}
1.24 KB
Binary file not shown.

0 commit comments

Comments
 (0)