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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6de855d8ac0f1c315220c4907fe6eb8d7bd40043
4
- data.tar.gz: b21751d55746b058abfef300e3927187fb8f6185
3
+ metadata.gz: d7278dc0b04a51ad156ea94582943fd934bd435d
4
+ data.tar.gz: 30091df627949d1342c7ac7389e26f353ebccb5f
5
5
  SHA512:
6
- metadata.gz: 97fbc5a6e919eb2b053fd2b4862eebc4edd1e4b1a2245c8274fe0780fcab557610a2c8aa8feb3c67cd13ea04bba2ed06b69416b4d0ea3ce9e825366c2b6e2b1c
7
- data.tar.gz: 80f240d5de8da536ad01c5a998ba273740cc5aa63c5b102dcb83d816666921e3c171204ebaa56ae8e102f434fe0214499872240fcc4f0ee241170855a8a8c738
6
+ metadata.gz: 3dfa97e8c4c1adb2cb28c67eaa07b405e5fce86643245b9f36a4458693235f73406b1353943b326bcce7b6b1a3db91d1d937e6fbcbfa33fe6199d5ca31c9874a
7
+ data.tar.gz: 0f0408f79ec21e587e2843225c40756da31322d4f90d5992fc7c204e3b63030f920229bd0d5de8ccb2ed948ff34620f6cc0e28a85f29f3e248da59682a8bf6ad
data/ChangeLog CHANGED
@@ -1,3 +1,7 @@
1
+ Release 0.2.1 - 2018-08-15
2
+
3
+ * Add `key_type` config, supports "inline" and "s3".
4
+
1
5
  Release 0.2.0 - 2018-06-13
2
6
 
3
7
  * Add `output_encoding` config, supports "base64" and "hex".
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
- - **key_hex**: encryption key (string, required)
26
- - **iv_hex**: encryption initialization vector (string, required if mode of the algorithm is CBC)
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
  ```
@@ -13,7 +13,7 @@ configurations {
13
13
  provided
14
14
  }
15
15
 
16
- version = "0.2.0"
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
- // compile "YOUR_JAR_DEPENDENCY_GROUP:YOUR_JAR_DEPENDENCY_MODULE:YOUR_JAR_DEPENDENCY_VERSION"
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 static enum Algorithm
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
- private Algorithm(String javaName, String javaKeySpecName, int keyLength, boolean useIv, String... displayNames)
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
- public String getKeyHex();
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
- if (task.getAlgorithm().useIv() && !task.getIvHex().isPresent()) {
182
- throw new ConfigException("Algorithm '" + task.getAlgorithm() + "' requires initialization vector. Please generate one and set it to iv_hex option.");
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
- else if (!task.getAlgorithm().useIv() && task.getIvHex().isPresent()) {
185
- log.warn("Algorithm '" + task.getAlgorithm() + "' doesn't use initialization vector. Please remove iv_hex option.");
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
- // validate configuration
366
+ // Validate Cipher
189
367
  try {
190
368
  getCipher(Cipher.ENCRYPT_MODE, task);
191
369
  }
192
- catch (Exception ex) {
193
- throw new ConfigException(ex);
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
- inputSchema.lookupColumn(name);
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(task.dump(), schema, outputSchema, filteredOutput);
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.0
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-06-14 00:00:00.000000000 Z
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/embulk-filter-encrypt-0.2.0.jar
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