Skip to content

Commit

Permalink
[Kerberos] Add bootstrap checks for kerberos realm (#31548)
Browse files Browse the repository at this point in the history
As there are some system properties like `java.security.krb5.kdc`
, `java.security.krb5.realm` which can specify values that are
applicable to whole JVM. This is the reason for having only one
instance of Kerberos realm.
Each ES node will have a Kerberos keytab with credentials. This
keytab must exist for Kerberos authentication to work.
`KerberosRealmBootstrapCheck` performs these checks for given
configuration.
  • Loading branch information
bizybot authored Jun 27, 2018
1 parent 52d7701 commit 8899920
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
import org.elasticsearch.xpack.security.authc.TokenService;
import org.elasticsearch.xpack.security.authc.esnative.NativeUsersStore;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.security.authc.kerberos.KerberosRealmBootstrapCheck;
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
import org.elasticsearch.xpack.security.authz.AuthorizationService;
import org.elasticsearch.xpack.security.authz.SecuritySearchOperationListener;
Expand Down Expand Up @@ -285,7 +286,8 @@ public Security(Settings settings, final Path configPath) {
checks.addAll(Arrays.asList(
new TokenSSLBootstrapCheck(),
new PkiRealmBootstrapCheck(settings, getSslService()),
new TLSLicenseBootstrapCheck()));
new TLSLicenseBootstrapCheck(),
new KerberosRealmBootstrapCheck(env)));
checks.addAll(InternalRealms.getBootstrapChecks(settings, env));
this.bootstrapChecks = Collections.unmodifiableList(checks);
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.bootstrap.BootstrapContext;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Map.Entry;

/**
* This class is used to perform bootstrap checks for kerberos realm.
* <p>
* We use service keytabs for validating incoming kerberos tickets and is a
* required configuration. Due to JVM wide system properties for Kerberos we
* cannot support multiple Kerberos realms. This class adds checks for node to
* fail if service keytab does not exist or multiple kerberos realms have been
* configured.
*/
public class KerberosRealmBootstrapCheck implements BootstrapCheck {
private final Environment env;

public KerberosRealmBootstrapCheck(final Environment env) {
this.env = env;
}

@Override
public BootstrapCheckResult check(final BootstrapContext context) {
final Map<String, Settings> realmsSettings = RealmSettings.getRealmSettings(context.settings);
boolean isKerberosRealmConfigured = false;
for (final Entry<String, Settings> entry : realmsSettings.entrySet()) {
final String name = entry.getKey();
final Settings realmSettings = entry.getValue();
final String type = realmSettings.get("type");
if (Strings.hasText(type) == false) {
return BootstrapCheckResult.failure("missing realm type for [" + name + "] realm");
}
if (KerberosRealmSettings.TYPE.equals(type)) {
if (isKerberosRealmConfigured) {
return BootstrapCheckResult.failure(
"multiple [" + type + "] realms are configured. [" + type + "] can only have one such realm configured");
}
isKerberosRealmConfigured = true;

final Path keytabPath = env.configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(realmSettings));
if (Files.exists(keytabPath) == false) {
return BootstrapCheckResult.failure("configured service key tab file [" + keytabPath + "] does not exist");
}
}
}
return BootstrapCheckResult.success();
}

@Override
public boolean alwaysEnforce() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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.elasticsearch.bootstrap.BootstrapCheck;
import org.elasticsearch.bootstrap.BootstrapContext;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings;
import org.elasticsearch.xpack.core.security.authc.pki.PkiRealmSettings;
import org.elasticsearch.xpack.security.authc.kerberos.support.KerberosTestCase;

import java.io.IOException;
import java.nio.file.Path;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;

