Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
set -e

if [[ $# -lt 1 ]]; then
echo 'Usage: addprinc.sh <principalNameNoRealm>'
echo 'Usage: addprinc.sh principalName [password]'
echo ' principalName user principal name without realm'
echo ' password If provided then will set password for user else it will provision user with keytab'
exit 1
fi

PRINC="$1"
PASSWD="$2"
USER=$(echo $PRINC | tr "/" "_")

VDIR=/vagrant
Expand All @@ -47,12 +50,17 @@ ADMIN_KTAB=$LOCALSTATEDIR/admin.keytab
USER_PRIN=$PRINC@$REALM
USER_KTAB=$LOCALSTATEDIR/$USER.keytab

if [ -f $USER_KTAB ]; then
if [ -f $USER_KTAB ] && [ -z "$PASSWD" ]; then
echo "Principal '${PRINC}@${REALM}' already exists. Re-copying keytab..."
sudo cp $USER_KTAB $KEYTAB_DIR/$USER.keytab
else
echo "Provisioning '${PRINC}@${REALM}' principal and keytab..."
sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "addprinc -randkey $USER_PRIN"
sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "ktadd -k $USER_KTAB $USER_PRIN"
if [ -z "$PASSWD" ]; then
echo "Provisioning '${PRINC}@${REALM}' principal and keytab..."
sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "addprinc -randkey $USER_PRIN"
sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "ktadd -k $USER_KTAB $USER_PRIN"
sudo cp $USER_KTAB $KEYTAB_DIR/$USER.keytab
else
echo "Provisioning '${PRINC}@${REALM}' principal with password..."
sudo kadmin -p $ADMIN_PRIN -kt $ADMIN_KTAB -q "addprinc -pw $PASSWD $PRINC"
fi
fi

sudo cp $USER_KTAB $KEYTAB_DIR/$USER.keytab
126 changes: 126 additions & 0 deletions x-pack/qa/kerberos-tests/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.Files

apply plugin: 'elasticsearch.vagrantsupport'
apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'

dependencies {
testCompile project(path: xpackModule('core'), configuration: 'runtime')
testCompile project(path: xpackModule('core'), configuration: 'testArtifacts')
testCompile project(path: xpackModule('security'), configuration: 'testArtifacts')
}

// MIT Kerberos Vagrant Testing Fixture
String box = "krb5kdc"
Map<String,String> vagrantEnvVars = [
'VAGRANT_CWD' : "${project(':test:fixtures:krb5kdc-fixture').projectDir}",
'VAGRANT_VAGRANTFILE' : 'Vagrantfile',
'VAGRANT_PROJECT_DIR' : "${project(':test:fixtures:krb5kdc-fixture').projectDir}"
]

task krb5kdcUpdate(type: org.elasticsearch.gradle.vagrant.VagrantCommandTask) {
command 'box'
subcommand 'update'
boxName box
environmentVars vagrantEnvVars
dependsOn "vagrantCheckVersion", "virtualboxCheckVersion"
}

task krb5kdcFixture(type: org.elasticsearch.gradle.test.VagrantFixture) {
command 'up'
args '--provision', '--provider', 'virtualbox'
boxName box
environmentVars vagrantEnvVars
dependsOn krb5kdcUpdate
}

task krb5AddPrincipals { dependsOn krb5kdcFixture }

List<String> principals = [
"HTTP/localhost",
"peppa",
"george:dino"
]
String realm = "BUILD.ELASTIC.CO"

for (String principal : principals) {
String[] princPwdPair = principal.split(':');
String princName = princPwdPair[0];
String password = "";
if (princPwdPair.length > 1) {
password = princPwdPair[1];
}
Task create = project.tasks.create("addPrincipal#${principal}".replace('/', '_'), org.elasticsearch.gradle.vagrant.VagrantCommandTask) {
command 'ssh'
args '--command', "sudo bash /vagrant/src/main/resources/provision/addprinc.sh $princName $password"
boxName box
environmentVars vagrantEnvVars
dependsOn krb5kdcFixture
}
krb5AddPrincipals.dependsOn(create)
}

def generatedResources = "$buildDir/generated-resources/keytabs"
task copyKeytabToGeneratedResources(type: Copy) {
Path peppaKeytab = project(':test:fixtures:krb5kdc-fixture').buildDir.toPath().resolve("keytabs").resolve("peppa.keytab").toAbsolutePath()
from peppaKeytab;
into generatedResources
}

integTestCluster {
setting 'xpack.license.self_generated.type', 'trial'
setting 'xpack.security.enabled', 'true'
setting 'xpack.security.authc.realms.file.type', 'file'
setting 'xpack.security.authc.realms.file.order', '0'
setting 'xpack.ml.enabled', 'false'
setting 'xpack.security.audit.enabled', 'true'
// Kerberos realm
setting 'xpack.security.authc.realms.kerberos.type', 'kerberos'
setting 'xpack.security.authc.realms.kerberos.order', '1'
setting 'xpack.security.authc.realms.kerberos.keytab.path', 'es.keytab'
setting 'xpack.security.authc.realms.kerberos.krb.debug', 'true'
setting 'xpack.security.authc.realms.kerberos.remove_realm_name', 'false'

Path krb5conf = project(':test:fixtures:krb5kdc-fixture').buildDir.toPath().resolve("conf").resolve("krb5.conf").toAbsolutePath()
String jvmArgsStr = " -Djava.security.krb5.conf=${krb5conf}" + " -Dsun.security.krb5.debug=true"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we specify debug in the realm and in the jvm args? Is it because our debug value overrides the system property? If so, we need to change the default of the setting to be the value of the system property.

Copy link
Contributor Author

@bizybot bizybot Jul 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two flags for debug level logs, -Djava.security.krb5.conf enables debug logs at the Kerberos protocol level. The flag that we take in realm config is only for Krb5LoginModule from JAAS. I will add this information to the debugging and troubleshooting documentation. Thank you.

jvmArgs jvmArgsStr
Path esKeytab = project(':test:fixtures:krb5kdc-fixture').buildDir.toPath().resolve("keytabs").resolve("HTTP_localhost.keytab").toAbsolutePath()
extraConfigFile("es.keytab", "${esKeytab}")

setupCommand 'setupTestAdmin',
'bin/elasticsearch-users', 'useradd', "test_admin", '-p', 'x-pack-test-password', '-r', "superuser"

waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: 'test_admin',
password: 'x-pack-test-password',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}

}

integTestRunner {
Path peppaKeytab = Paths.get("${project.buildDir}", "generated-resources", "keytabs", "peppa.keytab")
systemProperty 'test.userkt', "peppa@${realm}"
systemProperty 'test.userkt.keytab', "${peppaKeytab}"
systemProperty 'test.userpwd', "george@${realm}"
systemProperty 'test.userpwd.password', "dino"
systemProperty 'tests.security.manager', 'true'
Path krb5conf = project(':test:fixtures:krb5kdc-fixture').buildDir.toPath().resolve("conf").resolve("krb5.conf").toAbsolutePath()
List jvmargs = ["-Djava.security.krb5.conf=${krb5conf}","-Dsun.security.krb5.debug=true"]
jvmArgs jvmargs
}

if (project.rootProject.vagrantSupported == false) {
integTest.enabled = false
} else {
project.sourceSets.test.output.dir(generatedResources, builtBy: copyKeytabToGeneratedResources)
integTestCluster.dependsOn krb5AddPrincipals, krb5kdcFixture, copyKeytabToGeneratedResources
integTest.finalizedBy project(':test:fixtures:krb5kdc-fixture').halt
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.security.authc.kerberos;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.junit.Before;

import java.io.IOException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.List;
import java.util.Map;

import javax.security.auth.login.LoginContext;

import static org.elasticsearch.common.xcontent.XContentHelper.convertToMap;
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;

/**
* Integration test to demonstrate authentication against a real MIT Kerberos
* instance.
* <p>
* Demonstrates login by keytab and login by password for given user principal
* name using rest client.
*/
public class KerberosAuthenticationIT extends ESRestTestCase {
private static final String ENABLE_KERBEROS_DEBUG_LOGS_KEY = "test.krb.debug";
private static final String TEST_USER_WITH_KEYTAB_KEY = "test.userkt";
private static final String TEST_USER_WITH_KEYTAB_PATH_KEY = "test.userkt.keytab";
private static final String TEST_USER_WITH_PWD_KEY = "test.userpwd";
private static final String TEST_USER_WITH_PWD_PASSWD_KEY = "test.userpwd.password";
private static final String TEST_KERBEROS_REALM_NAME = "kerberos";

@Override
protected Settings restAdminSettings() {
final String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}

/**
* Creates simple mapping that maps the users from 'kerberos' realm to
* the 'kerb_test' role.
*/
@Before
public void setupRoleMapping() throws IOException {
final String json = Strings // top-level
.toString(XContentBuilder.builder(XContentType.JSON.xContent()).startObject()
.array("roles", new String[] { "kerb_test" })
.field("enabled", true)
.startObject("rules")
.startArray("all")
.startObject().startObject("field").field("realm.name", TEST_KERBEROS_REALM_NAME).endObject().endObject()
.endArray() // "all"
.endObject() // "rules"
.endObject());

final Request request = new Request("POST", "/_xpack/security/role_mapping/kerberosrolemapping");
request.setJsonEntity(json);
final Response response = adminClient().performRequest(request);
assertOK(response);
}

public void testLoginByKeytab() throws IOException, PrivilegedActionException {
final String userPrincipalName = System.getProperty(TEST_USER_WITH_KEYTAB_KEY);
final String keytabPath = System.getProperty(TEST_USER_WITH_KEYTAB_PATH_KEY);
final boolean enabledDebugLogs = Boolean.parseBoolean(System.getProperty(ENABLE_KERBEROS_DEBUG_LOGS_KEY));
final SpnegoHttpClientConfigCallbackHandler callbackHandler = new SpnegoHttpClientConfigCallbackHandler(userPrincipalName,
keytabPath, enabledDebugLogs);
executeRequestAndVerifyResponse(userPrincipalName, callbackHandler);
}

public void testLoginByUsernamePassword() throws IOException, PrivilegedActionException {
final String userPrincipalName = System.getProperty(TEST_USER_WITH_PWD_KEY);
final String password = System.getProperty(TEST_USER_WITH_PWD_PASSWD_KEY);
final boolean enabledDebugLogs = Boolean.parseBoolean(System.getProperty(ENABLE_KERBEROS_DEBUG_LOGS_KEY));
final SpnegoHttpClientConfigCallbackHandler callbackHandler = new SpnegoHttpClientConfigCallbackHandler(userPrincipalName,
new SecureString(password.toCharArray()), enabledDebugLogs);
executeRequestAndVerifyResponse(userPrincipalName, callbackHandler);
}

private void executeRequestAndVerifyResponse(final String userPrincipalName,
final SpnegoHttpClientConfigCallbackHandler callbackHandler) throws PrivilegedActionException, IOException {
final Request request = new Request("GET", "/_xpack/security/_authenticate");
try (RestClient restClient = buildRestClientForKerberos(callbackHandler)) {
final AccessControlContext accessControlContext = AccessController.getContext();
final LoginContext lc = callbackHandler.login();
Response response = SpnegoHttpClientConfigCallbackHandler.doAsPrivilegedWrapper(lc.getSubject(),
(PrivilegedExceptionAction<Response>) () -> {
return restClient.performRequest(request);
}, accessControlContext);

assertOK(response);
final Map<String, Object> map = parseResponseAsMap(response.getEntity());
assertThat(map.get("username"), equalTo(userPrincipalName));
assertThat(map.get("roles"), instanceOf(List.class));
assertThat(((List<?>) map.get("roles")), contains("kerb_test"));
}
}

private Map<String, Object> parseResponseAsMap(final HttpEntity entity) throws IOException {
return convertToMap(XContentType.JSON.xContent(), entity.getContent(), false);
}

private RestClient buildRestClientForKerberos(final SpnegoHttpClientConfigCallbackHandler callbackHandler) throws IOException {
final Settings settings = restAdminSettings();
final HttpHost[] hosts = getClusterHosts().toArray(new HttpHost[getClusterHosts().size()]);

final RestClientBuilder restClientBuilder = RestClient.builder(hosts);
configureRestClientBuilder(restClientBuilder, settings);
restClientBuilder.setHttpClientConfigCallback(callbackHandler);
return restClientBuilder.build();
}

private static void configureRestClientBuilder(final RestClientBuilder restClientBuilder, final Settings settings)
throws IOException {
final String requestTimeoutString = settings.get(CLIENT_RETRY_TIMEOUT);
if (requestTimeoutString != null) {
final TimeValue maxRetryTimeout = TimeValue.parseTimeValue(requestTimeoutString, CLIENT_RETRY_TIMEOUT);
restClientBuilder.setMaxRetryTimeoutMillis(Math.toIntExact(maxRetryTimeout.getMillis()));
}
final String socketTimeoutString = settings.get(CLIENT_SOCKET_TIMEOUT);
if (socketTimeoutString != null) {
final TimeValue socketTimeout = TimeValue.parseTimeValue(socketTimeoutString, CLIENT_SOCKET_TIMEOUT);
restClientBuilder.setRequestConfigCallback(conf -> conf.setSocketTimeout(Math.toIntExact(socketTimeout.getMillis())));
}
if (settings.hasValue(CLIENT_PATH_PREFIX)) {
restClientBuilder.setPathPrefix(settings.get(CLIENT_PATH_PREFIX));
}
}
}
Loading