Skip to content

Commit 8d9b552

Browse files
authored
Merge pull request #70 from kenkoooo/feat/encrypted-private-key
feat: support encrypted private key
2 parents e855ebd + 105da9a commit 8d9b552

File tree

3 files changed

+28
-11
lines changed

3 files changed

+28
-11
lines changed

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ Snowflake output plugin for Embulk loads records to Snowflake.
1313
- **host**: database host name (string, required)
1414
- **user**: database login user name (string, required)
1515
- **password**: database login password (string, default: "")
16-
- **privateKey**: database login using key-pair authentication(string, default: ""). This authentication method requires a 2048-bit (minimum) RSA key pair.
16+
- **privateKey**: database login using key-pair authentication(string, default: ""). This authentication method requires a 2048-bit (minimum) RSA key pair.
17+
- **private_key_passphrase**: passphrase for private_key (string, default: "")
1718
- **warehouse**: destination warehouse name (string, required)
1819
- **database**: destination database name (string, required)
1920
- **schema**: destination schema name (string, default: "public")
@@ -59,10 +60,6 @@ Snowflake output plugin for Embulk loads records to Snowflake.
5960

6061
## Build
6162

62-
## Not implement
63-
- Passphrase for `privateKey` in key-pair authentication.
64-
65-
6663
```
6764
$ ./gradlew gem # -t to watch change of files and rebuild continuously
6865
```

src/main/java/org/embulk/output/SnowflakeOutputPlugin.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import java.sql.SQLException;
55
import java.sql.Types;
66
import java.util.*;
7+
import net.snowflake.client.jdbc.internal.org.bouncycastle.operator.OperatorCreationException;
8+
import net.snowflake.client.jdbc.internal.org.bouncycastle.pkcs.PKCSException;
79
import org.embulk.config.ConfigDiff;
810
import org.embulk.config.ConfigException;
911
import org.embulk.config.TaskSource;
@@ -44,6 +46,10 @@ public interface SnowflakePluginTask extends PluginTask {
4446
@ConfigDefault("\"\"")
4547
String getPrivateKey();
4648

49+
@Config("private_key_passphrase")
50+
@ConfigDefault("\"\"")
51+
String getPrivateKeyPassphrase();
52+
4753
@Config("database")
4854
public String getDatabase();
4955

@@ -102,10 +108,11 @@ protected JdbcOutputConnector getConnector(PluginTask task, boolean retryableMet
102108
props.setProperty("password", t.getPassword());
103109
} else if (!t.getPrivateKey().isEmpty()) {
104110
try {
105-
props.put("privateKey", PrivateKeyReader.get(t.getPrivateKey()));
106-
} catch (IOException e) {
107-
// Because the source of newConnection definition does not assume IOException, change it to
108-
// ConfigException.
111+
props.put(
112+
"privateKey", PrivateKeyReader.get(t.getPrivateKey(), t.getPrivateKeyPassphrase()));
113+
} catch (IOException | OperatorCreationException | PKCSException e) {
114+
// Since this method is not allowed to throw any checked exception,
115+
// wrap it with ConfigException, which is unchecked.
109116
throw new ConfigException(e);
110117
}
111118
}

src/main/java/org/embulk/output/snowflake/PrivateKeyReader.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,31 @@
88
import net.snowflake.client.jdbc.internal.org.bouncycastle.jce.provider.BouncyCastleProvider;
99
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.PEMParser;
1010
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
11+
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
12+
import net.snowflake.client.jdbc.internal.org.bouncycastle.operator.InputDecryptorProvider;
13+
import net.snowflake.client.jdbc.internal.org.bouncycastle.operator.OperatorCreationException;
14+
import net.snowflake.client.jdbc.internal.org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
15+
import net.snowflake.client.jdbc.internal.org.bouncycastle.pkcs.PKCSException;
1116

1217
// ref:
1318
// https://docs.snowflake.com/en/developer-guide/jdbc/jdbc-configure#privatekey-property-in-connection-properties
1419
public class PrivateKeyReader {
15-
public static PrivateKey get(String pemString) throws IOException {
20+
public static PrivateKey get(String pemString, String passphrase)
21+
throws IOException, OperatorCreationException, PKCSException {
1622
Security.addProvider(new BouncyCastleProvider());
1723
PEMParser pemParser = new PEMParser(new StringReader(pemString));
1824
Object pemObject = pemParser.readObject();
1925
pemParser.close();
2026

2127
PrivateKeyInfo privateKeyInfo;
22-
if (pemObject instanceof PrivateKeyInfo) {
28+
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
29+
// Handle the case where the private key is encrypted.
30+
PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo =
31+
(PKCS8EncryptedPrivateKeyInfo) pemObject;
32+
InputDecryptorProvider pkcs8Prov =
33+
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(passphrase.toCharArray());
34+
privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(pkcs8Prov);
35+
} else if (pemObject instanceof PrivateKeyInfo) {
2336
privateKeyInfo = (PrivateKeyInfo) pemObject;
2437
} else {
2538
throw new IllegalArgumentException("Provided PEM does not contain a valid Private Key");

0 commit comments

Comments
 (0)