diff --git a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java index d8f9714e483..9002a598a89 100644 --- a/elasticsearch/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java +++ b/elasticsearch/src/main/java/org/elasticsearch/xpack/security/crypto/CryptoService.java @@ -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 ENCRYPTION_ALGO_SETTING = - new Setting<>(setting("encryption.algorithm"), s -> DEFAULT_ENCRYPTION_ALGORITHM, s -> s, Setting.Property.NodeScope); - public static final Setting ENCRYPTION_KEY_LENGTH_SETTING = - Setting.intSetting(setting("encryption_key.length"), DEFAULT_KEY_LENGTH, Setting.Property.NodeScope); - public static final Setting ENCRYPTION_KEY_ALGO_SETTING = - new Setting<>(setting("encryption_key.algorithm"), DEFAULT_KEY_ALGORITH, s -> s, Setting.Property.NodeScope); + private static final Setting SYSTEM_KEY_REQUIRED_SETTING = + Setting.boolSetting(setting("system_key.required"), false, Property.NodeScope); + private static final Setting ENCRYPTION_ALGO_SETTING = + new Setting<>(setting("encryption.algorithm"), s -> DEFAULT_ENCRYPTION_ALGORITHM, s -> s, Property.NodeScope); + private static final Setting ENCRYPTION_KEY_LENGTH_SETTING = + Setting.intSetting(setting("encryption_key.length"), DEFAULT_KEY_LENGTH, Property.NodeScope); + private static final Setting 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); } } diff --git a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/crypto/CryptoServiceTests.java b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/crypto/CryptoServiceTests.java index 76f861f49a4..7bd07b434f3 100644 --- a/elasticsearch/src/test/java/org/elasticsearch/xpack/security/crypto/CryptoServiceTests.java +++ b/elasticsearch/src/test/java/org/elasticsearch/xpack/security/crypto/CryptoServiceTests.java @@ -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")); + } }