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:
parent
1de85f4740
commit
714b891b03
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue