Moving secret values out of the main configuration file

This commit is contained in:
Sergey Skrobotov
2023-05-17 11:14:04 -07:00
parent 8d1c26d07d
commit 287e2fa89a
57 changed files with 959 additions and 551 deletions

View File

@@ -0,0 +1,154 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.configuration.secrets;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.fasterxml.jackson.databind.JsonMappingException;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.constraints.NotEmpty;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.whispersystems.textsecuregcm.util.ExactlySize;
import org.whispersystems.textsecuregcm.util.SystemMapper;
public class SecretsTest {
private static final String SECRET_REF = "secret_string";
private static final String SECRET_LIST_REF = "secret_string_list";
private static final String SECRET_BYTES_REF = "secret_bytes";
private static final String SECRET_BYTES_LIST_REF = "secret_bytes_list";
public record TestData(SecretString secret,
SecretBytes secretBytes,
SecretStringList secretList,
SecretBytesList secretBytesList) {
}
private static final String VALID_CONFIG_YAML = """
secret: secret://%s
secretBytes: secret://%s
secretList: secret://%s
secretBytesList: secret://%s
""".formatted(SECRET_REF, SECRET_BYTES_REF, SECRET_LIST_REF, SECRET_BYTES_LIST_REF);
@Test
public void testDeserialization() throws Exception {
final String secretString = "secret_string";
final byte[] secretBytes = RandomUtils.nextBytes(16);
final String secretBytesBase64 = Base64.getEncoder().encodeToString(secretBytes);
final List<String> secretStringList = List.of("secret1", "secret2", "secret3");
final List<byte[]> secretBytesList = List.of(RandomUtils.nextBytes(16), RandomUtils.nextBytes(16), RandomUtils.nextBytes(16));
final List<String> secretBytesListBase64 = secretBytesList.stream().map(Base64.getEncoder()::encodeToString).toList();
final Map<String, Secret<?>> storeMap = Map.of(
SECRET_REF, new SecretString(secretString),
SECRET_BYTES_REF, new SecretString(secretBytesBase64),
SECRET_LIST_REF, new SecretStringList(secretStringList),
SECRET_BYTES_LIST_REF, new SecretStringList(secretBytesListBase64)
);
SecretsModule.INSTANCE.setSecretStore(new SecretStore(storeMap));
final TestData result = SystemMapper.yamlMapper().readValue(VALID_CONFIG_YAML, TestData.class);
assertEquals(secretString, result.secret().value());
assertEquals(secretStringList, result.secretList().value());
assertArrayEquals(secretBytes, result.secretBytes().value());
for (int i = 0; i < secretBytesList.size(); i++) {
assertArrayEquals(secretBytesList.get(i), result.secretBytesList().value().get(i));
}
}
@Test
public void testValueWithoutPrefix() throws Exception {
final String config = """
secret: ref
""";
SecretsModule.INSTANCE.setSecretStore(new SecretStore(Collections.emptyMap()));
assertThrows(JsonMappingException.class, () -> SystemMapper.yamlMapper().readValue(config, TestData.class));
}
@Test
public void testNoSecretInTheStore() throws Exception {
final String config = """
secret: secret://missing
secretBytes: secret://missing
secretList: secret://missing
secretBytesList: secret://missing
""";
SecretsModule.INSTANCE.setSecretStore(new SecretStore(Collections.emptyMap()));
assertThrows(JsonMappingException.class, () -> SystemMapper.yamlMapper().readValue(config, TestData.class));
}
@Test
public void testSecretStoreNotSet() throws Exception {
assertThrows(JsonMappingException.class, () -> SystemMapper.yamlMapper().readValue(VALID_CONFIG_YAML, TestData.class));
}
@Test
public void testReadFromJson() throws Exception {
// checking that valid json secrets bundle is read correctly
final SecretStore secretStore = SecretStore.fromYamlStringSecretsBundle("""
secret_string: value
secret_string_list:
- value1
- value2
- value3
""");
assertEquals("value", secretStore.secretString("secret_string").value());
assertEquals(List.of("value1", "value2", "value3"), secretStore.secretStringList("secret_string_list").value());
// checking that secrets bundle can't have objects as values
assertThrows(IllegalArgumentException.class, () -> SecretStore.fromYamlStringSecretsBundle("""
secret_string: value
not_a_string_or_list:
k: v
"""));
// checking that secrets bundle can't have numbers as values
assertThrows(IllegalArgumentException.class, () -> SecretStore.fromYamlStringSecretsBundle("""
secret_string: value
not_a_string_or_list: 42
"""));
}
record NotEmptySecretStringList(@NotEmpty SecretStringList secret) {
}
record NotEmptySecretBytesList(@NotEmpty SecretBytesList secret) {
}
record ExactlySizeBytesSecret(@ExactlySize(32) SecretBytes secret) {
}
@Test
public void testValidators() throws Exception {
final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
// @NotEmpty SecretStringList
assertFalse(validator.validate(new NotEmptySecretStringList(new SecretStringList(List.of()))).isEmpty());
assertTrue(validator.validate(new NotEmptySecretStringList(new SecretStringList(List.of("smth")))).isEmpty());
// @NotEmpty SecretBytesList
assertFalse(validator.validate(new NotEmptySecretBytesList(new SecretBytesList(List.of()))).isEmpty());
assertTrue(validator.validate(new NotEmptySecretBytesList(new SecretBytesList(List.of(new byte[4])))).isEmpty());
// @ExactlySize SecretBytes
assertFalse(validator.validate(new ExactlySizeBytesSecret(new SecretBytes(new byte[16]))).isEmpty());
assertTrue(validator.validate(new ExactlySizeBytesSecret(new SecretBytes(new byte[32]))).isEmpty());
}
}