embulk-filter-encrypt 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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