|
@@ -18,19 +18,23 @@
|
|
|
|
|
|
package org.apache.hadoop.fs.s3a;
|
|
|
|
|
|
-import static org.apache.hadoop.fs.contract.ContractTestUtils.dataset;
|
|
|
-import static org.apache.hadoop.fs.contract.ContractTestUtils.rm;
|
|
|
-import static org.apache.hadoop.fs.s3a.S3ATestUtils.skipIfEncryptionTestsDisabled;
|
|
|
+import java.io.IOException;
|
|
|
+import java.util.concurrent.Callable;
|
|
|
+
|
|
|
+import org.junit.Rule;
|
|
|
+import org.junit.Test;
|
|
|
+import org.junit.rules.ExpectedException;
|
|
|
|
|
|
import org.apache.hadoop.conf.Configuration;
|
|
|
import org.apache.hadoop.fs.FileSystem;
|
|
|
import org.apache.hadoop.fs.Path;
|
|
|
import org.apache.hadoop.fs.contract.ContractTestUtils;
|
|
|
import org.apache.hadoop.fs.contract.s3a.S3AContract;
|
|
|
-import org.hamcrest.core.StringContains;
|
|
|
-import org.junit.Rule;
|
|
|
-import org.junit.Test;
|
|
|
-import org.junit.rules.ExpectedException;
|
|
|
+
|
|
|
+import static org.apache.hadoop.fs.contract.ContractTestUtils.dataset;
|
|
|
+import static org.apache.hadoop.fs.contract.ContractTestUtils.rm;
|
|
|
+import static org.apache.hadoop.fs.s3a.S3ATestUtils.skipIfEncryptionTestsDisabled;
|
|
|
+import static org.apache.hadoop.test.LambdaTestUtils.intercept;
|
|
|
|
|
|
/**
|
|
|
* Concrete class that extends {@link AbstractTestS3AEncryption}
|
|
@@ -58,39 +62,350 @@ public class ITestS3AEncryptionSSEC extends AbstractTestS3AEncryption {
|
|
|
* This will create and write to a file using encryption key A, then attempt
|
|
|
* to read from it again with encryption key B. This will not work as it
|
|
|
* cannot decrypt the file.
|
|
|
+ *
|
|
|
+ * This is expected AWS S3 SSE-C behavior.
|
|
|
+ *
|
|
|
* @throws Exception
|
|
|
*/
|
|
|
@Test
|
|
|
public void testCreateFileAndReadWithDifferentEncryptionKey() throws
|
|
|
- Exception {
|
|
|
- expectedException.expect(java.nio.file.AccessDeniedException.class);
|
|
|
- expectedException.expectMessage(StringContains
|
|
|
- .containsString("Service: Amazon S3; Status Code: 403;"));
|
|
|
-
|
|
|
- Path path = null;
|
|
|
- try {
|
|
|
- int len = 2048;
|
|
|
- skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
- describe("Create an encrypted file of size " + len);
|
|
|
- String src = createFilename(len);
|
|
|
- path = writeThenReadFile(src, len);
|
|
|
-
|
|
|
- Configuration conf = this.createConfiguration();
|
|
|
- conf.set(Constants.SERVER_SIDE_ENCRYPTION_KEY,
|
|
|
- "kX7SdwVc/1VXJr76kfKnkQ3ONYhxianyL2+C3rPVT9s=");
|
|
|
-
|
|
|
- S3AContract contract = (S3AContract) createContract(conf);
|
|
|
- contract.init();
|
|
|
- //skip tests if they aren't enabled
|
|
|
- assumeEnabled();
|
|
|
- //extract the test FS
|
|
|
- FileSystem fileSystem = contract.getTestFileSystem();
|
|
|
- byte[] data = dataset(len, 'a', 'z');
|
|
|
- ContractTestUtils.verifyFileContents(fileSystem, path, data);
|
|
|
- } catch(Exception e) {
|
|
|
- rm(getFileSystem(), path, false, false);
|
|
|
- throw e;
|
|
|
- }
|
|
|
+ Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ final Path[] path = new Path[1];
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Service: Amazon S3; Status Code: 403;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+ int len = 2048;
|
|
|
+ describe("Create an encrypted file of size " + len);
|
|
|
+ String src = createFilename(len);
|
|
|
+ path[0] = writeThenReadFile(src, len);
|
|
|
+
|
|
|
+ //extract the test FS
|
|
|
+ FileSystem fileSystem = createNewFileSystemWithSSECKey(
|
|
|
+ "kX7SdwVc/1VXJr76kfKnkQ3ONYhxianyL2+C3rPVT9s=");
|
|
|
+ byte[] data = dataset(len, 'a', 'z');
|
|
|
+ ContractTestUtils.verifyFileContents(fileSystem, path[0], data);
|
|
|
+ throw new Exception("Fail");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * While each object has it's own key and should be distinct, this verifies
|
|
|
+ * that hadoop treats object keys as a filesystem path. So if a top level
|
|
|
+ * dir is encrypted with keyA, a sublevel dir cannot be accessed with a
|
|
|
+ * different keyB.
|
|
|
+ *
|
|
|
+ * This is expected AWS S3 SSE-C behavior.
|
|
|
+ *
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testCreateSubdirWithDifferentKey() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ final Path[] path = new Path[1];
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Service: Amazon S3; Status Code: 403;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+
|
|
|
+ path[0] = S3ATestUtils.createTestPath(
|
|
|
+ new Path(createFilename("dir/"))
|
|
|
+ );
|
|
|
+ Path nestedDirectory = S3ATestUtils.createTestPath(
|
|
|
+ new Path(createFilename("dir/nestedDir/"))
|
|
|
+ );
|
|
|
+ FileSystem fsKeyB = createNewFileSystemWithSSECKey(
|
|
|
+ "G61nz31Q7+zpjJWbakxfTOZW4VS0UmQWAq2YXhcTXoo=");
|
|
|
+ getFileSystem().mkdirs(path[0]);
|
|
|
+ fsKeyB.mkdirs(nestedDirectory);
|
|
|
+
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ rm(getFileSystem(), path[0], true, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Ensures a file can't be created with keyA and then renamed with a different
|
|
|
+ * key.
|
|
|
+ *
|
|
|
+ * This is expected AWS S3 SSE-C behavior.
|
|
|
+ *
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testCreateFileThenMoveWithDifferentSSECKey() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ final Path[] path = new Path[1];
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Service: Amazon S3; Status Code: 403;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+
|
|
|
+ int len = 2048;
|
|
|
+ String src = createFilename(len);
|
|
|
+ path[0] = writeThenReadFile(src, len);
|
|
|
+
|
|
|
+ FileSystem fsKeyB = createNewFileSystemWithSSECKey(
|
|
|
+ "NTx0dUPrxoo9+LbNiT/gqf3z9jILqL6ilismFmJO50U=");
|
|
|
+ fsKeyB.rename(path[0],
|
|
|
+ new Path(createFilename("different-path.txt")));
|
|
|
+
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * General test to make sure move works with SSE-C with the same key, unlike
|
|
|
+ * with multiple keys.
|
|
|
+ *
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testRenameFile() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ String src = createFilename("original-path.txt");
|
|
|
+ Path path = writeThenReadFile(src, 2048);
|
|
|
+ Path newPath = path(createFilename("different-path.txt"));
|
|
|
+ getFileSystem().rename(path, newPath);
|
|
|
+ byte[] data = dataset(2048, 'a', 'z');
|
|
|
+ ContractTestUtils.verifyFileContents(getFileSystem(), newPath, data);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * It is possible to list the contents of a directory up to the actual
|
|
|
+ * end of the nested directories. This is due to how S3A mocks the
|
|
|
+ * directories and how prefixes work in S3.
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testListEncryptedDir() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ Path nestedDirectory = S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))
|
|
|
+ );
|
|
|
+ assertTrue(getFileSystem().mkdirs(nestedDirectory));
|
|
|
+
|
|
|
+ final FileSystem fsKeyB = createNewFileSystemWithSSECKey(
|
|
|
+ "msdo3VvvZznp66Gth58a91Hxe/UpExMkwU9BHkIjfW8=");
|
|
|
+
|
|
|
+ fsKeyB.listFiles(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/"))
|
|
|
+ ), true);
|
|
|
+ fsKeyB.listFiles(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/"))
|
|
|
+ ), true);
|
|
|
+
|
|
|
+ //Until this point, no exception is thrown about access
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Service: Amazon S3; Status Code: 403;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+ fsKeyB.listFiles(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))
|
|
|
+ ), false);
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ Configuration conf = this.createConfiguration();
|
|
|
+ conf.unset(Constants.SERVER_SIDE_ENCRYPTION_ALGORITHM);
|
|
|
+ conf.unset(Constants.SERVER_SIDE_ENCRYPTION_KEY);
|
|
|
+
|
|
|
+ S3AContract contract = (S3AContract) createContract(conf);
|
|
|
+ contract.init();
|
|
|
+ final FileSystem unencryptedFileSystem = contract.getTestFileSystem();
|
|
|
+
|
|
|
+ //unencrypted can access until the final directory
|
|
|
+ unencryptedFileSystem.listFiles(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/"))
|
|
|
+ ), true);
|
|
|
+ unencryptedFileSystem.listFiles(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/"))
|
|
|
+ ), true);
|
|
|
+ intercept(org.apache.hadoop.fs.s3a.AWSS3IOException.class,
|
|
|
+ "Bad Request (Service: Amazon S3; Status Code: 400; Error" +
|
|
|
+ " Code: 400 Bad Request;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+ unencryptedFileSystem.listFiles(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))
|
|
|
+ ), false);
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ rm(getFileSystem(), path(createFilename("/")), true, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Much like the above list encrypted directory test, you cannot get the
|
|
|
+ * metadata of an object without the correct encryption key.
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testListStatusEncryptedDir() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ Path nestedDirectory = S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))
|
|
|
+ );
|
|
|
+ assertTrue(getFileSystem().mkdirs(nestedDirectory));
|
|
|
+
|
|
|
+ final FileSystem fsKeyB = createNewFileSystemWithSSECKey(
|
|
|
+ "msdo3VvvZznp66Gth58a91Hxe/UpExMkwU9BHkIjfW8=");
|
|
|
+
|
|
|
+ fsKeyB.listStatus(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/"))));
|
|
|
+ fsKeyB.listStatus(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/"))));
|
|
|
+
|
|
|
+ //Until this point, no exception is thrown about access
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Service: Amazon S3; Status Code: 403;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+ fsKeyB.listStatus(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))));
|
|
|
+
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ //Now try it with an unencrypted filesystem.
|
|
|
+ Configuration conf = this.createConfiguration();
|
|
|
+ conf.unset(Constants.SERVER_SIDE_ENCRYPTION_ALGORITHM);
|
|
|
+ conf.unset(Constants.SERVER_SIDE_ENCRYPTION_KEY);
|
|
|
+
|
|
|
+ S3AContract contract = (S3AContract) createContract(conf);
|
|
|
+ contract.init();
|
|
|
+ final FileSystem unencryptedFileSystem = contract.getTestFileSystem();
|
|
|
+
|
|
|
+ //unencrypted can access until the final directory
|
|
|
+ unencryptedFileSystem.listStatus(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/"))));
|
|
|
+ unencryptedFileSystem.listStatus(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/"))));
|
|
|
+ intercept(org.apache.hadoop.fs.s3a.AWSS3IOException.class,
|
|
|
+ "Bad Request (Service: Amazon S3; Status Code: 400; Error Code: 400" +
|
|
|
+ " Bad Request;", new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+
|
|
|
+ unencryptedFileSystem.listStatus(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))));
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+ rm(getFileSystem(), path(createFilename("/")), true, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Much like trying to access a encrypted directory, an encrypted file cannot
|
|
|
+ * have its metadata read, since both are technically an object.
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testListStatusEncryptedFile() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ Path nestedDirectory = S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))
|
|
|
+ );
|
|
|
+ assertTrue(getFileSystem().mkdirs(nestedDirectory));
|
|
|
+
|
|
|
+ String src = createFilename("/a/b/c/fileToStat.txt");
|
|
|
+ final Path fileToStat = writeThenReadFile(src, 2048);
|
|
|
+
|
|
|
+ final FileSystem fsKeyB = createNewFileSystemWithSSECKey(
|
|
|
+ "msdo3VvvZznp66Gth58a91Hxe/UpExMkwU9BHkIjfW8=");
|
|
|
+
|
|
|
+ //Until this point, no exception is thrown about access
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Service: Amazon S3; Status Code: 403;",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+ fsKeyB.listStatus(S3ATestUtils.createTestPath(fileToStat));
|
|
|
+
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }});
|
|
|
+ rm(getFileSystem(), path(createFilename("/")), true, false);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * It is possible to delete directories without the proper encryption key and
|
|
|
+ * the hierarchy above it.
|
|
|
+ *
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testDeleteEncryptedObjectWithDifferentKey() throws Exception {
|
|
|
+ assumeEnabled();
|
|
|
+ skipIfEncryptionTestsDisabled(getConfiguration());
|
|
|
+
|
|
|
+ Path nestedDirectory = S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))
|
|
|
+ );
|
|
|
+ assertTrue(getFileSystem().mkdirs(nestedDirectory));
|
|
|
+ String src = createFilename("/a/b/c/filetobedeleted.txt");
|
|
|
+ final Path fileToDelete = writeThenReadFile(src, 2048);
|
|
|
+
|
|
|
+ final FileSystem fsKeyB = createNewFileSystemWithSSECKey(
|
|
|
+ "msdo3VvvZznp66Gth58a91Hxe/UpExMkwU9BHkIjfW8=");
|
|
|
+ intercept(java.nio.file.AccessDeniedException.class,
|
|
|
+ "Forbidden (Service: Amazon S3; Status Code: 403; Error Code: " +
|
|
|
+ "403 Forbidden",
|
|
|
+ new Callable<Void>() {
|
|
|
+ @Override
|
|
|
+ public Void call() throws Exception {
|
|
|
+
|
|
|
+ fsKeyB.delete(fileToDelete, false);
|
|
|
+ throw new Exception("Exception should be thrown.");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ //This is possible
|
|
|
+ fsKeyB.delete(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/c/"))), true);
|
|
|
+ fsKeyB.delete(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/b/"))), true);
|
|
|
+ fsKeyB.delete(S3ATestUtils.createTestPath(
|
|
|
+ path(createFilename("/a/"))), true);
|
|
|
+ }
|
|
|
+
|
|
|
+ private FileSystem createNewFileSystemWithSSECKey(String sseCKey) throws
|
|
|
+ IOException {
|
|
|
+ Configuration conf = this.createConfiguration();
|
|
|
+ conf.set(Constants.SERVER_SIDE_ENCRYPTION_KEY, sseCKey);
|
|
|
+
|
|
|
+ S3AContract contract = (S3AContract) createContract(conf);
|
|
|
+ contract.init();
|
|
|
+ FileSystem fileSystem = contract.getTestFileSystem();
|
|
|
+ return fileSystem;
|
|
|
}
|
|
|
|
|
|
@Override
|