From 88999208e7237d3d3e08d0dbd794d7db083272d9 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Wed, 27 Jun 2018 10:18:32 +1000 Subject: [PATCH] [Kerberos] Add bootstrap checks for kerberos realm (#31548) 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. --- .../xpack/security/Security.java | 4 +- .../kerberos/KerberosRealmBootstrapCheck.java | 69 +++++++++++ .../KerberosRealmBootstrapCheckTests.java | 115 ++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheck.java create mode 100644 x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheckTests.java diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index dbb50a92f1088..88d9e1dfc9d13 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -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; @@ -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 { diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheck.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheck.java new file mode 100644 index 0000000000000..bab899a866425 --- /dev/null +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheck.java @@ -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. + *

+ * 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 realmsSettings = RealmSettings.getRealmSettings(context.settings); + boolean isKerberosRealmConfigured = false; + for (final Entry 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; + } +} diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheckTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheckTests.java new file mode 100644 index 0000000000000..d2a40f0f6162f --- /dev/null +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/kerberos/KerberosRealmBootstrapCheckTests.java @@ -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; + } +}