[Kerberos] Remove Kerberos bootstrap checks (#32451)

This commit removes Kerberos bootstrap checks as they were more
validation checks and better done in Kerberos realm constructor
than as bootstrap checks. This also moves the check
for one Kerberos realm per node to where we initialize realms.
This commit adds few validations which were missing earlier
like missing read permissions on keytab file or if it is directory
to throw exception with error message.
This commit is contained in:
Yogesh Gaikwad 2018-07-31 10:59:36 +10:00 committed by GitHub
parent d75efbcf68
commit f0b36679ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 199 deletions

View File

@ -176,7 +176,6 @@ import org.elasticsearch.xpack.security.authc.Realms;
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;
@ -306,8 +305,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
new PasswordHashingAlgorithmBootstrapCheck(),
new FIPS140SecureSettingsBootstrapCheck(settings, env),
new FIPS140JKSKeystoreBootstrapCheck(settings),
new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings),
new KerberosRealmBootstrapCheck(env)));
new FIPS140PasswordHashingAlgorithmBootstrapCheck(settings)));
checks.addAll(InternalRealms.getBootstrapChecks(settings, env));
this.bootstrapChecks = Collections.unmodifiableList(checks);
Automatons.updateMaxDeterminizedStates(settings);

View File

@ -35,6 +35,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmSettings;
import org.elasticsearch.xpack.core.security.authc.esnative.NativeRealmSettings;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings;
import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings;
/**
@ -152,6 +153,7 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
Settings realmsSettings = RealmSettings.get(settings);
Set<String> internalTypes = new HashSet<>();
List<Realm> realms = new ArrayList<>();
List<String> kerberosRealmNames = new ArrayList<>();
for (String name : realmsSettings.names()) {
Settings realmSettings = realmsSettings.getAsSettings(name);
String type = realmSettings.get("type");
@ -178,6 +180,13 @@ public class Realms extends AbstractComponent implements Iterable<Realm> {
}
internalTypes.add(type);
}
if (KerberosRealmSettings.TYPE.equals(type)) {
kerberosRealmNames.add(name);
if (kerberosRealmNames.size() > 1) {
throw new IllegalArgumentException("multiple realms " + kerberosRealmNames.toString() + " configured of type [" + type
+ "], [" + type + "] can only have one such realm configured");
}
}
realms.add(factory.create(config));
}

View File

@ -25,6 +25,7 @@ import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
import org.ietf.jgss.GSSException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
@ -87,6 +88,16 @@ public final class KerberosRealm extends Realm implements CachingRealm {
this.kerberosTicketValidator = kerberosTicketValidator;
this.threadPool = threadPool;
this.keytabPath = config.env().configFile().resolve(KerberosRealmSettings.HTTP_SERVICE_KEYTAB_PATH.get(config.settings()));
if (Files.exists(keytabPath) == false) {
throw new IllegalArgumentException("configured service key tab file [" + keytabPath + "] does not exist");
}
if (Files.isDirectory(keytabPath)) {
throw new IllegalArgumentException("configured service key tab file [" + keytabPath + "] is a directory");
}
if (Files.isReadable(keytabPath) == false) {
throw new IllegalArgumentException("configured service key tab file [" + keytabPath + "] must have read permission");
}
this.enableKerberosDebug = KerberosRealmSettings.SETTING_KRB_DEBUG_ENABLE.get(config.settings());
this.removeRealmName = KerberosRealmSettings.SETTING_REMOVE_REALM_NAME.get(config.settings());
}

View File

@ -1,69 +0,0 @@
/*
* 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;
}
}

View File

@ -27,6 +27,7 @@ import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm;
import org.junit.Before;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -51,13 +52,16 @@ public class RealmsTests extends ESTestCase {
private XPackLicenseState licenseState;
private ThreadContext threadContext;
private ReservedRealm reservedRealm;
private int randomRealmTypesCount;
@Before
public void init() throws Exception {
factories = new HashMap<>();
factories.put(FileRealmSettings.TYPE, config -> new DummyRealm(FileRealmSettings.TYPE, config));
factories.put(NativeRealmSettings.TYPE, config -> new DummyRealm(NativeRealmSettings.TYPE, config));
for (int i = 0; i < randomIntBetween(1, 5); i++) {
factories.put(KerberosRealmSettings.TYPE, config -> new DummyRealm(KerberosRealmSettings.TYPE, config));
randomRealmTypesCount = randomIntBetween(1, 5);
for (int i = 0; i < randomRealmTypesCount; i++) {
String name = "type_" + i;
factories.put(name, config -> new DummyRealm(name, config));
}
@ -73,13 +77,13 @@ public class RealmsTests extends ESTestCase {
public void testWithSettings() throws Exception {
Settings.Builder builder = Settings.builder()
.put("path.home", createTempDir());
List<Integer> orders = new ArrayList<>(factories.size() - 2);
for (int i = 0; i < factories.size() - 2; i++) {
List<Integer> orders = new ArrayList<>(randomRealmTypesCount);
for (int i = 0; i < randomRealmTypesCount; i++) {
orders.add(i);
}
Collections.shuffle(orders, random());
Map<Integer, Integer> orderToIndex = new HashMap<>();
for (int i = 0; i < factories.size() - 2; i++) {
for (int i = 0; i < randomRealmTypesCount; i++) {
builder.put("xpack.security.authc.realms.realm_" + i + ".type", "type_" + i);
builder.put("xpack.security.authc.realms.realm_" + i + ".order", orders.get(i));
orderToIndex.put(orders.get(i), i);
@ -107,14 +111,14 @@ public class RealmsTests extends ESTestCase {
public void testWithSettingsWhereDifferentRealmsHaveSameOrder() throws Exception {
Settings.Builder builder = Settings.builder()
.put("path.home", createTempDir());
List<Integer> randomSeq = new ArrayList<>(factories.size() - 2);
for (int i = 0; i < factories.size() - 2; i++) {
List<Integer> randomSeq = new ArrayList<>(randomRealmTypesCount);
for (int i = 0; i < randomRealmTypesCount; i++) {
randomSeq.add(i);
}
Collections.shuffle(randomSeq, random());
TreeMap<String, Integer> nameToRealmId = new TreeMap<>();
for (int i = 0; i < factories.size() - 2; i++) {
for (int i = 0; i < randomRealmTypesCount; i++) {
int randomizedRealmId = randomSeq.get(i);
String randomizedRealmName = randomAlphaOfLengthBetween(12,32);
nameToRealmId.put("realm_" + randomizedRealmName, randomizedRealmId);
@ -181,13 +185,13 @@ public class RealmsTests extends ESTestCase {
public void testUnlicensedWithOnlyCustomRealms() throws Exception {
Settings.Builder builder = Settings.builder()
.put("path.home", createTempDir());
List<Integer> orders = new ArrayList<>(factories.size() - 2);
for (int i = 0; i < factories.size() - 2; i++) {
List<Integer> orders = new ArrayList<>(randomRealmTypesCount);
for (int i = 0; i < randomRealmTypesCount; i++) {
orders.add(i);
}
Collections.shuffle(orders, random());
Map<Integer, Integer> orderToIndex = new HashMap<>();
for (int i = 0; i < factories.size() - 2; i++) {
for (int i = 0; i < randomRealmTypesCount; i++) {
builder.put("xpack.security.authc.realms.realm_" + i + ".type", "type_" + i);
builder.put("xpack.security.authc.realms.realm_" + i + ".order", orders.get(i));
orderToIndex.put(orders.get(i), i);
@ -384,13 +388,13 @@ public class RealmsTests extends ESTestCase {
public void testDisabledRealmsAreNotAdded() throws Exception {
Settings.Builder builder = Settings.builder()
.put("path.home", createTempDir());
List<Integer> orders = new ArrayList<>(factories.size() - 2);
for (int i = 0; i < factories.size() - 2; i++) {
List<Integer> orders = new ArrayList<>(randomRealmTypesCount);
for (int i = 0; i < randomRealmTypesCount; i++) {
orders.add(i);
}
Collections.shuffle(orders, random());
Map<Integer, Integer> orderToIndex = new HashMap<>();
for (int i = 0; i < factories.size() - 2; i++) {
for (int i = 0; i < randomRealmTypesCount; i++) {
builder.put("xpack.security.authc.realms.realm_" + i + ".type", "type_" + i);
builder.put("xpack.security.authc.realms.realm_" + i + ".order", orders.get(i));
boolean enabled = randomBoolean();
@ -520,6 +524,20 @@ public class RealmsTests extends ESTestCase {
}
}
public void testInitRealmsFailsForMultipleKerberosRealms() throws IOException {
final Settings.Builder builder = Settings.builder().put("path.home", createTempDir());
builder.put("xpack.security.authc.realms.realm_1.type", "kerberos");
builder.put("xpack.security.authc.realms.realm_1.order", 1);
builder.put("xpack.security.authc.realms.realm_2.type", "kerberos");
builder.put("xpack.security.authc.realms.realm_2.order", 2);
final Settings settings = builder.build();
Environment env = TestEnvironment.newEnvironment(settings);
final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
() -> new Realms(settings, env, factories, licenseState, threadContext, reservedRealm));
assertThat(iae.getMessage(), is(equalTo(
"multiple realms [realm_1, realm_2] configured of type [kerberos], [kerberos] can only have one such realm configured")));
}
static class DummyRealm extends Realm {
DummyRealm(String type, RealmConfig config) {

View File

@ -1,114 +0,0 @@
/*
* 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 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;
}
}

View File

@ -11,17 +11,30 @@ import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.PlainActionFuture;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.env.TestEnvironment;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
import org.elasticsearch.xpack.core.security.authc.kerberos.KerberosRealmSettings;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper.UserData;
import org.ietf.jgss.GSSException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import javax.security.auth.login.LoginException;
@ -31,6 +44,7 @@ import static org.hamcrest.Matchers.nullValue;
import static org.mockito.AdditionalMatchers.aryEq;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@ -94,4 +108,44 @@ public class KerberosRealmTests extends KerberosRealmTestCase {
assertThat(future.actionGet(), is(nullValue()));
}
public void testKerberosRealmWithInvalidKeytabPathConfigurations() throws IOException {
final String keytabPathCase = randomFrom("keytabPathAsDirectory", "keytabFileDoesNotExist", "keytabPathWithNoReadPermissions");
final String expectedErrorMessage;
final String keytabPath;
final Set<PosixFilePermission> filePerms;
switch (keytabPathCase) {
case "keytabPathAsDirectory":
final String dirName = randomAlphaOfLength(5);
Files.createDirectory(dir.resolve(dirName));
keytabPath = dir.resolve(dirName).toString();
expectedErrorMessage = "configured service key tab file [" + keytabPath + "] is a directory";
break;
case "keytabFileDoesNotExist":
keytabPath = dir.resolve(randomAlphaOfLength(5) + ".keytab").toString();
expectedErrorMessage = "configured service key tab file [" + keytabPath + "] does not exist";
break;
case "keytabPathWithNoReadPermissions":
filePerms = PosixFilePermissions.fromString("---------");
final String keytabFileName = randomAlphaOfLength(5) + ".keytab";
final FileAttribute<Set<PosixFilePermission>> fileAttributes = PosixFilePermissions.asFileAttribute(filePerms);
try (SeekableByteChannel byteChannel = Files.newByteChannel(dir.resolve(keytabFileName),
EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE), fileAttributes)) {
byteChannel.write(ByteBuffer.wrap(randomByteArrayOfLength(10)));
}
keytabPath = dir.resolve(keytabFileName).toString();
expectedErrorMessage = "configured service key tab file [" + keytabPath + "] must have read permission";
break;
default:
throw new IllegalArgumentException("Unknown test case :" + keytabPathCase);
}
settings = KerberosTestCase.buildKerberosRealmSettings(keytabPath, 100, "10m", true, randomBoolean());
config = new RealmConfig("test-kerb-realm", settings, globalSettings, TestEnvironment.newEnvironment(globalSettings),
new ThreadContext(globalSettings));
mockNativeRoleMappingStore = roleMappingStore(Arrays.asList("user"));
mockKerberosTicketValidator = mock(KerberosTicketValidator.class);
final IllegalArgumentException iae = expectThrows(IllegalArgumentException.class,
() -> new KerberosRealm(config, mockNativeRoleMappingStore, mockKerberosTicketValidator, threadPool, null));
assertThat(iae.getMessage(), is(equalTo(expectedErrorMessage)));
}
}