blob: ea39c259baacb723aa9feb309319a528c940c6c6 [file] [log] [blame]
/**
* 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.gateway;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.security.Principal;
import java.util.Properties;
import com.mycila.xmltool.XMLDoc;
import com.mycila.xmltool.XMLTag;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.http.HttpConfig;
import org.apache.hadoop.minikdc.MiniKdc;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
import org.apache.hadoop.test.TestUtils;
import org.apache.hadoop.test.category.ReleaseTest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.BasicUserPrincipal;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.PropertyConfigurator;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_BLOCK_ACCESS_TOKEN_ENABLE_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_HTTPS_KEYSTORE_RESOURCE_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_HTTPS_ADDRESS_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_KERBEROS_PRINCIPAL_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATANODE_KEYTAB_FILE_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_DATA_ENCRYPTION_ALGORITHM_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_HTTP_POLICY_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_JOURNALNODE_HTTPS_ADDRESS_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_JOURNALNODE_KERBEROS_INTERNAL_SPNEGO_PRINCIPAL_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_JOURNALNODE_KERBEROS_PRINCIPAL_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_JOURNALNODE_KEYTAB_FILE_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY;
import static org.apache.hadoop.hdfs.DFSConfigKeys.IPC_CLIENT_CONNECT_MAX_RETRIES_KEY;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@Category(ReleaseTest.class)
public class SecureClusterTest {
private static MiniDFSCluster miniDFSCluster;
private static MiniKdc kdc;
private static HdfsConfiguration configuration;
private static int nameNodeHttpPort;
private static String userName;
private static GatewayTestDriver driver = new GatewayTestDriver();
private static File baseDir;
@BeforeClass
public static void setupSuite() throws Exception {
nameNodeHttpPort = TestUtils.findFreePort();
configuration = new HdfsConfiguration();
baseDir = new File(KeyStoreTestUtil.getClasspathDir(SecureClusterTest.class));
System.setProperty(MiniDFSCluster.PROP_TEST_BUILD_DATA, baseDir.getAbsolutePath());
initKdc();
miniDFSCluster = new MiniDFSCluster.Builder(configuration)
.nameNodePort(20112)
.nameNodeHttpPort(nameNodeHttpPort)
.numDataNodes(0)
.format(true)
.racks(null)
.build();
}
private static void initKdc() throws Exception {
Properties kdcConf = MiniKdc.createConf();
kdc = new MiniKdc(kdcConf, baseDir);
kdc.start();
configuration = new HdfsConfiguration();
SecurityUtil.setAuthenticationMethod(UserGroupInformation.AuthenticationMethod.KERBEROS, configuration);
UserGroupInformation.setConfiguration(configuration);
assertTrue("Expected configuration to enable security", UserGroupInformation.isSecurityEnabled());
userName = UserGroupInformation.createUserForTesting("guest", new String[] {"users"}).getUserName();
File keytabFile = new File(baseDir, userName + ".keytab");
String keytab = keytabFile.getAbsolutePath();
// Windows will not reverse name lookup "127.0.0.1" to "localhost".
String krbInstance = Path.WINDOWS ? "127.0.0.1" : "localhost";
kdc.createPrincipal(keytabFile, userName + "/" + krbInstance, "HTTP/" + krbInstance);
String hdfsPrincipal = userName + "/" + krbInstance + "@" + kdc.getRealm();
String spnegoPrincipal = "HTTP/" + krbInstance + "@" + kdc.getRealm();
configuration.set(DFS_NAMENODE_KERBEROS_PRINCIPAL_KEY, hdfsPrincipal);
configuration.set(DFS_NAMENODE_KEYTAB_FILE_KEY, keytab);
configuration.set(DFS_DATANODE_KERBEROS_PRINCIPAL_KEY, hdfsPrincipal);
configuration.set(DFS_DATANODE_KEYTAB_FILE_KEY, keytab);
configuration.set(DFS_WEB_AUTHENTICATION_KERBEROS_PRINCIPAL_KEY, spnegoPrincipal);
configuration.set(DFS_JOURNALNODE_KEYTAB_FILE_KEY, keytab);
configuration.set(DFS_JOURNALNODE_KERBEROS_PRINCIPAL_KEY, hdfsPrincipal);
configuration.set(DFS_JOURNALNODE_KERBEROS_INTERNAL_SPNEGO_PRINCIPAL_KEY, spnegoPrincipal);
configuration.setBoolean(DFS_BLOCK_ACCESS_TOKEN_ENABLE_KEY, true);
configuration.set(DFS_DATA_ENCRYPTION_ALGORITHM_KEY, "authentication");
configuration.set(DFS_HTTP_POLICY_KEY, HttpConfig.Policy.HTTP_AND_HTTPS.name());
configuration.set(DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0");
configuration.set(DFS_DATANODE_HTTPS_ADDRESS_KEY, "localhost:0");
configuration.set(DFS_JOURNALNODE_HTTPS_ADDRESS_KEY, "localhost:0");
configuration.setInt(IPC_CLIENT_CONNECT_MAX_RETRIES_KEY, 10);
configuration.set("hadoop.proxyuser." + userName + ".hosts", "*");
configuration.set("hadoop.proxyuser." + userName + ".groups", "*");
configuration.setBoolean("dfs.permissions", true);
String keystoresDir = baseDir.getAbsolutePath();
File sslClientConfFile = new File(keystoresDir + "/ssl-client.xml");
File sslServerConfFile = new File(keystoresDir + "/ssl-server.xml");
KeyStoreTestUtil.setupSSLConfig(keystoresDir, keystoresDir, configuration, false);
configuration.set(DFS_CLIENT_HTTPS_KEYSTORE_RESOURCE_KEY,
sslClientConfFile.getName());
configuration.set(DFS_SERVER_HTTPS_KEYSTORE_RESOURCE_KEY,
sslServerConfFile.getName());
setupKnox(keytab, hdfsPrincipal);
}
private static void setupKnox(String keytab, String hdfsPrincipal) throws Exception {
//kerberos setup for http client
File jaasConf = setupJaasConf(baseDir, keytab, hdfsPrincipal);
System.setProperty("java.security.krb5.conf", kdc.getKrb5conf().getAbsolutePath());
System.setProperty("java.security.auth.login.config", jaasConf.getAbsolutePath());
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("sun.security.krb5.debug", "true");
//knox setup
System.setProperty("gateway.hadoop.kerberos.secured", "true");
GatewayTestConfig config = new GatewayTestConfig();
config.setGatewayPath( "gateway" );
config.setHadoopKerberosSecured(true);
config.setKerberosConfig(kdc.getKrb5conf().getAbsolutePath());
config.setKerberosLoginConfig(jaasConf.getAbsolutePath());
driver.setResourceBase(SecureClusterTest.class);
driver.setupLdap(0);
driver.setupGateway(config, "cluster", createTopology(), true);
}
@AfterClass
public static void cleanupSuite() throws Exception {
kdc.stop();
miniDFSCluster.shutdown();
driver.cleanup();
}
@Test
public void basicGetUserHomeRequest() throws Exception {
setupLogging();
CloseableHttpClient client = getHttpClient();
String method = "GET";
String uri = driver.getClusterUrl() + "/webhdfs/v1?op=GETHOMEDIRECTORY";
HttpHost target = new HttpHost("localhost", driver.getGatewayPort(), "http");
HttpRequest request = new BasicHttpRequest(method, uri);
CloseableHttpResponse response = client.execute(target, request);
String json = EntityUtils.toString(response.getEntity());
response.close();
System.out.println(json);
assertEquals("{\"Path\":\"/user/" + userName + "\"}", json);
}
private CloseableHttpClient getHttpClient() {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new Credentials() {
@Override
public Principal getUserPrincipal() {
return new BasicUserPrincipal("guest");
}
@Override
public String getPassword() {
return "guest-password";
}
});
return HttpClients.custom()
.setDefaultCredentialsProvider(credentialsProvider)
.build();
}
private static void setupLogging() {
PropertyConfigurator.configure(ClassLoader.getSystemResource("log4j.properties"));
}
private static File setupJaasConf(File baseDir, String keyTabFile, String principal) throws IOException {
File file = new File(baseDir, "jaas.conf");
if (!file.exists()) {
file.createNewFile();
} else {
file.delete();
file.createNewFile();
}
FileWriter writer = new FileWriter(file);
String content = String.format("com.sun.security.jgss.initiate {\n" +
"com.sun.security.auth.module.Krb5LoginModule required\n" +
"renewTGT=true\n" +
"doNotPrompt=true\n" +
"useKeyTab=true\n" +
"keyTab=\"%s\"\n" +
"principal=\"%s\"\n" +
"isInitiator=true\n" +
"storeKey=true\n" +
"useTicketCache=true\n" +
"client=true;\n" +
"};\n", keyTabFile, principal);
writer.write(content);
writer.close();
return file;
}
/**
* Creates a topology that is deployed to the gateway instance for the test suite.
* Note that this topology is shared by all of the test methods in this suite.
* @return A populated XML structure for a topology file.
*/
private static XMLTag createTopology() {
XMLTag xml = XMLDoc.newDocument(true)
.addRoot("topology")
.addTag( "gateway" )
.addTag( "provider" )
.addTag("role").addText("webappsec")
.addTag("name").addText("WebAppSec")
.addTag("enabled").addText("true")
.addTag( "param" )
.addTag("name").addText("csrf.enabled")
.addTag("value").addText("true").gotoParent().gotoParent()
.addTag("provider")
.addTag("role").addText("authentication")
.addTag("name").addText("ShiroProvider")
.addTag("enabled").addText("true")
.addTag( "param" )
.addTag("name").addText("main.ldapRealm")
.addTag("value").addText("org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm").gotoParent()
.addTag( "param" )
.addTag("name").addText("main.ldapRealm.userDnTemplate")
.addTag( "value" ).addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent()
.addTag( "param" )
.addTag("name").addText("main.ldapRealm.contextFactory.url")
.addTag( "value" ).addText(driver.getLdapUrl()).gotoParent()
.addTag( "param" )
.addTag("name").addText("main.ldapRealm.contextFactory.authenticationMechanism")
.addTag( "value" ).addText("simple").gotoParent()
.addTag( "param" )
.addTag("name").addText("urls./**")
.addTag( "value" ).addText("authcBasic").gotoParent().gotoParent()
.addTag("provider")
.addTag("role").addText("identity-assertion")
.addTag("enabled").addText("true")
.addTag("name").addText("Default").gotoParent()
.addTag("provider")
.addTag( "role" ).addText( "authorization" )
.addTag( "enabled" ).addText( "true" )
.addTag("name").addText("AclsAuthz").gotoParent()
.addTag("param")
.addTag("name").addText( "webhdfs-acl" )
.addTag("value").addText("hdfs;*;*").gotoParent()
.gotoRoot()
.addTag("service")
.addTag("role").addText("WEBHDFS")
.addTag("url").addText("http://localhost:" + nameNodeHttpPort + "/webhdfs/").gotoParent()
.gotoRoot();
// System.out.println( "GATEWAY=" + xml.toString() );
return xml;
}
}