public class KerberosRealmBootstrapCheckTests extends ESTestCase {

public void testBootstrapCheckFailsForMultipleKerberosRealms() throws IOException {
final Path tempDir = createTempDir();
final Settings settings1 = buildKerberosRealmSettings("kerb1", false, tempDir);
final Settings settings2 = buildKerberosRealmSettings("kerb2", false, tempDir);
final Settings settings3 = realm("pki1", PkiRealmSettings.TYPE, Settings.builder()).build();
final Settings settings =
Settings.builder().put("path.home", tempDir).put(settings1).put(settings2).put(settings3).build();
final BootstrapContext context = new BootstrapContext(settings, null);
final KerberosRealmBootstrapCheck kerbRealmBootstrapCheck =
new KerberosRealmBootstrapCheck(TestEnvironment.newEnvironment(settings));
final BootstrapCheck.BootstrapCheckResult result = kerbRealmBootstrapCheck.check(context);
assertThat(result, is(notNullValue()));
assertThat(result.isFailure(), is(true));
assertThat(result.getMessage(), equalTo("multiple [" + KerberosRealmSettings.TYPE + "] realms are configured. ["
+ KerberosRealmSettings.TYPE + "] can only have one such realm configured"));
}

public void testBootstrapCheckFailsForMissingKeytabFile() throws IOException {
final Path tempDir = createTempDir();
final Settings settings =
Settings.builder().put("path.home", tempDir).put(buildKerberosRealmSettings("kerb1", true, tempDir)).build();
final BootstrapContext context = new BootstrapContext(settings, null);
final KerberosRealmBootstrapCheck kerbRealmBootstrapCheck =
new KerberosRealmBootstrapCheck(TestEnvironment.newEnvironment(settings));
final BootstrapCheck.BootstrapCheckResult result = kerbRealmBootstrapCheck.check(context);
assertThat(result, is(notNullValue()));
assertThat(result.isFailure(), is(true));
assertThat(result.getMessage(),
equalTo("configured service key tab file [" + tempDir.resolve("kerb1.keytab").toString() + "] does not exist"));
}

public void testBootstrapCheckFailsForMissingRealmType() throws IOException {
final Path tempDir = createTempDir();
final String name = "kerb1";
final Settings settings1 = buildKerberosRealmSettings("kerb1", false, tempDir);
final Settings settings2 = realm(name, randomFrom("", " "), Settings.builder()).build();
final Settings settings =
Settings.builder().put("path.home", tempDir).put(settings1).put(settings2).build();
final BootstrapContext context = new BootstrapContext(settings, null);
final KerberosRealmBootstrapCheck kerbRealmBootstrapCheck =
new KerberosRealmBootstrapCheck(TestEnvironment.newEnvironment(settings));
final BootstrapCheck.BootstrapCheckResult result = kerbRealmBootstrapCheck.check(context);
assertThat(result, is(notNullValue()));
assertThat(result.isFailure(), is(true));
assertThat(result.getMessage(), equalTo("missing realm type for [" + name + "] realm"));
}

public void testBootstrapCheckSucceedsForCorrectConfiguration() throws IOException {
final Path tempDir = createTempDir();
final Settings finalSettings =
Settings.builder().put("path.home", tempDir).put(buildKerberosRealmSettings("kerb1", false, tempDir)).build();
final BootstrapContext context = new BootstrapContext(finalSettings, null);
final KerberosRealmBootstrapCheck kerbRealmBootstrapCheck =
new KerberosRealmBootstrapCheck(TestEnvironment.newEnvironment(finalSettings));
final BootstrapCheck.BootstrapCheckResult result = kerbRealmBootstrapCheck.check(context);
assertThat(result, is(notNullValue()));
assertThat(result.isSuccess(), is(true));
}

public void testBootstrapCheckSucceedsForNoKerberosRealms() throws IOException {
final Path tempDir = createTempDir();
final Settings finalSettings = Settings.builder().put("path.home", tempDir).build();
final BootstrapContext context = new BootstrapContext(finalSettings, null);
final KerberosRealmBootstrapCheck kerbRealmBootstrapCheck =
new KerberosRealmBootstrapCheck(TestEnvironment.newEnvironment(finalSettings));
final BootstrapCheck.BootstrapCheckResult result = kerbRealmBootstrapCheck.check(context);
assertThat(result, is(notNullValue()));
assertThat(result.isSuccess(), is(true));
}

private Settings buildKerberosRealmSettings(final String name, final boolean missingKeytab, final Path tempDir) throws IOException {
final Settings.Builder builder = Settings.builder();
if (missingKeytab == false) {
KerberosTestCase.writeKeyTab(tempDir.resolve(name + ".keytab"), null);
}
builder.put(KerberosTestCase.buildKerberosRealmSettings(tempDir.resolve(name + ".keytab").toString()));
return realm(name, KerberosRealmSettings.TYPE, builder).build();
}

private Settings.Builder realm(final String name, final String type, final Settings.Builder settings) {
final String prefix = RealmSettings.PREFIX + name + ".";
if (type != null) {
settings.put("type", type);
}
final Settings.Builder builder = Settings.builder().put(settings.normalizePrefix(prefix).build(), false);
return builder;
}
}

0 comments on commit 8899920

Please sign in to comment.