security: add setting that makes system key required

This commit adds a setting that makes the system key required. If this setting is set to
true, a node will fail to startup when the system key does not exist.

Closes elastic/elasticsearch#3957

Original commit: elastic/x-pack-elasticsearch@e6d3000974
This commit is contained in:
Jay Modi 2016-11-03 07:54:38 -04:00 committed by GitHub
parent 1de85f4740
commit 714b891b03
2 changed files with 43 additions and 18 deletions

View File

@ -13,6 +13,7 @@ import javax.crypto.Mac;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -32,6 +33,7 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent; import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackPlugin;
@ -59,12 +61,14 @@ public class CryptoService extends AbstractComponent {
private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$[^\\$]*\\$\\$.+"); private static final Pattern SIG_PATTERN = Pattern.compile("^\\$\\$[0-9]+\\$\\$[^\\$]*\\$\\$.+");
private static final byte[] HKDF_APP_INFO = "es-security-crypto-service".getBytes(StandardCharsets.UTF_8); private static final byte[] HKDF_APP_INFO = "es-security-crypto-service".getBytes(StandardCharsets.UTF_8);
public static final Setting<String> ENCRYPTION_ALGO_SETTING = private static final Setting<Boolean> SYSTEM_KEY_REQUIRED_SETTING =
new Setting<>(setting("encryption.algorithm"), s -> DEFAULT_ENCRYPTION_ALGORITHM, s -> s, Setting.Property.NodeScope); Setting.boolSetting(setting("system_key.required"), false, Property.NodeScope);
public static final Setting<Integer> ENCRYPTION_KEY_LENGTH_SETTING = private static final Setting<String> ENCRYPTION_ALGO_SETTING =
Setting.intSetting(setting("encryption_key.length"), DEFAULT_KEY_LENGTH, Setting.Property.NodeScope); new Setting<>(setting("encryption.algorithm"), s -> DEFAULT_ENCRYPTION_ALGORITHM, s -> s, Property.NodeScope);
public static final Setting<String> ENCRYPTION_KEY_ALGO_SETTING = private static final Setting<Integer> ENCRYPTION_KEY_LENGTH_SETTING =
new Setting<>(setting("encryption_key.algorithm"), DEFAULT_KEY_ALGORITH, s -> s, Setting.Property.NodeScope); Setting.intSetting(setting("encryption_key.length"), DEFAULT_KEY_LENGTH, Property.NodeScope);
private static final Setting<String> ENCRYPTION_KEY_ALGO_SETTING =
new Setting<>(setting("encryption_key.algorithm"), DEFAULT_KEY_ALGORITH, s -> s, Property.NodeScope);
private final SecureRandom secureRandom = new SecureRandom(); private final SecureRandom secureRandom = new SecureRandom();
private final String encryptionAlgorithm; private final String encryptionAlgorithm;
@ -93,7 +97,7 @@ public class CryptoService extends AbstractComponent {
} }
keyFile = resolveSystemKey(env); keyFile = resolveSystemKey(env);
systemKey = readSystemKey(keyFile); systemKey = readSystemKey(keyFile, SYSTEM_KEY_REQUIRED_SETTING.get(settings));
randomKey = generateSecretKey(RANDOM_KEY_SIZE); randomKey = generateSecretKey(RANDOM_KEY_SIZE);
randomKeyBase64 = Base64.getUrlEncoder().encodeToString(randomKey.getEncoded()); randomKeyBase64 = Base64.getUrlEncoder().encodeToString(randomKey.getEncoded());
@ -139,16 +143,17 @@ public class CryptoService extends AbstractComponent {
} }
} }
private static SecretKey readSystemKey(Path file) { private static SecretKey readSystemKey(Path file, boolean required) throws IOException {
if (!Files.exists(file)) { if (Files.exists(file)) {
return null;
}
try {
byte[] bytes = Files.readAllBytes(file); byte[] bytes = Files.readAllBytes(file);
return new SecretKeySpec(bytes, KEY_ALGO); return new SecretKeySpec(bytes, KEY_ALGO);
} catch (IOException e) {
throw new ElasticsearchException("could not read secret key", e);
} }
if (required) {
throw new FileNotFoundException("[" + file + "] must be present with a valid key");
}
return null;
} }
/** /**
@ -492,5 +497,6 @@ public class CryptoService extends AbstractComponent {
settings.add(ENCRYPTION_KEY_LENGTH_SETTING); settings.add(ENCRYPTION_KEY_LENGTH_SETTING);
settings.add(ENCRYPTION_KEY_ALGO_SETTING); settings.add(ENCRYPTION_KEY_ALGO_SETTING);
settings.add(ENCRYPTION_ALGO_SETTING); settings.add(ENCRYPTION_ALGO_SETTING);
settings.add(SYSTEM_KEY_REQUIRED_SETTING);
} }
} }

View File

@ -7,6 +7,7 @@ package org.elasticsearch.xpack.security.crypto;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.FileNotFoundException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Arrays; import java.util.Arrays;
@ -17,6 +18,7 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackPlugin;
import org.junit.Before; import org.junit.Before;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
@ -37,6 +39,7 @@ public class CryptoServiceTests extends ESTestCase {
Files.write(keyFile, CryptoService.generateKey()); Files.write(keyFile, CryptoService.generateKey());
settings = Settings.builder() settings = Settings.builder()
.put("resource.reload.interval.high", "2s") .put("resource.reload.interval.high", "2s")
.put("xpack.security.system_key.required", randomBoolean())
.put("path.home", home) .put("path.home", home)
.build(); .build();
env = new Environment(settings); env = new Environment(settings);
@ -189,4 +192,20 @@ public class CryptoServiceTests extends ESTestCase {
assertThat(regenerated, equalTo(signingKey)); assertThat(regenerated, equalTo(signingKey));
} }
} }
public void testSystemKeyFileRequired() throws Exception {
Files.delete(keyFile);
Settings customSettings = Settings.builder().put(settings).put("xpack.security.system_key.required", true).build();
FileNotFoundException fnfe = expectThrows(FileNotFoundException.class, () -> new CryptoService(customSettings, env));
assertThat(fnfe.getMessage(), containsString("must be present with a valid key"));
}
public void testEmptySystemKeyFile() throws Exception {
// delete and create empty file
Files.delete(keyFile);
Files.createFile(keyFile);
assertTrue(Files.exists(keyFile));
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> new CryptoService(settings, env));
assertThat(iae.getMessage(), containsString("Empty key"));
}
} }