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;
+ }
+}