embulk-filter-encrypt 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ChangeLog +4 -0
- data/README.md +29 -3
- data/build.gradle +4 -2
- data/src/main/java/org/embulk/filter/encrypt/EncryptFilterPlugin.java +190 -14
- data/src/test/java/org/embulk/filter/encrypt/TestEncryptFilterPlugin.java +284 -8
- metadata +16 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d7278dc0b04a51ad156ea94582943fd934bd435d
|
4
|
+
data.tar.gz: 30091df627949d1342c7ac7389e26f353ebccb5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3dfa97e8c4c1adb2cb28c67eaa07b405e5fce86643245b9f36a4458693235f73406b1353943b326bcce7b6b1a3db91d1d937e6fbcbfa33fe6199d5ca31c9874a
|
7
|
+
data.tar.gz: 0f0408f79ec21e587e2843225c40756da31322d4f90d5992fc7c204e3b63030f920229bd0d5de8ccb2ed948ff34620f6cc0e28a85f29f3e248da59682a8bf6ad
|
data/ChangeLog
CHANGED
data/README.md
CHANGED
@@ -22,10 +22,17 @@ You can apply encryption to password column and get following outputs:
|
|
22
22
|
|
23
23
|
- **algorithm**: encryption algorithm (see below) (enum, required)
|
24
24
|
- **column_names**: names of string columns to encrypt (array of string, required)
|
25
|
-
- **
|
26
|
-
- **
|
25
|
+
- **key_type**: encryption key (enum, optional, default: inline), can be either "inline" or "s3"
|
26
|
+
- **key_hex**: encryption key (string, required if key_type is inline)
|
27
|
+
- **iv_hex**: encryption initialization vector (string, required if mode of the algorithm is CBC and key_type is inline)
|
27
28
|
- **output_encoding**: the encoding of encrypted value, can be either "base64" or "hex" (base16)
|
28
|
-
|
29
|
+
- **aws_params**: AWS/S3 parameters (hash, required if key_type is s3)
|
30
|
+
- **region**: a valid AWS region
|
31
|
+
- **access_key**: a valid AWS access key
|
32
|
+
- **secret_key**: a valid AWS secret key
|
33
|
+
- **bucket**: a valid S3 bucket
|
34
|
+
- **path**: a valid S3 key (S3 file path)
|
35
|
+
|
29
36
|
## Algorithms
|
30
37
|
|
31
38
|
Available algorithms are:
|
@@ -115,6 +122,8 @@ You can use Hive's `aes_decrypt(input binary, key binary)` function (available s
|
|
115
122
|
|
116
123
|
## Example
|
117
124
|
|
125
|
+
* Inline key type
|
126
|
+
|
118
127
|
```yaml
|
119
128
|
filters:
|
120
129
|
- type: encrypt
|
@@ -125,6 +134,23 @@ filters:
|
|
125
134
|
output_encoding: hex
|
126
135
|
```
|
127
136
|
|
137
|
+
* S3 key type
|
138
|
+
|
139
|
+
```yaml
|
140
|
+
filters:
|
141
|
+
- type: encrypt
|
142
|
+
algorithm: AES-256-CBC
|
143
|
+
column_names: [password, ip]
|
144
|
+
output_encoding: hex
|
145
|
+
key_type: s3
|
146
|
+
aws_params:
|
147
|
+
region: us-east-2
|
148
|
+
access_key: XXXXXXXXXXXXXXXXXXXX
|
149
|
+
secret_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
150
|
+
bucket: com.sample.keys
|
151
|
+
path: key.aes
|
152
|
+
```
|
153
|
+
|
128
154
|
## Build
|
129
155
|
|
130
156
|
```
|
data/build.gradle
CHANGED
@@ -13,7 +13,7 @@ configurations {
|
|
13
13
|
provided
|
14
14
|
}
|
15
15
|
|
16
|
-
version = "0.2.
|
16
|
+
version = "0.2.1"
|
17
17
|
|
18
18
|
sourceCompatibility = 1.7
|
19
19
|
targetCompatibility = 1.7
|
@@ -21,9 +21,11 @@ targetCompatibility = 1.7
|
|
21
21
|
dependencies {
|
22
22
|
compile "org.embulk:embulk-core:0.8.6"
|
23
23
|
provided "org.embulk:embulk-core:0.8.6"
|
24
|
-
|
24
|
+
compile "com.amazonaws:aws-java-sdk-s3:1.11.253"
|
25
|
+
|
25
26
|
testCompile "junit:junit:4.+"
|
26
27
|
testCompile "org.embulk:embulk-core:0.8.18:tests"
|
28
|
+
testCompile "org.mockito:mockito-core:2.+"
|
27
29
|
}
|
28
30
|
|
29
31
|
task classpath(type: Copy, dependsOn: ["jar"]) {
|
@@ -1,7 +1,17 @@
|
|
1
1
|
package org.embulk.filter.encrypt;
|
2
2
|
|
3
|
+
import com.amazonaws.AmazonServiceException;
|
4
|
+
import com.amazonaws.SdkClientException;
|
5
|
+
import com.amazonaws.auth.AWSCredentials;
|
6
|
+
import com.amazonaws.auth.AWSCredentialsProvider;
|
7
|
+
import com.amazonaws.auth.BasicAWSCredentials;
|
8
|
+
import com.amazonaws.services.s3.AmazonS3;
|
9
|
+
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
|
10
|
+
import com.amazonaws.services.s3.model.GetObjectRequest;
|
11
|
+
import com.amazonaws.services.s3.model.S3Object;
|
3
12
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
4
13
|
import com.fasterxml.jackson.annotation.JsonValue;
|
14
|
+
import com.google.common.annotations.VisibleForTesting;
|
5
15
|
import com.google.common.base.Optional;
|
6
16
|
import com.google.common.io.BaseEncoding;
|
7
17
|
import org.embulk.config.Config;
|
@@ -10,6 +20,7 @@ import org.embulk.config.ConfigException;
|
|
10
20
|
import org.embulk.config.ConfigSource;
|
11
21
|
import org.embulk.config.Task;
|
12
22
|
import org.embulk.config.TaskSource;
|
23
|
+
import org.embulk.config.YamlTagResolver;
|
13
24
|
import org.embulk.spi.Column;
|
14
25
|
import org.embulk.spi.ColumnVisitor;
|
15
26
|
import org.embulk.spi.DataException;
|
@@ -21,6 +32,10 @@ import org.embulk.spi.PageOutput;
|
|
21
32
|
import org.embulk.spi.PageReader;
|
22
33
|
import org.embulk.spi.Schema;
|
23
34
|
import org.slf4j.Logger;
|
35
|
+
import org.yaml.snakeyaml.DumperOptions;
|
36
|
+
import org.yaml.snakeyaml.Yaml;
|
37
|
+
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
38
|
+
import org.yaml.snakeyaml.representer.Representer;
|
24
39
|
|
25
40
|
import javax.crypto.BadPaddingException;
|
26
41
|
import javax.crypto.Cipher;
|
@@ -29,12 +44,15 @@ import javax.crypto.NoSuchPaddingException;
|
|
29
44
|
import javax.crypto.spec.IvParameterSpec;
|
30
45
|
import javax.crypto.spec.SecretKeySpec;
|
31
46
|
|
47
|
+
import java.io.IOException;
|
32
48
|
import java.security.InvalidAlgorithmParameterException;
|
33
49
|
import java.security.InvalidKeyException;
|
34
50
|
import java.security.NoSuchAlgorithmException;
|
35
51
|
import java.util.EnumSet;
|
36
52
|
import java.util.List;
|
53
|
+
import java.util.Map;
|
37
54
|
|
55
|
+
import static com.google.common.base.Strings.isNullOrEmpty;
|
38
56
|
import static java.lang.String.format;
|
39
57
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
40
58
|
import static org.apache.commons.lang3.StringUtils.join;
|
@@ -42,7 +60,7 @@ import static org.apache.commons.lang3.StringUtils.join;
|
|
42
60
|
public class EncryptFilterPlugin
|
43
61
|
implements FilterPlugin
|
44
62
|
{
|
45
|
-
public
|
63
|
+
public enum Algorithm
|
46
64
|
{
|
47
65
|
AES_256_CBC("AES/CBC/PKCS5Padding", "AES", 256, true, "AES", "AES-256", "AES-256-CBC"),
|
48
66
|
AES_192_CBC("AES/CBC/PKCS5Padding", "AES", 192, true, "AES-192", "AES-192-CBC"),
|
@@ -57,7 +75,7 @@ public class EncryptFilterPlugin
|
|
57
75
|
private final boolean useIv;
|
58
76
|
private String[] displayNames;
|
59
77
|
|
60
|
-
|
78
|
+
Algorithm(String javaName, String javaKeySpecName, int keyLength, boolean useIv, String... displayNames)
|
61
79
|
{
|
62
80
|
this.javaName = javaName;
|
63
81
|
this.javaKeySpecName = javaKeySpecName;
|
@@ -149,6 +167,25 @@ public class EncryptFilterPlugin
|
|
149
167
|
}
|
150
168
|
}
|
151
169
|
|
170
|
+
public enum KeyType
|
171
|
+
{
|
172
|
+
INLINE,
|
173
|
+
S3;
|
174
|
+
|
175
|
+
@JsonCreator
|
176
|
+
public static KeyType of(String value)
|
177
|
+
{
|
178
|
+
return KeyType.valueOf(value.toUpperCase());
|
179
|
+
}
|
180
|
+
|
181
|
+
@Override
|
182
|
+
@JsonValue
|
183
|
+
public String toString()
|
184
|
+
{
|
185
|
+
return super.toString().toLowerCase();
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
152
189
|
public interface PluginTask
|
153
190
|
extends Task
|
154
191
|
{
|
@@ -159,17 +196,49 @@ public class EncryptFilterPlugin
|
|
159
196
|
@ConfigDefault("\"base64\"")
|
160
197
|
public Encoder getOutputEncoding();
|
161
198
|
|
199
|
+
@Config("key_type")
|
200
|
+
@ConfigDefault("\"inline\"")
|
201
|
+
KeyType getKeyType();
|
202
|
+
|
162
203
|
@Config("key_hex")
|
163
|
-
|
204
|
+
@ConfigDefault("null")
|
205
|
+
public Optional<String> getKeyHex();
|
206
|
+
|
207
|
+
public void setKeyHex(Optional<String> key);
|
164
208
|
|
165
209
|
@Config("iv_hex")
|
166
210
|
@ConfigDefault("null")
|
167
211
|
public Optional<String> getIvHex();
|
168
212
|
|
213
|
+
public void setIvHex(Optional<String> iv);
|
214
|
+
|
215
|
+
@Config("aws_params")
|
216
|
+
@ConfigDefault("null")
|
217
|
+
public Optional<AWSParams> getAWSParams();
|
218
|
+
|
169
219
|
@Config("column_names")
|
170
220
|
public List<String> getColumnNames();
|
171
221
|
}
|
172
222
|
|
223
|
+
public interface AWSParams extends Task
|
224
|
+
{
|
225
|
+
@Config("region")
|
226
|
+
public String getRegion();
|
227
|
+
|
228
|
+
@Config("access_key")
|
229
|
+
public String getAccessKey();
|
230
|
+
|
231
|
+
@Config("secret_key")
|
232
|
+
public String getSecretKey();
|
233
|
+
|
234
|
+
@Config("bucket")
|
235
|
+
public String getBucket();
|
236
|
+
|
237
|
+
@Config("path")
|
238
|
+
public String getPath();
|
239
|
+
}
|
240
|
+
|
241
|
+
private static final Yaml yaml = new Yaml(new SafeConstructor(), new Representer(), new DumperOptions(), new YamlTagResolver());
|
173
242
|
private static final Logger log = Exec.getLogger(EncryptFilterPlugin.class);
|
174
243
|
|
175
244
|
@Override
|
@@ -178,27 +247,134 @@ public class EncryptFilterPlugin
|
|
178
247
|
{
|
179
248
|
PluginTask task = config.loadConfig(PluginTask.class);
|
180
249
|
|
181
|
-
|
182
|
-
|
250
|
+
validateAndResolveKey(task, inputSchema);
|
251
|
+
|
252
|
+
control.run(task.dump(), inputSchema);
|
253
|
+
}
|
254
|
+
|
255
|
+
@VisibleForTesting
|
256
|
+
public Map<String, String> retrieveKey(final String bucket, final String path, final AmazonS3 client)
|
257
|
+
{
|
258
|
+
S3Object fullObject = null;
|
259
|
+
|
260
|
+
try {
|
261
|
+
fullObject = client.getObject(new GetObjectRequest(bucket, path));
|
262
|
+
if (fullObject == null) {
|
263
|
+
throw new ConfigException("S3 key file is not enabled to be retrieved");
|
264
|
+
}
|
265
|
+
return (Map<String, String>) yaml.load(fullObject.getObjectContent());
|
266
|
+
}
|
267
|
+
catch (AmazonServiceException e) {
|
268
|
+
// The call was transmitted successfully, but Amazon S3 couldn't process
|
269
|
+
// it, so it returned an error response.
|
270
|
+
if (e.getErrorType().equals(AmazonServiceException.ErrorType.Client)) {
|
271
|
+
// HTTP 40x errors. auth error, bucket doesn't exist, etc. See AWS document for the full list:
|
272
|
+
// http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
|
273
|
+
if (e.getStatusCode() != 400
|
274
|
+
|| "ExpiredToken".equalsIgnoreCase(e.getErrorCode())) {
|
275
|
+
throw new ConfigException(e);
|
276
|
+
}
|
277
|
+
}
|
278
|
+
throw e;
|
279
|
+
}
|
280
|
+
catch (ClassCastException e) {
|
281
|
+
throw new ConfigException("S3 key file content is unexpected format");
|
282
|
+
}
|
283
|
+
finally {
|
284
|
+
// To ensure that the network connection doesn't remain open, close any open input streams.
|
285
|
+
if (fullObject != null) {
|
286
|
+
try {
|
287
|
+
fullObject.close();
|
288
|
+
}
|
289
|
+
catch (IOException e) {
|
290
|
+
log.warn("Failure to close S3 Object input stream", e);
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
@VisibleForTesting
|
297
|
+
public AmazonS3 newS3Client(final AWSParams awsParams)
|
298
|
+
{
|
299
|
+
AWSCredentialsProvider awsCredentialsProvider = new AWSCredentialsProvider()
|
300
|
+
{
|
301
|
+
@Override
|
302
|
+
public AWSCredentials getCredentials()
|
303
|
+
{
|
304
|
+
return new BasicAWSCredentials(awsParams.getAccessKey(), awsParams.getSecretKey());
|
305
|
+
}
|
306
|
+
|
307
|
+
@Override
|
308
|
+
public void refresh()
|
309
|
+
{
|
310
|
+
}
|
311
|
+
};
|
312
|
+
|
313
|
+
try {
|
314
|
+
return AmazonS3ClientBuilder.standard()
|
315
|
+
.withRegion(awsParams.getRegion())
|
316
|
+
.withCredentials(awsCredentialsProvider)
|
317
|
+
.build();
|
318
|
+
}
|
319
|
+
catch (SdkClientException e) {
|
320
|
+
throw new ConfigException(e);
|
183
321
|
}
|
184
|
-
|
185
|
-
|
322
|
+
}
|
323
|
+
|
324
|
+
private void validateAndResolveKey(PluginTask task, Schema schema) throws ConfigException
|
325
|
+
{
|
326
|
+
switch (task.getKeyType()) {
|
327
|
+
case INLINE:
|
328
|
+
if (!task.getKeyHex().isPresent()) {
|
329
|
+
throw new ConfigException("Field 'key_hex' is required but not set");
|
330
|
+
}
|
331
|
+
if (task.getAlgorithm().useIv() && !task.getIvHex().isPresent()) {
|
332
|
+
throw new ConfigException("Algorithm '" + task.getAlgorithm() + "' requires initialization vector. Please generate one and set it to iv_hex option.");
|
333
|
+
}
|
334
|
+
else if (!task.getAlgorithm().useIv() && task.getIvHex().isPresent()) {
|
335
|
+
log.warn("Algorithm '" + task.getAlgorithm() + "' doesn't use initialization vector. iv_hex is ignored");
|
336
|
+
}
|
337
|
+
break;
|
338
|
+
case S3:
|
339
|
+
if (!task.getAWSParams().isPresent()) {
|
340
|
+
throw new ConfigException("AWS Params are required for S3 Key type");
|
341
|
+
}
|
342
|
+
AWSParams params = task.getAWSParams().get();
|
343
|
+
AmazonS3 s3Client = newS3Client(params);
|
344
|
+
Map<String, String> keys = retrieveKey(params.getBucket(), params.getPath(), s3Client);
|
345
|
+
|
346
|
+
String key = keys.get("key_hex");
|
347
|
+
if (isNullOrEmpty(key)) {
|
348
|
+
throw new ConfigException("Field 'key_hex' is required but not set");
|
349
|
+
}
|
350
|
+
String iv = keys.get("iv_hex");
|
351
|
+
if (task.getAlgorithm().useIv() && isNullOrEmpty(iv)) {
|
352
|
+
throw new ConfigException("Algorithm '" + task.getAlgorithm() + "' requires initialization vector. Please generate one and set it to iv_hex option.");
|
353
|
+
}
|
354
|
+
else if (!task.getAlgorithm().useIv() && !isNullOrEmpty(iv)) {
|
355
|
+
log.warn("Algorithm '" + task.getAlgorithm() + "' doesn't use initialization vector. iv_hex is ignored");
|
356
|
+
}
|
357
|
+
task.setKeyHex(Optional.of(key));
|
358
|
+
if (!isNullOrEmpty(iv)) {
|
359
|
+
task.setIvHex(Optional.of(iv));
|
360
|
+
}
|
361
|
+
break;
|
362
|
+
default:
|
363
|
+
throw new ConfigException(String.format("Key type [%s] is not supported", task.getKeyType().toString()));
|
186
364
|
}
|
187
365
|
|
188
|
-
//
|
366
|
+
// Validate Cipher
|
189
367
|
try {
|
190
368
|
getCipher(Cipher.ENCRYPT_MODE, task);
|
191
369
|
}
|
192
|
-
catch (Exception
|
193
|
-
throw new ConfigException(
|
370
|
+
catch (Exception e) {
|
371
|
+
throw new ConfigException(e);
|
194
372
|
}
|
195
373
|
|
196
374
|
// validate column_names
|
197
375
|
for (String name : task.getColumnNames()) {
|
198
|
-
|
376
|
+
schema.lookupColumn(name);
|
199
377
|
}
|
200
|
-
|
201
|
-
control.run(task.dump(), inputSchema);
|
202
378
|
}
|
203
379
|
|
204
380
|
private Cipher getCipher(int mode, PluginTask task)
|
@@ -206,7 +382,7 @@ public class EncryptFilterPlugin
|
|
206
382
|
{
|
207
383
|
Algorithm algo = task.getAlgorithm();
|
208
384
|
|
209
|
-
byte[] keyData = BaseEncoding.base16().decode(task.getKeyHex());
|
385
|
+
byte[] keyData = BaseEncoding.base16().decode(task.getKeyHex().get());
|
210
386
|
SecretKeySpec key = new SecretKeySpec(keyData, algo.getJavaKeySpecName());
|
211
387
|
|
212
388
|
if (algo.useIv()) {
|
@@ -1,5 +1,7 @@
|
|
1
1
|
package org.embulk.filter.encrypt;
|
2
2
|
|
3
|
+
import com.amazonaws.services.s3.AmazonS3;
|
4
|
+
import com.amazonaws.services.s3.model.Region;
|
3
5
|
import com.google.common.collect.ImmutableList;
|
4
6
|
import org.embulk.EmbulkTestRuntime;
|
5
7
|
import org.embulk.config.ConfigException;
|
@@ -31,7 +33,9 @@ import java.security.InvalidAlgorithmParameterException;
|
|
31
33
|
import java.security.InvalidKeyException;
|
32
34
|
import java.security.NoSuchAlgorithmException;
|
33
35
|
import java.util.Arrays;
|
36
|
+
import java.util.HashMap;
|
34
37
|
import java.util.List;
|
38
|
+
import java.util.Map;
|
35
39
|
|
36
40
|
import static com.google.common.base.Charsets.UTF_8;
|
37
41
|
import static com.google.common.io.BaseEncoding.base16;
|
@@ -53,6 +57,9 @@ import static org.embulk.spi.PageTestUtils.buildPage;
|
|
53
57
|
import static org.junit.Assert.assertEquals;
|
54
58
|
import static org.junit.Assert.assertNotEquals;
|
55
59
|
import static org.junit.Assert.fail;
|
60
|
+
import static org.mockito.ArgumentMatchers.any;
|
61
|
+
import static org.mockito.Mockito.doReturn;
|
62
|
+
import static org.mockito.Mockito.spy;
|
56
63
|
|
57
64
|
public class TestEncryptFilterPlugin
|
58
65
|
{
|
@@ -73,6 +80,22 @@ public class TestEncryptFilterPlugin
|
|
73
80
|
.set("iv_hex", "2A1D6BD59D2DB50A59364BAD3B9B6544");
|
74
81
|
}
|
75
82
|
|
83
|
+
private ConfigSource s3Config()
|
84
|
+
{
|
85
|
+
ConfigSource awsParams = runtime.getExec().newConfigSource()
|
86
|
+
.set("region", "us-east-2")
|
87
|
+
.set("access_key", "a_access_key")
|
88
|
+
.set("secret_key", "a_secret_key")
|
89
|
+
.set("bucket", "a_bucket")
|
90
|
+
.set("path", "a_path");
|
91
|
+
|
92
|
+
return runtime.getExec().newConfigSource()
|
93
|
+
.set("type", "encrypt")
|
94
|
+
.set("algorithm", "AES-256-CBC")
|
95
|
+
.set("key_type", "s3")
|
96
|
+
.setNested("aws_params", awsParams);
|
97
|
+
}
|
98
|
+
|
76
99
|
@Before
|
77
100
|
public void setup()
|
78
101
|
{
|
@@ -275,13 +298,13 @@ public class TestEncryptFilterPlugin
|
|
275
298
|
plaintext,
|
276
299
|
decrypt(ciphertext,
|
277
300
|
task.getAlgorithm(),
|
278
|
-
task.getKeyHex(),
|
301
|
+
task.getKeyHex().get(),
|
279
302
|
task.getIvHex().orNull(),
|
280
303
|
BASE64));
|
281
304
|
try {
|
282
305
|
decrypt(ciphertext,
|
283
306
|
task.getAlgorithm(),
|
284
|
-
task.getKeyHex(),
|
307
|
+
task.getKeyHex().get(),
|
285
308
|
task.getIvHex().orNull(),
|
286
309
|
HEX);
|
287
310
|
}
|
@@ -311,13 +334,13 @@ public class TestEncryptFilterPlugin
|
|
311
334
|
plaintext,
|
312
335
|
decrypt(ciphertext,
|
313
336
|
task.getAlgorithm(),
|
314
|
-
task.getKeyHex(),
|
337
|
+
task.getKeyHex().get(),
|
315
338
|
task.getIvHex().orNull(),
|
316
339
|
HEX));
|
317
340
|
try {
|
318
341
|
decrypt(ciphertext,
|
319
342
|
task.getAlgorithm(),
|
320
|
-
task.getKeyHex(),
|
343
|
+
task.getKeyHex().get(),
|
321
344
|
task.getIvHex().orNull(),
|
322
345
|
BASE64);
|
323
346
|
}
|
@@ -386,6 +409,260 @@ public class TestEncryptFilterPlugin
|
|
386
409
|
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
387
410
|
}
|
388
411
|
|
412
|
+
@Test()
|
413
|
+
public void encrypt_with_required_IV_algorithm_for_s3() throws Exception
|
414
|
+
{
|
415
|
+
ConfigSource config = s3Config()
|
416
|
+
.set("column_names", ImmutableList.of("should_be_encrypted"));
|
417
|
+
Schema schema = Schema.builder()
|
418
|
+
.add("should_be_encrypted", Types.STRING)
|
419
|
+
.build();
|
420
|
+
|
421
|
+
plugin = spy(plugin);
|
422
|
+
Map<String, String> keys = new HashMap<>();
|
423
|
+
keys.put("key_hex", "D0867C9310D061F17ACD11EB30DE68265DCB79849BE5FB2BE157919D19BF2F42");
|
424
|
+
keys.put("iv_hex", "2A1D6BD59D2DB50A59364BAD3B9B6544");
|
425
|
+
doReturn(keys).when(plugin).retrieveKey(any(String.class), any(String.class), any(AmazonS3.class));
|
426
|
+
|
427
|
+
List rawRecord = ImmutableList.of("My super secret");
|
428
|
+
List filteredRecord = applyFilter(config, schema, rawRecord);
|
429
|
+
|
430
|
+
String plaintext = (String) rawRecord.get(0);
|
431
|
+
String ciphertext = (String) filteredRecord.get(0);
|
432
|
+
|
433
|
+
assertNotEquals(plaintext, ciphertext);
|
434
|
+
config.set("key_hex", keys.get("key_hex"));
|
435
|
+
config.set("iv_hex", keys.get("iv_hex"));
|
436
|
+
assertEquals(plaintext, decrypt(ciphertext, AES_256_CBC, config));
|
437
|
+
}
|
438
|
+
|
439
|
+
@Test()
|
440
|
+
public void encrypt_with_not_required_IV_algorithm_for_s3() throws Exception
|
441
|
+
{
|
442
|
+
ConfigSource config = s3Config()
|
443
|
+
.set("column_names", ImmutableList.of("should_be_encrypted"))
|
444
|
+
.set("algorithm", "AES-256-ECB");
|
445
|
+
Schema schema = Schema.builder()
|
446
|
+
.add("should_be_encrypted", Types.STRING)
|
447
|
+
.build();
|
448
|
+
|
449
|
+
plugin = spy(plugin);
|
450
|
+
Map<String, String> keys = new HashMap<>();
|
451
|
+
keys.put("key_hex", "D0867C9310D061F17ACD11EB30DE68265DCB79849BE5FB2BE157919D19BF2F42");
|
452
|
+
keys.put("iv_hex", "2A1D6BD59D2DB50A59364BAD3B9B6544");
|
453
|
+
doReturn(keys).when(plugin).retrieveKey(any(String.class), any(String.class), any(AmazonS3.class));
|
454
|
+
|
455
|
+
List rawRecord = ImmutableList.of("My super secret");
|
456
|
+
List filteredRecord = applyFilter(config, schema, rawRecord);
|
457
|
+
|
458
|
+
String plaintext = (String) rawRecord.get(0);
|
459
|
+
String ciphertext = (String) filteredRecord.get(0);
|
460
|
+
|
461
|
+
assertNotEquals(plaintext, ciphertext);
|
462
|
+
config.set("key_hex", keys.get("key_hex"));
|
463
|
+
assertEquals(plaintext, decrypt(ciphertext, AES_256_ECB, config));
|
464
|
+
}
|
465
|
+
|
466
|
+
@Test()
|
467
|
+
public void encrypt_with_not_required_IV_algorithm_for_s3_should_ignore_IV() throws Exception
|
468
|
+
{
|
469
|
+
ConfigSource config = s3Config()
|
470
|
+
.set("column_names", ImmutableList.of("should_be_encrypted"))
|
471
|
+
.set("algorithm", "AES-256-ECB");
|
472
|
+
Schema schema = Schema.builder()
|
473
|
+
.add("should_be_encrypted", Types.STRING)
|
474
|
+
.build();
|
475
|
+
|
476
|
+
plugin = spy(plugin);
|
477
|
+
Map<String, String> keys = new HashMap<>();
|
478
|
+
keys.put("key_hex", "D0867C9310D061F17ACD11EB30DE68265DCB79849BE5FB2BE157919D19BF2F42");
|
479
|
+
doReturn(keys).when(plugin).retrieveKey(any(String.class), any(String.class), any(AmazonS3.class));
|
480
|
+
|
481
|
+
List rawRecord = ImmutableList.of("My super secret");
|
482
|
+
List filteredRecord = applyFilter(config, schema, rawRecord);
|
483
|
+
|
484
|
+
String plaintext = (String) rawRecord.get(0);
|
485
|
+
String ciphertext = (String) filteredRecord.get(0);
|
486
|
+
|
487
|
+
assertNotEquals(plaintext, ciphertext);
|
488
|
+
config.set("key_hex", keys.get("key_hex"));
|
489
|
+
assertEquals(plaintext, decrypt(ciphertext, AES_256_ECB, config));
|
490
|
+
}
|
491
|
+
|
492
|
+
@Test
|
493
|
+
public void absence_of_aws_params_for_s3_should_yell_a_meaningful_ConfigException()
|
494
|
+
{
|
495
|
+
ConfigSource config = s3Config()
|
496
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"))
|
497
|
+
.remove("aws_params");
|
498
|
+
Schema schema = Schema.builder()
|
499
|
+
.add("attempt_to_encrypt", Types.STRING)
|
500
|
+
.build();
|
501
|
+
expectedException.expect(ConfigException.class);
|
502
|
+
expectedException.expectMessage("AWS Params");
|
503
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
504
|
+
}
|
505
|
+
|
506
|
+
@Test
|
507
|
+
public void absence_of_aws_region_param_for_s3_should_yell_a_meaningful_ConfigException()
|
508
|
+
{
|
509
|
+
ConfigSource config = s3Config()
|
510
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
511
|
+
|
512
|
+
config.getNested("aws_params")
|
513
|
+
.remove("region");
|
514
|
+
Schema schema = Schema.builder()
|
515
|
+
.add("attempt_to_encrypt", Types.STRING)
|
516
|
+
.build();
|
517
|
+
expectedException.expect(ConfigException.class);
|
518
|
+
expectedException.expectMessage("region");
|
519
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
520
|
+
}
|
521
|
+
|
522
|
+
@Test
|
523
|
+
public void absence_of_aws_path_param_for_s3_should_yell_a_meaningful_ConfigException()
|
524
|
+
{
|
525
|
+
ConfigSource config = s3Config()
|
526
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
527
|
+
|
528
|
+
config.getNested("aws_params")
|
529
|
+
.remove("path");
|
530
|
+
Schema schema = Schema.builder()
|
531
|
+
.add("attempt_to_encrypt", Types.STRING)
|
532
|
+
.build();
|
533
|
+
expectedException.expect(ConfigException.class);
|
534
|
+
expectedException.expectMessage("path");
|
535
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
536
|
+
}
|
537
|
+
|
538
|
+
@Test
|
539
|
+
public void absence_of_aws_bucket_param_for_s3_should_yell_a_meaningful_ConfigException()
|
540
|
+
{
|
541
|
+
ConfigSource config = s3Config()
|
542
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
543
|
+
|
544
|
+
config.getNested("aws_params")
|
545
|
+
.remove("bucket");
|
546
|
+
Schema schema = Schema.builder()
|
547
|
+
.add("attempt_to_encrypt", Types.STRING)
|
548
|
+
.build();
|
549
|
+
expectedException.expect(ConfigException.class);
|
550
|
+
expectedException.expectMessage("bucket");
|
551
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
552
|
+
}
|
553
|
+
|
554
|
+
@Test
|
555
|
+
public void absence_of_aws_access_key_param_for_s3_should_yell_a_meaningful_ConfigException()
|
556
|
+
{
|
557
|
+
ConfigSource config = s3Config()
|
558
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
559
|
+
|
560
|
+
config.getNested("aws_params")
|
561
|
+
.remove("access_key");
|
562
|
+
Schema schema = Schema.builder()
|
563
|
+
.add("attempt_to_encrypt", Types.STRING)
|
564
|
+
.build();
|
565
|
+
expectedException.expect(ConfigException.class);
|
566
|
+
expectedException.expectMessage("access_key");
|
567
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
568
|
+
}
|
569
|
+
|
570
|
+
@Test
|
571
|
+
public void absence_of_aws_secret_key_param_for_s3_should_yell_a_meaningful_ConfigException()
|
572
|
+
{
|
573
|
+
ConfigSource config = s3Config()
|
574
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
575
|
+
|
576
|
+
config.getNested("aws_params")
|
577
|
+
.remove("secret_key");
|
578
|
+
Schema schema = Schema.builder()
|
579
|
+
.add("attempt_to_encrypt", Types.STRING)
|
580
|
+
.build();
|
581
|
+
expectedException.expect(ConfigException.class);
|
582
|
+
expectedException.expectMessage("secret_key");
|
583
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
584
|
+
}
|
585
|
+
|
586
|
+
@Test
|
587
|
+
public void absence_of_encryption_key_for_s3_should_yell_a_meaningful_ConfigException()
|
588
|
+
{
|
589
|
+
ConfigSource config = s3Config()
|
590
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
591
|
+
Schema schema = Schema.builder()
|
592
|
+
.add("attempt_to_encrypt", Types.STRING)
|
593
|
+
.build();
|
594
|
+
|
595
|
+
plugin = spy(plugin);
|
596
|
+
Map<String, String> keys = new HashMap<>();
|
597
|
+
doReturn(keys).when(plugin).retrieveKey(any(String.class), any(String.class), any(AmazonS3.class));
|
598
|
+
|
599
|
+
expectedException.expect(ConfigException.class);
|
600
|
+
expectedException.expectMessage("key_hex");
|
601
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
602
|
+
}
|
603
|
+
|
604
|
+
@Test
|
605
|
+
public void absence_of_iv_on_a_required_iv_algorithm_for_s3_should_yell_a_meaningful_ConfigException()
|
606
|
+
{
|
607
|
+
ConfigSource config = s3Config()
|
608
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
609
|
+
Schema schema = Schema.builder()
|
610
|
+
.add("attempt_to_encrypt", Types.STRING)
|
611
|
+
.build();
|
612
|
+
|
613
|
+
plugin = spy(plugin);
|
614
|
+
Map<String, String> keys = new HashMap<>();
|
615
|
+
keys.put("key_hex", "D0867C9310D061F17ACD11EB30DE68265DCB79849BE5FB2BE157919D19BF2F42");
|
616
|
+
doReturn(keys).when(plugin).retrieveKey(any(String.class), any(String.class), any(AmazonS3.class));
|
617
|
+
|
618
|
+
expectedException.expect(ConfigException.class);
|
619
|
+
expectedException.expectMessage("iv_hex");
|
620
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
621
|
+
}
|
622
|
+
|
623
|
+
@Test
|
624
|
+
// Previously, this will throw
|
625
|
+
public void presence_of_iv_on_a_non_iv_algorithm_for_s3_should_be_silent()
|
626
|
+
{
|
627
|
+
ConfigSource config = s3Config()
|
628
|
+
.set("algorithm", "AES-128-ECB")
|
629
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
630
|
+
Schema schema = Schema.builder()
|
631
|
+
.add("attempt_to_encrypt", Types.STRING)
|
632
|
+
.build();
|
633
|
+
|
634
|
+
plugin = spy(plugin);
|
635
|
+
Map<String, String> keys = new HashMap<>();
|
636
|
+
keys.put("key_hex", "D0867C9310D061F17ACD11EB30DE68265DCB79849BE5FB2BE157919D19BF2F42");
|
637
|
+
doReturn(keys).when(plugin).retrieveKey(any(String.class), any(String.class), any(AmazonS3.class));
|
638
|
+
|
639
|
+
applyFilter(config, schema, ImmutableList.of("Try to encrypt me buddy!"));
|
640
|
+
}
|
641
|
+
|
642
|
+
@Test
|
643
|
+
public void s3_client_should_reflect_region_config()
|
644
|
+
{
|
645
|
+
ConfigSource configSource = s3Config()
|
646
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
647
|
+
|
648
|
+
AmazonS3 s3Client = plugin.newS3Client(configSource.loadConfig(EncryptFilterPlugin.PluginTask.class).getAWSParams().get());
|
649
|
+
|
650
|
+
assertEquals(s3Client.getRegion(), Region.US_East_2);
|
651
|
+
}
|
652
|
+
|
653
|
+
@Test
|
654
|
+
public void s3_client_invalid_region_should_yell_a_meaningful_ConfigException()
|
655
|
+
{
|
656
|
+
ConfigSource configSource = s3Config()
|
657
|
+
.set("column_names", ImmutableList.of("attempt_to_encrypt"));
|
658
|
+
configSource.getNested("aws_params")
|
659
|
+
.set("region", "invalid_region");
|
660
|
+
|
661
|
+
expectedException.expect(ConfigException.class);
|
662
|
+
expectedException.expectMessage("Unable to find a region via the region provider chain");
|
663
|
+
plugin.newS3Client(configSource.loadConfig(EncryptFilterPlugin.PluginTask.class).getAWSParams().get());
|
664
|
+
}
|
665
|
+
|
389
666
|
/** Apply the filter to a single record */
|
390
667
|
private PageReader applyFilter(ConfigSource config, final Schema schema, final Object... rawRecord)
|
391
668
|
{
|
@@ -393,7 +670,6 @@ public class TestEncryptFilterPlugin
|
|
393
670
|
throw new UnsupportedOperationException("applyFilter() only supports a single record, " +
|
394
671
|
"number of supplied values exceed the schema column size.");
|
395
672
|
}
|
396
|
-
final PluginTask task = config.loadConfig(PluginTask.class);
|
397
673
|
|
398
674
|
final MockPageOutput filteredOutput = new MockPageOutput();
|
399
675
|
|
@@ -402,7 +678,7 @@ public class TestEncryptFilterPlugin
|
|
402
678
|
@Override
|
403
679
|
public void run(TaskSource taskSource, Schema outputSchema)
|
404
680
|
{
|
405
|
-
PageOutput originalOutput = plugin.open(
|
681
|
+
PageOutput originalOutput = plugin.open(taskSource, schema, outputSchema, filteredOutput);
|
406
682
|
originalOutput.add(buildPage(runtime.getBufferAllocator(), schema, rawRecord).get(0));
|
407
683
|
originalOutput.finish();
|
408
684
|
originalOutput.close();
|
@@ -502,7 +778,7 @@ public class TestEncryptFilterPlugin
|
|
502
778
|
return decrypt(
|
503
779
|
ciphertext,
|
504
780
|
task.getAlgorithm(),
|
505
|
-
task.getKeyHex(),
|
781
|
+
task.getKeyHex().get(),
|
506
782
|
task.getIvHex().orNull(),
|
507
783
|
task.getOutputEncoding());
|
508
784
|
}
|
@@ -520,7 +796,7 @@ public class TestEncryptFilterPlugin
|
|
520
796
|
return decrypt(
|
521
797
|
ciphertext,
|
522
798
|
algo,
|
523
|
-
task.getKeyHex(),
|
799
|
+
task.getKeyHex().get(),
|
524
800
|
task.getIvHex().orNull(),
|
525
801
|
task.getOutputEncoding());
|
526
802
|
}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: embulk-filter-encrypt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sadayuki Furuhashi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-08-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,7 +62,20 @@ files:
|
|
62
62
|
- lib/embulk/filter/encrypt.rb
|
63
63
|
- src/main/java/org/embulk/filter/encrypt/EncryptFilterPlugin.java
|
64
64
|
- src/test/java/org/embulk/filter/encrypt/TestEncryptFilterPlugin.java
|
65
|
-
- classpath/
|
65
|
+
- classpath/aws-java-sdk-core-1.11.253.jar
|
66
|
+
- classpath/aws-java-sdk-kms-1.11.253.jar
|
67
|
+
- classpath/aws-java-sdk-s3-1.11.253.jar
|
68
|
+
- classpath/commons-codec-1.9.jar
|
69
|
+
- classpath/commons-logging-1.2.jar
|
70
|
+
- classpath/embulk-filter-encrypt-0.2.1.jar
|
71
|
+
- classpath/httpclient-4.5.2.jar
|
72
|
+
- classpath/httpcore-4.4.4.jar
|
73
|
+
- classpath/ion-java-1.0.2.jar
|
74
|
+
- classpath/jackson-annotations-2.6.0.jar
|
75
|
+
- classpath/jackson-core-2.6.7.jar
|
76
|
+
- classpath/jackson-databind-2.6.7.1.jar
|
77
|
+
- classpath/jackson-dataformat-cbor-2.6.7.jar
|
78
|
+
- classpath/jmespath-java-1.11.253.jar
|
66
79
|
homepage: https://github.com/embulk/embulk-filter-encrypt
|
67
80
|
licenses:
|
68
81
|
- Apache 2.0
|