Skip to content

Commit

Permalink
Merge pull request #70 from kenkoooo/feat/encrypted-private-key
Browse files Browse the repository at this point in the history
feat: support encrypted private key
  • Loading branch information
d-hrs authored Feb 26, 2024
2 parents e855ebd + 105da9a commit 8d9b552
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 11 deletions.
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Snowflake output plugin for Embulk loads records to Snowflake.
- **host**: database host name (string, required)
- **user**: database login user name (string, required)
- **password**: database login password (string, default: "")
- **privateKey**: database login using key-pair authentication(string, default: ""). This authentication method requires a 2048-bit (minimum) RSA key pair.
- **privateKey**: database login using key-pair authentication(string, default: ""). This authentication method requires a 2048-bit (minimum) RSA key pair.
- **private_key_passphrase**: passphrase for private_key (string, default: "")
- **warehouse**: destination warehouse name (string, required)
- **database**: destination database name (string, required)
- **schema**: destination schema name (string, default: "public")
Expand Down Expand Up @@ -59,10 +60,6 @@ Snowflake output plugin for Embulk loads records to Snowflake.

## Build

## Not implement
- Passphrase for `privateKey` in key-pair authentication.


```
$ ./gradlew gem # -t to watch change of files and rebuild continuously
```
15 changes: 11 additions & 4 deletions src/main/java/org/embulk/output/SnowflakeOutputPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;
import net.snowflake.client.jdbc.internal.org.bouncycastle.operator.OperatorCreationException;
import net.snowflake.client.jdbc.internal.org.bouncycastle.pkcs.PKCSException;
import org.embulk.config.ConfigDiff;
import org.embulk.config.ConfigException;
import org.embulk.config.TaskSource;
Expand Down Expand Up @@ -44,6 +46,10 @@ public interface SnowflakePluginTask extends PluginTask {
@ConfigDefault("\"\"")
String getPrivateKey();

@Config("private_key_passphrase")
@ConfigDefault("\"\"")
String getPrivateKeyPassphrase();

@Config("database")
public String getDatabase();

Expand Down Expand Up @@ -102,10 +108,11 @@ protected JdbcOutputConnector getConnector(PluginTask task, boolean retryableMet
props.setProperty("password", t.getPassword());
} else if (!t.getPrivateKey().isEmpty()) {
try {
props.put("privateKey", PrivateKeyReader.get(t.getPrivateKey()));
} catch (IOException e) {
// Because the source of newConnection definition does not assume IOException, change it to
// ConfigException.
props.put(
"privateKey", PrivateKeyReader.get(t.getPrivateKey(), t.getPrivateKeyPassphrase()));
} catch (IOException | OperatorCreationException | PKCSException e) {
// Since this method is not allowed to throw any checked exception,
// wrap it with ConfigException, which is unchecked.
throw new ConfigException(e);
}
}
Expand Down
17 changes: 15 additions & 2 deletions src/main/java/org/embulk/output/snowflake/PrivateKeyReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,31 @@
import net.snowflake.client.jdbc.internal.org.bouncycastle.jce.provider.BouncyCastleProvider;
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.PEMParser;
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import net.snowflake.client.jdbc.internal.org.bouncycastle.operator.InputDecryptorProvider;
import net.snowflake.client.jdbc.internal.org.bouncycastle.operator.OperatorCreationException;
import net.snowflake.client.jdbc.internal.org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
import net.snowflake.client.jdbc.internal.org.bouncycastle.pkcs.PKCSException;

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

PrivateKeyInfo privateKeyInfo;
if (pemObject instanceof PrivateKeyInfo) {
if (pemObject instanceof PKCS8EncryptedPrivateKeyInfo) {
// Handle the case where the private key is encrypted.
PKCS8EncryptedPrivateKeyInfo encryptedPrivateKeyInfo =
(PKCS8EncryptedPrivateKeyInfo) pemObject;
InputDecryptorProvider pkcs8Prov =
new JceOpenSSLPKCS8DecryptorProviderBuilder().build(passphrase.toCharArray());
privateKeyInfo = encryptedPrivateKeyInfo.decryptPrivateKeyInfo(pkcs8Prov);
} else if (pemObject instanceof PrivateKeyInfo) {
privateKeyInfo = (PrivateKeyInfo) pemObject;
} else {
throw new IllegalArgumentException("Provided PEM does not contain a valid Private Key");
Expand Down

0 comments on commit 8d9b552

Please sign in to comment.