Skip to content

Commit a0b186f

Browse files
authored
Merge pull request #65 from katamotokosuke/add-auth-method-that-is-key-pair-authentication
Support key pair authentication
2 parents 41e976a + df15f33 commit a0b186f

File tree

4 files changed

+59
-2
lines changed

4 files changed

+59
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ build/
1111
.classpath
1212
.project
1313
config.yml
14+
config.yaml
1415
default_jdbc_driver/
1516
/bin/

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ 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.
1617
- **warehouse**: destination warehouse name (string, required)
1718
- **database**: destination database name (string, required)
1819
- **schema**: destination schema name (string, default: "public")
@@ -58,6 +59,10 @@ Snowflake output plugin for Embulk loads records to Snowflake.
5859

5960
## Build
6061

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

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import java.sql.Types;
66
import java.util.*;
77
import org.embulk.config.ConfigDiff;
8+
import org.embulk.config.ConfigException;
89
import org.embulk.config.TaskSource;
910
import org.embulk.output.jdbc.*;
1011
import org.embulk.output.snowflake.SnowflakeCopyBatchInsert;
1112
import org.embulk.output.snowflake.SnowflakeOutputConnection;
1213
import org.embulk.output.snowflake.SnowflakeOutputConnector;
1314
import org.embulk.output.snowflake.StageIdentifier;
1415
import org.embulk.output.snowflake.StageIdentifierHolder;
16+
import org.embulk.output.snowflake.PrivateKeyReader;
1517
import org.embulk.spi.Column;
1618
import org.embulk.spi.ColumnVisitor;
1719
import org.embulk.spi.OutputPlugin;
@@ -38,6 +40,10 @@ public interface SnowflakePluginTask extends PluginTask {
3840
@ConfigDefault("\"\"")
3941
public String getPassword();
4042

43+
@Config("privateKey")
44+
@ConfigDefault("\"\"")
45+
String getPrivateKey();
46+
4147
@Config("database")
4248
public String getDatabase();
4349

@@ -92,7 +98,17 @@ protected JdbcOutputConnector getConnector(PluginTask task, boolean retryableMet
9298
Properties props = new Properties();
9399

94100
props.setProperty("user", t.getUser());
95-
props.setProperty("password", t.getPassword());
101+
if (!t.getPassword().isEmpty()) {
102+
props.setProperty("password", t.getPassword());
103+
} else if (!t.getPrivateKey().isEmpty()) {
104+
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 ConfigException.
108+
throw new ConfigException(e);
109+
}
110+
}
111+
96112
props.setProperty("warehouse", t.getWarehouse());
97113
props.setProperty("db", t.getDatabase());
98114
props.setProperty("schema", t.getSchema());
@@ -170,11 +186,14 @@ protected BatchInsert newBatchInsert(PluginTask task, Optional<MergeConfig> merg
170186
@Override
171187
protected void logConnectionProperties(String url, Properties props) {
172188
Properties maskedProps = new Properties();
173-
for (String key : props.stringPropertyNames()) {
189+
for (Object keyObj : props.keySet()) {
190+
String key = (String) keyObj;
174191
if (key.equals("password")) {
175192
maskedProps.setProperty(key, "***");
176193
} else if (key.equals("proxyPassword")) {
177194
maskedProps.setProperty(key, "***");
195+
} else if (key.equals("privateKey")) {
196+
maskedProps.setProperty(key, "***");
178197
} else {
179198
maskedProps.setProperty(key, props.getProperty(key));
180199
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package org.embulk.output.snowflake;
2+
3+
import net.snowflake.client.jdbc.internal.org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
4+
import net.snowflake.client.jdbc.internal.org.bouncycastle.jce.provider.BouncyCastleProvider;
5+
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.PEMParser;
6+
import net.snowflake.client.jdbc.internal.org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
7+
8+
import java.io.IOException;
9+
import java.io.StringReader;
10+
import java.security.PrivateKey;
11+
import java.security.Security;
12+
13+
// ref: https://docs.snowflake.com/en/developer-guide/jdbc/jdbc-configure#privatekey-property-in-connection-properties
14+
public class PrivateKeyReader
15+
{
16+
public static PrivateKey get(String pemString) throws IOException {
17+
Security.addProvider(new BouncyCastleProvider());
18+
PEMParser pemParser = new PEMParser(new StringReader(pemString));
19+
Object pemObject = pemParser.readObject();
20+
pemParser.close();
21+
22+
PrivateKeyInfo privateKeyInfo;
23+
if (pemObject instanceof PrivateKeyInfo) {
24+
privateKeyInfo = (PrivateKeyInfo) pemObject;
25+
} else {
26+
throw new IllegalArgumentException("Provided PEM does not contain a valid Private Key");
27+
}
28+
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
29+
return converter.getPrivateKey(privateKeyInfo);
30+
}
31+
32+
}

0 commit comments

Comments
 (0)