|
@@ -0,0 +1,190 @@
|
|
|
+/*
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
+ * distributed with this work for additional information
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
+ *
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
+ *
|
|
|
+ * Unless required by applicable law or agreed to in writing, software
|
|
|
+ * distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
+ * See the License for the specific language governing permissions and
|
|
|
+ * limitations under the License.
|
|
|
+ */
|
|
|
+
|
|
|
+package org.apache.hadoop.fs.s3a.auth;
|
|
|
+
|
|
|
+import java.io.ByteArrayOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.PrintStream;
|
|
|
+import java.net.URI;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+
|
|
|
+import org.assertj.core.api.Assertions;
|
|
|
+import org.junit.AfterClass;
|
|
|
+import org.junit.Test;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
+import org.apache.hadoop.fs.FileSystem;
|
|
|
+import org.apache.hadoop.fs.Path;
|
|
|
+import org.apache.hadoop.fs.s3a.AbstractS3ATestBase;
|
|
|
+import org.apache.hadoop.io.IOUtils;
|
|
|
+import org.apache.hadoop.security.UserGroupInformation;
|
|
|
+import org.apache.hadoop.security.alias.CredentialShell;
|
|
|
+
|
|
|
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_CREDENTIAL_PROVIDER_PATH;
|
|
|
+import static org.apache.hadoop.fs.s3a.Constants.S3A_SECURITY_CREDENTIAL_PROVIDER_PATH;
|
|
|
+import static org.apache.hadoop.fs.s3a.S3ATestUtils.disableFilesystemCaching;
|
|
|
+import static org.apache.hadoop.fs.s3a.S3ATestUtils.removeBaseAndBucketOverrides;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Test JCEKS file load/save on S3a.
|
|
|
+ * Uses CredentialShell to better replicate the CLI.
|
|
|
+ *
|
|
|
+ * See HADOOP-17894.
|
|
|
+ * This test is at risk of leaking FS instances in the JCEKS providers;
|
|
|
+ * this is handled in an AfterClass operation.
|
|
|
+ *
|
|
|
+ */
|
|
|
+public class ITestJceksIO extends AbstractS3ATestBase {
|
|
|
+ private static final Logger LOG = LoggerFactory.getLogger(
|
|
|
+ ITestJceksIO.class);
|
|
|
+ private static final String UTF8 = StandardCharsets.UTF_8.name();
|
|
|
+ private PrintStream oldStdout, oldStderr;
|
|
|
+ private ByteArrayOutputStream stdout, stderr;
|
|
|
+ private PrintStream printStdout, printStderr;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void setup() throws Exception {
|
|
|
+ super.setup();
|
|
|
+ oldStdout = System.out;
|
|
|
+ oldStderr = System.err;
|
|
|
+ stdout = new ByteArrayOutputStream();
|
|
|
+ printStdout = new PrintStream(stdout);
|
|
|
+ System.setOut(printStdout);
|
|
|
+
|
|
|
+ stderr = new ByteArrayOutputStream();
|
|
|
+ printStderr = new PrintStream(stderr);
|
|
|
+ System.setErr(printStderr);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void teardown() throws Exception {
|
|
|
+ System.setOut(oldStdout);
|
|
|
+ System.setErr(oldStderr);
|
|
|
+ IOUtils.cleanupWithLogger(LOG, printStdout, printStderr);
|
|
|
+ super.teardown();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Shut down all filesystems for this user to avoid
|
|
|
+ * leaking those used by credential providers.
|
|
|
+ */
|
|
|
+ @AfterClass
|
|
|
+ public static void closeAllFilesystems() {
|
|
|
+ try {
|
|
|
+ LOG.info("Closing down all filesystems for current user");
|
|
|
+ FileSystem.closeAllForUGI(UserGroupInformation.getCurrentUser());
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.warn("UGI.getCurrentUser()", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * FS config with no providers. FS caching is disabled.
|
|
|
+ * @return a new configuration.
|
|
|
+ */
|
|
|
+ private Configuration createNewConfiguration() {
|
|
|
+ final Configuration conf = new Configuration(getConfiguration());
|
|
|
+ removeBaseAndBucketOverrides(conf,
|
|
|
+ HADOOP_SECURITY_CREDENTIAL_PROVIDER_PATH,
|
|
|
+ S3A_SECURITY_CREDENTIAL_PROVIDER_PATH);
|
|
|
+ disableFilesystemCaching(conf);
|
|
|
+ return conf;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * List credentials; expect the file to be missing.
|
|
|
+ * hadoop credential list -provider jceks://s3a@bucket/s3.jceks
|
|
|
+ */
|
|
|
+ @Test
|
|
|
+ public void testListMissingJceksFile() throws Throwable {
|
|
|
+ final Path dir = path("jceks");
|
|
|
+ Path keystore = new Path(dir, "keystore.jceks");
|
|
|
+ String jceksProvider = toJceksProvider(keystore);
|
|
|
+
|
|
|
+ CredentialShell cs = new CredentialShell();
|
|
|
+
|
|
|
+ cs.setConf(createNewConfiguration());
|
|
|
+ run(cs, null,
|
|
|
+ "list", "-provider", jceksProvider);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ public void testCredentialSuccessfulLifecycle() throws Exception {
|
|
|
+ final Path dir = path("jceks");
|
|
|
+ Path keystore = new Path(dir, "keystore.jceks");
|
|
|
+ String jceksProvider = toJceksProvider(keystore);
|
|
|
+ CredentialShell cs = new CredentialShell();
|
|
|
+ cs.setConf(createNewConfiguration());
|
|
|
+ run(cs, "credential1 has been successfully created.", "create", "credential1", "-value",
|
|
|
+ "p@ssw0rd", "-provider",
|
|
|
+ jceksProvider);
|
|
|
+
|
|
|
+ assertIsFile(keystore);
|
|
|
+ run(cs, "credential1",
|
|
|
+ "list", "-provider", jceksProvider);
|
|
|
+
|
|
|
+ run(cs, "credential1 has been successfully deleted.",
|
|
|
+ "delete", "credential1", "-f", "-provider",
|
|
|
+ jceksProvider);
|
|
|
+
|
|
|
+ String[] args5 = {
|
|
|
+ "list", "-provider",
|
|
|
+ jceksProvider
|
|
|
+ };
|
|
|
+ String out = run(cs, null, args5);
|
|
|
+ Assertions.assertThat(out)
|
|
|
+ .describedAs("Command result of list")
|
|
|
+ .doesNotContain("credential1");
|
|
|
+ }
|
|
|
+
|
|
|
+ private String run(CredentialShell cs, String expected, String... args)
|
|
|
+ throws Exception {
|
|
|
+ stdout.reset();
|
|
|
+ int rc = cs.run(args);
|
|
|
+ final String out = stdout.toString(UTF8);
|
|
|
+ LOG.error("{}", stderr.toString(UTF8));
|
|
|
+ LOG.info("{}", out);
|
|
|
+
|
|
|
+ Assertions.assertThat(rc)
|
|
|
+ .describedAs("Command result of %s with output %s",
|
|
|
+ args[0], out)
|
|
|
+ .isEqualTo(0);
|
|
|
+ if (expected != null) {
|
|
|
+ Assertions.assertThat(out)
|
|
|
+ .describedAs("Command result of %s", args[0])
|
|
|
+ .contains(expected);
|
|
|
+ }
|
|
|
+ return out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Convert a path to a jceks URI.
|
|
|
+ * @param keystore store
|
|
|
+ * @return string for the command line
|
|
|
+ */
|
|
|
+ private String toJceksProvider(Path keystore) {
|
|
|
+ final URI uri = keystore.toUri();
|
|
|
+ return String.format("jceks://%s@%s%s",
|
|
|
+ uri.getScheme(), uri.getHost(), uri.getPath());
|
|
|
+ }
|
|
|
+
|
|
|
+}
|