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.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
@ -32,6 +33,7 @@ import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
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 byte[] HKDF_APP_INFO = "es-security-crypto-service".getBytes(StandardCharsets.UTF_8);
public static final Setting<String> ENCRYPTION_ALGO_SETTING =
new Setting<>(setting("encryption.algorithm"), s -> DEFAULT_ENCRYPTION_ALGORITHM, s -> s, Setting.Property.NodeScope);
public static final Setting<Integer> ENCRYPTION_KEY_LENGTH_SETTING =
Setting.intSetting(setting("encryption_key.length"), DEFAULT_KEY_LENGTH, Setting.Property.NodeScope);
public static final Setting<String> ENCRYPTION_KEY_ALGO_SETTING =
new Setting<>(setting("encryption_key.algorithm"), DEFAULT_KEY_ALGORITH, s -> s, Setting.Property.NodeScope);
private static final Setting<Boolean> SYSTEM_KEY_REQUIRED_SETTING =
Setting.boolSetting(setting("system_key.required"), false, Property.NodeScope);
private static final Setting<String> ENCRYPTION_ALGO_SETTING =
new Setting<>(setting("encryption.algorithm"), s -> DEFAULT_ENCRYPTION_ALGORITHM, s -> s, Property.NodeScope);
private static final Setting<Integer> ENCRYPTION_KEY_LENGTH_SETTING =
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 String encryptionAlgorithm;
@ -93,7 +97,7 @@ public class CryptoService extends AbstractComponent {
}
keyFile = resolveSystemKey(env);
systemKey = readSystemKey(keyFile);
systemKey = readSystemKey(keyFile, SYSTEM_KEY_REQUIRED_SETTING.get(settings));
randomKey = generateSecretKey(RANDOM_KEY_SIZE);
randomKeyBase64 = Base64.getUrlEncoder().encodeToString(randomKey.getEncoded());
@ -139,16 +143,17 @@ public class CryptoService extends AbstractComponent {
}
}
private static SecretKey readSystemKey(Path file) {
if (!Files.exists(file)) {
return null;
}
try {
private static SecretKey readSystemKey(Path file, boolean required) throws IOException {
if (Files.exists(file)) {
byte[] bytes = Files.readAllBytes(file);
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_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.spec.SecretKeySpec;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
@ -17,6 +18,7 @@ import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.XPackPlugin;
import org.junit.Before;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@ -37,6 +39,7 @@ public class CryptoServiceTests extends ESTestCase {
Files.write(keyFile, CryptoService.generateKey());
settings = Settings.builder()
.put("resource.reload.interval.high", "2s")
.put("xpack.security.system_key.required", randomBoolean())
.put("path.home", home)
.build();
env = new Environment(settings);
@ -148,7 +151,7 @@ public class CryptoServiceTests extends ESTestCase {
public void testEncryptionAndDecryptionCharsWithoutKey() throws Exception {
Files.delete(keyFile);
CryptoService service = new CryptoService(Settings.EMPTY, env);
assertThat(service.isEncryptionEnabled(), is(false));
assertThat(service.isEncryptionEnabled(), is(false));
final char[] chars = randomAsciiOfLengthBetween(0, 1000).toCharArray();
final char[] encryptedChars = service.encrypt(chars);
final char[] decryptedChars = service.decrypt(encryptedChars);
@ -158,18 +161,18 @@ public class CryptoServiceTests extends ESTestCase {
public void testEncryptionEnabledWithKey() throws Exception {
CryptoService service = new CryptoService(settings, env);
assertThat(service.isEncryptionEnabled(), is(true));
assertThat(service.isEncryptionEnabled(), is(true));
}
public void testEncryptionEnabledWithoutKey() throws Exception {
Files.delete(keyFile);
CryptoService service = new CryptoService(Settings.EMPTY, env);
assertThat(service.isEncryptionEnabled(), is(false));
assertThat(service.isEncryptionEnabled(), is(false));
}
public void testEncryptedChar() throws Exception {
CryptoService service = new CryptoService(settings, env);
assertThat(service.isEncryptionEnabled(), is(true));
assertThat(service.isEncryptionEnabled(), is(true));
assertThat(service.isEncrypted((char[]) null), is(false));
assertThat(service.isEncrypted(new char[0]), is(false));
@ -189,4 +192,20 @@ public class CryptoServiceTests extends ESTestCase {
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"));
}
}