From 1932966ff7dec69ec76d1637dc8fedac37e686fb Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Tue, 24 Dec 2019 15:26:37 +0000 Subject: [PATCH 1/5] Add generalised key vault config that contains a map of generalised properties. Add validator for key vault config that is aware of vault type and is able to apply validations for that given type. Deprecate the Hashicorp and Azurekey vault config. Service factories use KeyVaultConfig rather than its deprecated implementations --- .../tessera/config/cli/OverrideUtilTest.java | 3 +- .../tessera/config/AzureKeyVaultConfig.java | 2 + .../tessera/config/DefaultKeyVaultConfig.java | 47 +++++++++ .../config/HashicorpKeyVaultConfig.java | 17 ++-- .../tessera/config/KeyConfiguration.java | 16 +++ .../quorum/tessera/config/KeyVaultConfig.java | 22 +++++ .../config/KeyVaultConfigConverter.java | 41 ++++++++ .../constraints/KeyVaultConfigValidator.java | 71 ++++++++++++++ .../constraints/ValidKeyVaultConfig.java | 22 +++++ .../quorum/tessera/config/util/JaxbUtil.java | 3 +- .../config/DefaultKeyVaultConfigTest.java | 32 ++++++ .../config/KeyVaultConfigConverterTest.java | 44 +++++++++ .../tessera/config/KeyVaultConfigTest.java | 61 ++++++++++++ .../quorum/tessera/config/OpenPojoTest.java | 1 + .../KeyVaultConfigValidatorTest.java | 97 +++++++++++++++++++ .../key/vault/azure/AzureKeyVaultService.java | 10 +- .../azure/AzureKeyVaultServiceFactory.java | 4 +- .../AzureKeyVaultServiceFactoryTest.java | 7 +- .../vault/azure/AzureKeyVaultServiceTest.java | 10 +- .../HashicorpKeyVaultServiceFactory.java | 11 ++- .../HashicorpKeyVaultServiceFactoryUtil.java | 24 +++-- .../HashicorpKeyVaultServiceFactoryTest.java | 36 +++---- ...shicorpKeyVaultServiceFactoryUtilTest.java | 42 ++++---- 23 files changed, 552 insertions(+), 71 deletions(-) create mode 100644 config/src/main/java/com/quorum/tessera/config/DefaultKeyVaultConfig.java create mode 100644 config/src/main/java/com/quorum/tessera/config/KeyVaultConfigConverter.java create mode 100644 config/src/main/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidator.java create mode 100644 config/src/main/java/com/quorum/tessera/config/constraints/ValidKeyVaultConfig.java create mode 100644 config/src/test/java/com/quorum/tessera/config/DefaultKeyVaultConfigTest.java create mode 100644 config/src/test/java/com/quorum/tessera/config/KeyVaultConfigConverterTest.java create mode 100644 config/src/test/java/com/quorum/tessera/config/KeyVaultConfigTest.java create mode 100644 config/src/test/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidatorTest.java diff --git a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java index a44319b864..c85a5b9349 100644 --- a/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java +++ b/cli/config-cli/src/test/java/com/quorum/tessera/config/cli/OverrideUtilTest.java @@ -168,7 +168,8 @@ public void buildOptions() { "server.influxConfig.sslConfig.sslConfigType", "server.influxConfig.sslConfig.excludeCipherSuites", "features.enableRemoteKeyValidation", - "encryptor.type"); + "encryptor.type", + "keys.keyVaultConfig.keyVaultType"); final Map results = OverrideUtil.buildConfigOptions(); diff --git a/config/src/main/java/com/quorum/tessera/config/AzureKeyVaultConfig.java b/config/src/main/java/com/quorum/tessera/config/AzureKeyVaultConfig.java index 557b978599..c2ba57b69a 100644 --- a/config/src/main/java/com/quorum/tessera/config/AzureKeyVaultConfig.java +++ b/config/src/main/java/com/quorum/tessera/config/AzureKeyVaultConfig.java @@ -6,6 +6,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; +@Deprecated @XmlAccessorType(XmlAccessType.FIELD) public class AzureKeyVaultConfig extends ConfigItem implements KeyVaultConfig { @@ -34,4 +35,5 @@ public KeyVaultType getKeyVaultType() { return KeyVaultType.AZURE; } + } diff --git a/config/src/main/java/com/quorum/tessera/config/DefaultKeyVaultConfig.java b/config/src/main/java/com/quorum/tessera/config/DefaultKeyVaultConfig.java new file mode 100644 index 0000000000..960021a7ce --- /dev/null +++ b/config/src/main/java/com/quorum/tessera/config/DefaultKeyVaultConfig.java @@ -0,0 +1,47 @@ +package com.quorum.tessera.config; + +import com.quorum.tessera.config.adapters.MapAdapter; + +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@XmlType(name = "keyVaultConfig") +@XmlAccessorType(XmlAccessType.FIELD) +public class DefaultKeyVaultConfig extends ConfigItem implements KeyVaultConfig { + + @NotNull + @XmlAttribute + private KeyVaultType keyVaultType; + + @XmlJavaTypeAdapter(MapAdapter.class) + @XmlElement + private Map properties = new HashMap<>(); + + public DefaultKeyVaultConfig() {} + + public DefaultKeyVaultConfig setProperty(String name, String value) { + this.properties.put(name, value); + return this; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + public Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + @Override + public KeyVaultType getKeyVaultType() { + return keyVaultType; + } + + public void setKeyVaultType(KeyVaultType keyVaultType) { + this.keyVaultType = keyVaultType; + } +} diff --git a/config/src/main/java/com/quorum/tessera/config/HashicorpKeyVaultConfig.java b/config/src/main/java/com/quorum/tessera/config/HashicorpKeyVaultConfig.java index 88d172b76f..4dd83f637a 100644 --- a/config/src/main/java/com/quorum/tessera/config/HashicorpKeyVaultConfig.java +++ b/config/src/main/java/com/quorum/tessera/config/HashicorpKeyVaultConfig.java @@ -12,17 +12,13 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.nio.file.Path; +@Deprecated @XmlAccessorType(XmlAccessType.FIELD) public class HashicorpKeyVaultConfig extends ConfigItem implements KeyVaultConfig { - @Valid - @NotNull - @XmlAttribute - private String url; + @Valid @NotNull @XmlAttribute private String url; - @Valid - @XmlElement - private String approlePath; + @Valid @XmlElement private String approlePath; @Valid @ValidPath(checkExists = true, message = "File does not exist") @@ -43,8 +39,7 @@ public HashicorpKeyVaultConfig(String url, String approlePath, Path tlsKeyStoreP this.tlsTrustStorePath = tlsTrustStorePath; } - public HashicorpKeyVaultConfig() { - } + public HashicorpKeyVaultConfig() {} public String getUrl() { return this.url; @@ -71,7 +66,7 @@ public void setTlsTrustStorePath(Path tlsTrustStorePath) { } public String getApprolePath() { - if(approlePath == null) { + if (approlePath == null) { return "approle"; } return approlePath; @@ -85,4 +80,6 @@ public void setApprolePath(String approlePath) { public KeyVaultType getKeyVaultType() { return KeyVaultType.HASHICORP; } + + } diff --git a/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java b/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java index 5127d7f771..c141ccbb46 100644 --- a/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java +++ b/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java @@ -2,6 +2,7 @@ import com.quorum.tessera.config.adapters.KeyDataAdapter; import com.quorum.tessera.config.adapters.PathAdapter; +import com.quorum.tessera.config.constraints.ValidKeyVaultConfig; import com.quorum.tessera.config.constraints.ValidPath; import com.quorum.tessera.config.keypairs.ConfigKeyPair; @@ -35,6 +36,8 @@ public class KeyConfiguration extends ConfigItem { @XmlJavaTypeAdapter(KeyDataAdapter.class) private List<@Valid ConfigKeyPair> keyData; + @ValidKeyVaultConfig @XmlElement private DefaultKeyVaultConfig keyVaultConfig; + @Valid @XmlElement private AzureKeyVaultConfig azureKeyVaultConfig; @Valid @XmlElement private HashicorpKeyVaultConfig hashicorpKeyVaultConfig; @@ -50,6 +53,11 @@ public KeyConfiguration( this.keyData = keyData; this.azureKeyVaultConfig = azureKeyVaultConfig; this.hashicorpKeyVaultConfig = hashicorpKeyVaultConfig; + if(null != azureKeyVaultConfig) { + this.keyVaultConfig = KeyVaultConfigConverter.convert(azureKeyVaultConfig); + } else if(null != hashicorpKeyVaultConfig) { + this.keyVaultConfig = KeyVaultConfigConverter.convert(hashicorpKeyVaultConfig); + } } public KeyConfiguration() {} @@ -93,4 +101,12 @@ public void setAzureKeyVaultConfig(AzureKeyVaultConfig azureKeyVaultConfig) { public void setHashicorpKeyVaultConfig(HashicorpKeyVaultConfig hashicorpKeyVaultConfig) { this.hashicorpKeyVaultConfig = hashicorpKeyVaultConfig; } + + public DefaultKeyVaultConfig getKeyVaultConfig() { + return keyVaultConfig; + } + + public void setKeyVaultConfig(DefaultKeyVaultConfig keyVaultConfig) { + this.keyVaultConfig = keyVaultConfig; + } } diff --git a/config/src/main/java/com/quorum/tessera/config/KeyVaultConfig.java b/config/src/main/java/com/quorum/tessera/config/KeyVaultConfig.java index f88a14fa00..2636f164de 100644 --- a/config/src/main/java/com/quorum/tessera/config/KeyVaultConfig.java +++ b/config/src/main/java/com/quorum/tessera/config/KeyVaultConfig.java @@ -1,7 +1,29 @@ package com.quorum.tessera.config; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + public interface KeyVaultConfig { KeyVaultType getKeyVaultType(); + default Map getProperties() { + return Collections.EMPTY_MAP; + } + + default boolean hasProperty(String... name) { + return Stream.of(name) + .allMatch(n -> getProperties().containsKey(n)); + } + + default Optional getProperty(String name) { + return Optional.ofNullable(getProperties().get(name)); + } + + static KeyVaultConfig create() { + return new DefaultKeyVaultConfig(); + } + } diff --git a/config/src/main/java/com/quorum/tessera/config/KeyVaultConfigConverter.java b/config/src/main/java/com/quorum/tessera/config/KeyVaultConfigConverter.java new file mode 100644 index 0000000000..5829db809f --- /dev/null +++ b/config/src/main/java/com/quorum/tessera/config/KeyVaultConfigConverter.java @@ -0,0 +1,41 @@ +package com.quorum.tessera.config; + +import java.util.Objects; +import java.util.Optional; + +public interface KeyVaultConfigConverter { + + /* + TODO: Remove these when AzureKeyVaultConfig is removed + */ + static DefaultKeyVaultConfig convert(AzureKeyVaultConfig azureKeyVaultConfig) { + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + config.setKeyVaultType(azureKeyVaultConfig.getKeyVaultType()); + config.setProperty("url", azureKeyVaultConfig.getUrl()); + return config; + } + + /* + TODO: Remove these when HashicorpKeyVaultConfig is removed + */ + static DefaultKeyVaultConfig convert(HashicorpKeyVaultConfig hashicorpKeyVaultConfig) { + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + config.setKeyVaultType(hashicorpKeyVaultConfig.getKeyVaultType()); + config.setProperty("url",hashicorpKeyVaultConfig.getUrl()); + config.setProperty("approlePath",hashicorpKeyVaultConfig.getApprolePath()); + + Optional.ofNullable(hashicorpKeyVaultConfig.getTlsKeyStorePath()) + .map(Objects::toString) + .ifPresent(v -> { + config.setProperty("tlsKeyStorePath",v); + }); + + Optional.ofNullable(hashicorpKeyVaultConfig.getTlsTrustStorePath()) + .map(Objects::toString) + .ifPresent(v -> { + config.setProperty("tlsTrustStorePath",v); + }); + + return config; + } +} diff --git a/config/src/main/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidator.java b/config/src/main/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidator.java new file mode 100644 index 0000000000..8256106884 --- /dev/null +++ b/config/src/main/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidator.java @@ -0,0 +1,71 @@ +package com.quorum.tessera.config.constraints; + +import com.quorum.tessera.config.DefaultKeyVaultConfig; +import com.quorum.tessera.config.KeyVaultType; + +import javax.validation.ConstraintValidator; +import javax.validation.ConstraintValidatorContext; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +// Define here to be used during path validation +@ValidPath(checkExists = true, message = "File does not exist") +public class KeyVaultConfigValidator implements ConstraintValidator { + + private ValidKeyVaultConfig config; + + @Override + public void initialize(ValidKeyVaultConfig config) { + this.config = config; + } + + @Override + public boolean isValid( + DefaultKeyVaultConfig keyVaultConfig, ConstraintValidatorContext constraintValidatorContext) { + if (keyVaultConfig == null || keyVaultConfig.getKeyVaultType() == null) { + return true; + } + + KeyVaultType keyVaultType = keyVaultConfig.getKeyVaultType(); + + List outcomes = new ArrayList<>(); + if (keyVaultType == KeyVaultType.AZURE) { + + if (!keyVaultConfig.getProperties().containsKey("url")) { + constraintValidatorContext.disableDefaultConstraintViolation(); + constraintValidatorContext + .buildConstraintViolationWithTemplate("URL is required") + .addConstraintViolation(); + outcomes.add(Boolean.FALSE); + } + } + + if (keyVaultType == KeyVaultType.HASHICORP) { + + if (!keyVaultConfig.getProperties().containsKey("url")) { + constraintValidatorContext.disableDefaultConstraintViolation(); + constraintValidatorContext + .buildConstraintViolationWithTemplate("URL is required") + .addConstraintViolation(); + outcomes.add(Boolean.FALSE); + } + + PathValidator pathValidator = new PathValidator(); + ValidPath validPath = this.getClass().getAnnotation(ValidPath.class); + pathValidator.initialize(validPath); + + if (keyVaultConfig.getProperties().containsKey("tlsKeyStorePath")) { + Path tlsKeyStorePath = Paths.get(keyVaultConfig.getProperties().get("tlsKeyStorePath")); + outcomes.add(pathValidator.isValid(tlsKeyStorePath, constraintValidatorContext)); + } + if (keyVaultConfig.getProperties().containsKey("tlsTrustStorePath")) { + Path tlsKeyStorePath = Paths.get(keyVaultConfig.getProperties().get("tlsTrustStorePath")); + outcomes.add(pathValidator.isValid(tlsKeyStorePath, constraintValidatorContext)); + } + } + + return outcomes.stream().allMatch(Boolean::booleanValue); + } +} diff --git a/config/src/main/java/com/quorum/tessera/config/constraints/ValidKeyVaultConfig.java b/config/src/main/java/com/quorum/tessera/config/constraints/ValidKeyVaultConfig.java new file mode 100644 index 0000000000..73c8cadbe6 --- /dev/null +++ b/config/src/main/java/com/quorum/tessera/config/constraints/ValidKeyVaultConfig.java @@ -0,0 +1,22 @@ +package com.quorum.tessera.config.constraints; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({FIELD, PARAMETER, ANNOTATION_TYPE}) +@Retention(RUNTIME) +@Constraint(validatedBy = KeyVaultConfigValidator.class) +@Documented +public @interface ValidKeyVaultConfig { + String message() default "{ValidKeyVaultConfig.message}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/config/src/main/java/com/quorum/tessera/config/util/JaxbUtil.java b/config/src/main/java/com/quorum/tessera/config/util/JaxbUtil.java index 388c983e36..f9b021d111 100644 --- a/config/src/main/java/com/quorum/tessera/config/util/JaxbUtil.java +++ b/config/src/main/java/com/quorum/tessera/config/util/JaxbUtil.java @@ -40,7 +40,8 @@ public final class JaxbUtil { SslAuthenticationMode.class, SslConfig.class, SslTrustMode.class, - ConfigProperties.class + ConfigProperties.class, + DefaultKeyVaultConfig.class }; private JaxbUtil() {} diff --git a/config/src/test/java/com/quorum/tessera/config/DefaultKeyVaultConfigTest.java b/config/src/test/java/com/quorum/tessera/config/DefaultKeyVaultConfigTest.java new file mode 100644 index 0000000000..8ff6b47063 --- /dev/null +++ b/config/src/test/java/com/quorum/tessera/config/DefaultKeyVaultConfigTest.java @@ -0,0 +1,32 @@ +package com.quorum.tessera.config; + +import com.quorum.tessera.config.util.JaxbUtil; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class DefaultKeyVaultConfigTest { + + @Test + public void defaultInstance() { + DefaultKeyVaultConfig defaultKeyVaultConfig = new DefaultKeyVaultConfig(); + assertThat(defaultKeyVaultConfig.getKeyVaultType()).isNull(); + assertThat(defaultKeyVaultConfig.getProperties()).isEmpty(); + } + + @Test + public void keyVaultConfigDefaultGetPropertiesIsEmpty() { + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); + assertThat(keyVaultConfig.getKeyVaultType()).isNull(); + assertThat(keyVaultConfig.getProperties()).isEmpty(); + } + + @Test + public void doStuff() { + DefaultKeyVaultConfig defaultKeyVaultConfig = new DefaultKeyVaultConfig(); + defaultKeyVaultConfig.setKeyVaultType(KeyVaultType.HASHICORP); + defaultKeyVaultConfig.setProperty("url","someurl"); + JaxbUtil.marshalWithNoValidation(defaultKeyVaultConfig, System.out); + } +} diff --git a/config/src/test/java/com/quorum/tessera/config/KeyVaultConfigConverterTest.java b/config/src/test/java/com/quorum/tessera/config/KeyVaultConfigConverterTest.java new file mode 100644 index 0000000000..f09461070a --- /dev/null +++ b/config/src/test/java/com/quorum/tessera/config/KeyVaultConfigConverterTest.java @@ -0,0 +1,44 @@ +package com.quorum.tessera.config; + +import org.junit.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.assertj.core.api.Assertions.assertThat; + +public class KeyVaultConfigConverterTest { + + @Test + public void convertAzure() { + AzureKeyVaultConfig azureKeyVaultConfig = new AzureKeyVaultConfig(); + azureKeyVaultConfig.setUrl("SomeUrl"); + + DefaultKeyVaultConfig result = KeyVaultConfigConverter.convert(azureKeyVaultConfig); + + assertThat(result.getKeyVaultType()).isEqualTo(KeyVaultType.AZURE); + assertThat(result.getProperties()).containsKeys("url"); + assertThat(result.getProperties().get("url")).isEqualTo("SomeUrl"); + } + + + @Test + public void convertHashicorp() { + HashicorpKeyVaultConfig hashicorpKeyVaultConfig = new HashicorpKeyVaultConfig(); + hashicorpKeyVaultConfig.setUrl("SomeUrl"); + Path path = Paths.get("SomePath"); + hashicorpKeyVaultConfig.setTlsKeyStorePath(path); + hashicorpKeyVaultConfig.setTlsTrustStorePath(path); + + DefaultKeyVaultConfig result = KeyVaultConfigConverter.convert(hashicorpKeyVaultConfig); + + assertThat(result.getKeyVaultType()).isEqualTo(KeyVaultType.HASHICORP); + assertThat(result.getProperties()).containsKeys("url","tlsKeyStorePath","tlsTrustStorePath"); + assertThat(result.getProperties().get("url")).isEqualTo(hashicorpKeyVaultConfig.getUrl()); + assertThat(result.getProperties().get("tlsTrustStorePath")).isEqualTo(path.toString()); + assertThat(result.getProperties().get("tlsKeyStorePath")).isEqualTo(path.toString()); + assertThat(result.getProperties().get("approlePath")).isEqualTo(hashicorpKeyVaultConfig.getApprolePath()); + } + + +} diff --git a/config/src/test/java/com/quorum/tessera/config/KeyVaultConfigTest.java b/config/src/test/java/com/quorum/tessera/config/KeyVaultConfigTest.java new file mode 100644 index 0000000000..45afb0094c --- /dev/null +++ b/config/src/test/java/com/quorum/tessera/config/KeyVaultConfigTest.java @@ -0,0 +1,61 @@ +package com.quorum.tessera.config; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +public class KeyVaultConfigTest { + + @Test + public void hasAndGetProperty() { + + final Map props = new HashMap<>(); + props.put("greeting","Hellow"); + + + KeyVaultConfig keyVaultConfig = new KeyVaultConfig() { + @Override + public KeyVaultType getKeyVaultType() { + return null; + } + + @Override + public Map getProperties() { + return props; + } + }; + + + assertThat(keyVaultConfig.hasProperty("greeting")).isTrue(); + assertThat(keyVaultConfig.getProperty("greeting")).isPresent(); + assertThat(keyVaultConfig.getProperties()).isSameAs(props); + assertThat(keyVaultConfig.getProperty("greeting").get()).isEqualTo("Hellow"); + + } + @Test + public void create() { + KeyVaultConfig keyVaultConfig = KeyVaultConfig.create(); + assertThat(keyVaultConfig).isNotNull() + .isExactlyInstanceOf(DefaultKeyVaultConfig.class); + assertThat(keyVaultConfig.getProperties()).isEmpty(); + + } + + @Test + public void defaultProperiesIsEmpty() { + KeyVaultConfig keyVaultConfig = new KeyVaultConfig() { + + @Override + public KeyVaultType getKeyVaultType() { + return null; + } + }; + + assertThat(keyVaultConfig.getProperties()).isEmpty(); + + } + +} diff --git a/config/src/test/java/com/quorum/tessera/config/OpenPojoTest.java b/config/src/test/java/com/quorum/tessera/config/OpenPojoTest.java index e3a9b2b2ed..4553178c6e 100644 --- a/config/src/test/java/com/quorum/tessera/config/OpenPojoTest.java +++ b/config/src/test/java/com/quorum/tessera/config/OpenPojoTest.java @@ -25,6 +25,7 @@ public void executeOpenPojoValidations() { final PojoClassFilter[] filters = new PojoClassFilter[] { + pc -> !pc.getClazz().getName().contains(KeyVaultConfigTest.class.getSimpleName()), pc -> !pc.getClazz().isAssignableFrom(ObjectFactory.class), pc -> !pc.getClazz().getName().startsWith(JaxbConfigFactory.class.getName()), pc -> !pc.getClazz().isAssignableFrom(ConfigException.class), diff --git a/config/src/test/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidatorTest.java b/config/src/test/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidatorTest.java new file mode 100644 index 0000000000..dcd4e67ad6 --- /dev/null +++ b/config/src/test/java/com/quorum/tessera/config/constraints/KeyVaultConfigValidatorTest.java @@ -0,0 +1,97 @@ +package com.quorum.tessera.config.constraints; + +import com.quorum.tessera.config.DefaultKeyVaultConfig; +import com.quorum.tessera.config.KeyVaultType; +import org.junit.Before; +import org.junit.Test; + +import javax.validation.ConstraintValidatorContext; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class KeyVaultConfigValidatorTest { + + private KeyVaultConfigValidator keyVaultConfigValidator; + + private ConstraintValidatorContext context; + + @Before + public void setUp() { + context = mock(ConstraintValidatorContext.class); + + ConstraintValidatorContext.ConstraintViolationBuilder builder = + mock(ConstraintValidatorContext.ConstraintViolationBuilder.class); + when(context.buildConstraintViolationWithTemplate(any(String.class))).thenReturn(builder); + + keyVaultConfigValidator = new KeyVaultConfigValidator(); + + ValidKeyVaultConfig validKeyVaultConfig = mock(ValidKeyVaultConfig.class); + keyVaultConfigValidator.initialize(validKeyVaultConfig); + } + + @Test + public void nullKeyConfigurationIsAllowedAndWillBePickedUpByNotNullAnnotation() { + assertThat(keyVaultConfigValidator.isValid(null, context)).isTrue(); + } + + @Test + public void nullKeyVaultTypeIsAllowedAndWillBePickedUpByNotNullAnnotation() { + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + + assertThat(keyVaultConfigValidator.isValid(config, context)).isTrue(); + } + + @Test + public void validAzureConfig() { + + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + config.setKeyVaultType(KeyVaultType.AZURE); + config.setProperty("url", "someurl"); + + assertThat(keyVaultConfigValidator.isValid(config, context)).isTrue(); + } + + @Test + public void invalidAzureConfig() { + + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + config.setKeyVaultType(KeyVaultType.AZURE); + + assertThat(keyVaultConfigValidator.isValid(config, context)).isFalse(); + } + + @Test + public void validHahicorpConfig() throws Exception { + + Path somePath = Files.createTempFile(UUID.randomUUID().toString(), ".txt"); + somePath.toFile().deleteOnExit(); + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + config.setKeyVaultType(KeyVaultType.HASHICORP); + config.setProperty("url", "someurl"); + config.setProperty("tlsKeyStorePath", somePath.toString()); + config.setProperty("tlsTrustStorePath", somePath.toString()); + + assertThat(keyVaultConfigValidator.isValid(config, context)).isTrue(); + } + + @Test + public void invalidHahicorpConfig() throws Exception { + + Path somePath = mock(Path.class); + + DefaultKeyVaultConfig config = new DefaultKeyVaultConfig(); + config.setKeyVaultType(KeyVaultType.HASHICORP); + + config.setProperty("tlsKeyStorePath", somePath.toString()); + config.setProperty("tlsTrustStorePath", somePath.toString()); + + assertThat(keyVaultConfigValidator.isValid(config, context)).isFalse(); + } +} diff --git a/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java b/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java index 560fc006c9..5046b982e3 100644 --- a/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java +++ b/key-vault/azure-key-vault/src/main/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultService.java @@ -2,7 +2,7 @@ import com.microsoft.azure.keyvault.models.SecretBundle; import com.microsoft.azure.keyvault.requests.SetSecretRequest; -import com.quorum.tessera.config.AzureKeyVaultConfig; +import com.quorum.tessera.config.KeyVaultConfig; import com.quorum.tessera.config.vault.data.AzureGetSecretData; import com.quorum.tessera.config.vault.data.AzureSetSecretData; import com.quorum.tessera.key.vault.KeyVaultService; @@ -16,8 +16,12 @@ public class AzureKeyVaultService implements KeyVaultService new ConfigException(new RuntimeException("Trying to create Azure key vault connection but no Azure configuration provided"))); return new AzureKeyVaultService( diff --git a/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceFactoryTest.java b/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceFactoryTest.java index 0dbbf9c2e5..3d3c9fb19e 100644 --- a/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceFactoryTest.java +++ b/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceFactoryTest.java @@ -6,6 +6,8 @@ import org.junit.Before; import org.junit.Test; +import java.util.Optional; + import static com.quorum.tessera.config.util.EnvironmentVariables.AZURE_CLIENT_ID; import static com.quorum.tessera.config.util.EnvironmentVariables.AZURE_CLIENT_SECRET; import static org.assertj.core.api.Assertions.assertThat; @@ -100,8 +102,9 @@ public void nullKeyVaultConfigurationThrowsException() { public void envVarsAndKeyVaultConfigProvidedCreatesAzureKeyVaultService() { when(envProvider.getEnv(anyString())).thenReturn("envVar"); KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); - AzureKeyVaultConfig keyVaultConfig = mock(AzureKeyVaultConfig.class); - when(keyConfiguration.getAzureKeyVaultConfig()).thenReturn(keyVaultConfig); + DefaultKeyVaultConfig keyVaultConfig = mock(DefaultKeyVaultConfig.class); + when(keyVaultConfig.getProperty("url")).thenReturn(Optional.of("URL")); + when(keyConfiguration.getKeyVaultConfig()).thenReturn(keyVaultConfig); when(config.getKeys()).thenReturn(keyConfiguration); KeyVaultService result = azureKeyVaultServiceFactory.create(config, envProvider); diff --git a/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java b/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java index 882a9319d2..ac158d4046 100644 --- a/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java +++ b/key-vault/azure-key-vault/src/test/java/com/quorum/tessera/key/vault/azure/AzureKeyVaultServiceTest.java @@ -2,7 +2,7 @@ import com.microsoft.azure.keyvault.models.SecretBundle; import com.microsoft.azure.keyvault.requests.SetSecretRequest; -import com.quorum.tessera.config.AzureKeyVaultConfig; +import com.quorum.tessera.config.KeyVaultConfig; import com.quorum.tessera.config.vault.data.AzureGetSecretData; import com.quorum.tessera.config.vault.data.AzureSetSecretData; import com.quorum.tessera.key.vault.VaultSecretNotFoundException; @@ -10,6 +10,8 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; import static org.mockito.Mockito.*; @@ -22,13 +24,13 @@ public class AzureKeyVaultServiceTest { private String vaultUrl = "url"; - private AzureKeyVaultConfig keyVaultConfig; + private KeyVaultConfig keyVaultConfig; @Before public void setUp() { this.azureKeyVaultClientDelegate = mock(AzureKeyVaultClientDelegate.class); - this.keyVaultConfig = mock(AzureKeyVaultConfig.class); - when(keyVaultConfig.getUrl()).thenReturn(vaultUrl); + this.keyVaultConfig = mock(KeyVaultConfig.class); + when(keyVaultConfig.getProperty("url")).thenReturn(Optional.of(vaultUrl)); this.keyVaultService = new AzureKeyVaultService(keyVaultConfig, azureKeyVaultClientDelegate); } diff --git a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java index a43b792c26..3f462c7df3 100644 --- a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java +++ b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java @@ -14,8 +14,10 @@ import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.SslConfiguration; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; @@ -50,15 +52,16 @@ else if(isOnlyOneInputNull(roleId, secretId)) { throw new HashicorpCredentialNotSetException("Only one of the " + HASHICORP_ROLE_ID + " and " + HASHICORP_SECRET_ID + " environment variables to authenticate with Hashicorp Vault using the AppRole method has been set"); } - HashicorpKeyVaultConfig keyVaultConfig = Optional.ofNullable(config.getKeys()) - .map(KeyConfiguration::getHashicorpKeyVaultConfig) + KeyVaultConfig keyVaultConfig = Optional.ofNullable(config.getKeys()) + .map(KeyConfiguration::getKeyVaultConfig) .orElseThrow(() -> new ConfigException(new RuntimeException("Trying to create Hashicorp Vault connection but no Vault configuration provided"))); VaultEndpoint vaultEndpoint; try { - vaultEndpoint = VaultEndpoint.from(new URI(keyVaultConfig.getUrl())); - } catch (URISyntaxException | IllegalArgumentException e) { + URI uri = new URI(keyVaultConfig.getProperty("url").get()); + vaultEndpoint = VaultEndpoint.from(uri); + } catch (URISyntaxException | NoSuchElementException | IllegalArgumentException e) { throw new ConfigException(new RuntimeException("Provided Hashicorp Vault url is incorrectly formatted", e)); } diff --git a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtil.java b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtil.java index c148863d6a..9394291f45 100644 --- a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtil.java +++ b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtil.java @@ -1,6 +1,6 @@ package com.quorum.tessera.key.vault.hashicorp; -import com.quorum.tessera.config.HashicorpKeyVaultConfig; +import com.quorum.tessera.config.KeyVaultConfig; import com.quorum.tessera.config.util.EnvironmentVariableProvider; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; @@ -16,17 +16,22 @@ import org.springframework.vault.support.SslConfiguration; import org.springframework.web.client.RestOperations; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Objects; import static com.quorum.tessera.config.util.EnvironmentVariables.*; class HashicorpKeyVaultServiceFactoryUtil { - SslConfiguration configureSsl(HashicorpKeyVaultConfig keyVaultConfig, EnvironmentVariableProvider envProvider) { - if(keyVaultConfig.getTlsKeyStorePath() != null && keyVaultConfig.getTlsTrustStorePath() != null) { + SslConfiguration configureSsl(KeyVaultConfig keyVaultConfig, EnvironmentVariableProvider envProvider) { + if(keyVaultConfig.hasProperty("tlsKeyStorePath","tlsTrustStorePath")) { - Resource clientKeyStore = new FileSystemResource(keyVaultConfig.getTlsKeyStorePath().toFile()); - Resource clientTrustStore = new FileSystemResource(keyVaultConfig.getTlsTrustStorePath().toFile()); + Path tlsKeyStorePath = keyVaultConfig.getProperty("tlsKeyStorePath").map(Paths::get).get(); + Path tlsTrustStorePath = keyVaultConfig.getProperty("tlsTrustStorePath").map(Paths::get).get(); + + Resource clientKeyStore = new FileSystemResource(tlsKeyStorePath.toFile()); + Resource clientTrustStore = new FileSystemResource(tlsTrustStorePath.toFile()); SslConfiguration.KeyStoreConfiguration keyStoreConfiguration = SslConfiguration.KeyStoreConfiguration.of( clientKeyStore, @@ -40,9 +45,10 @@ SslConfiguration configureSsl(HashicorpKeyVaultConfig keyVaultConfig, Environmen return new SslConfiguration(keyStoreConfiguration, trustStoreConfiguration); - } else if (keyVaultConfig.getTlsTrustStorePath() != null) { + } else if (keyVaultConfig.hasProperty("tlsTrustStorePath")) { - Resource clientTrustStore = new FileSystemResource(keyVaultConfig.getTlsTrustStorePath().toFile()); + Path tlsTrustStorePath = keyVaultConfig.getProperty("tlsTrustStorePath").map(Paths::get).get(); + Resource clientTrustStore = new FileSystemResource(tlsTrustStorePath.toFile()); return SslConfiguration.forTrustStore(clientTrustStore, envProvider.getEnvAsCharArray(HASHICORP_CLIENT_TRUSTSTORE_PWD)); @@ -55,7 +61,7 @@ ClientHttpRequestFactory createClientHttpRequestFactory(ClientOptions clientOpti return ClientHttpRequestFactoryFactory.create(clientOptions, sslConfiguration); } - ClientAuthentication configureClientAuthentication(HashicorpKeyVaultConfig keyVaultConfig, EnvironmentVariableProvider envProvider, ClientHttpRequestFactory clientHttpRequestFactory, VaultEndpoint vaultEndpoint) { + ClientAuthentication configureClientAuthentication(KeyVaultConfig keyVaultConfig, EnvironmentVariableProvider envProvider, ClientHttpRequestFactory clientHttpRequestFactory, VaultEndpoint vaultEndpoint) { final String roleId = envProvider.getEnv(HASHICORP_ROLE_ID); final String secretId = envProvider.getEnv(HASHICORP_SECRET_ID); @@ -64,7 +70,7 @@ ClientAuthentication configureClientAuthentication(HashicorpKeyVaultConfig keyVa if(roleId != null && secretId != null) { AppRoleAuthenticationOptions appRoleAuthenticationOptions = AppRoleAuthenticationOptions.builder() - .path(keyVaultConfig.getApprolePath()) + .path(keyVaultConfig.getProperty("approlePath").get()) .roleId(AppRoleAuthenticationOptions.RoleId.provided(roleId)) .secretId(AppRoleAuthenticationOptions.SecretId.provided(secretId)) .build(); diff --git a/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryTest.java b/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryTest.java index fd33a4fd2f..81f63970df 100644 --- a/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryTest.java +++ b/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryTest.java @@ -11,6 +11,8 @@ import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.SslConfiguration; +import java.util.Optional; + import static com.quorum.tessera.config.util.EnvironmentVariables.*; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.catchThrowable; @@ -190,11 +192,11 @@ public void exceptionThrownIfKeyVaultConfigUrlSyntaxIncorrect() { KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); when(config.getKeys()).thenReturn(keyConfiguration); - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); - when(keyConfiguration.getHashicorpKeyVaultConfig()).thenReturn(keyVaultConfig); + DefaultKeyVaultConfig keyVaultConfig = mock(DefaultKeyVaultConfig.class); + when(keyConfiguration.getKeyVaultConfig()).thenReturn(keyVaultConfig); - when(keyVaultConfig.getUrl()).thenReturn("noschemeurl"); - when(keyVaultConfig.getApprolePath()).thenReturn("approle"); + when(keyVaultConfig.getProperty("url")).thenReturn(Optional.of("noschemeurl")); + when(keyVaultConfig.getProperty("approlePath")).thenReturn(Optional.of("approle")); setUpUtilMocks(keyVaultConfig); @@ -213,11 +215,11 @@ public void exceptionThrownIfKeyVaultConfigUrlIsMalformed() { KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); when(config.getKeys()).thenReturn(keyConfiguration); - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); - when(keyConfiguration.getHashicorpKeyVaultConfig()).thenReturn(keyVaultConfig); + DefaultKeyVaultConfig keyVaultConfig = mock(DefaultKeyVaultConfig.class); + when(keyVaultConfig.getProperty("url")).thenReturn(Optional.of("http://malformedurl:-1")); + when(keyVaultConfig.getProperty("approlePath")).thenReturn(Optional.of("approle")); - when(keyVaultConfig.getUrl()).thenReturn("http://malformedurl:-1"); - when(keyVaultConfig.getApprolePath()).thenReturn("approle"); + when(keyConfiguration.getKeyVaultConfig()).thenReturn(keyVaultConfig); setUpUtilMocks(keyVaultConfig); @@ -227,7 +229,7 @@ public void exceptionThrownIfKeyVaultConfigUrlIsMalformed() { assertThat(ex.getMessage()).contains("Provided Hashicorp Vault url is incorrectly formatted"); } - private void setUpUtilMocks(HashicorpKeyVaultConfig keyVaultConfig) { + private void setUpUtilMocks(KeyVaultConfig keyVaultConfig) { SslConfiguration sslConfiguration = mock(SslConfiguration.class); when(keyVaultServiceFactoryUtil.configureSsl(keyVaultConfig, envProvider)).thenReturn(sslConfiguration); @@ -255,11 +257,11 @@ public void returnedValueIsCorrectType() { KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); when(config.getKeys()).thenReturn(keyConfiguration); - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); - when(keyConfiguration.getHashicorpKeyVaultConfig()).thenReturn(keyVaultConfig); + DefaultKeyVaultConfig keyVaultConfig = mock(DefaultKeyVaultConfig.class); + when(keyConfiguration.getKeyVaultConfig()).thenReturn(keyVaultConfig); - when(keyVaultConfig.getUrl()).thenReturn("http://someurl"); - when(keyVaultConfig.getApprolePath()).thenReturn("approle"); + when(keyVaultConfig.getProperty("url")).thenReturn(Optional.of("http://someurl")); + when(keyVaultConfig.getProperty("approlePath")).thenReturn(Optional.of("approle")); setUpUtilMocks(keyVaultConfig); @@ -277,11 +279,11 @@ public void returnedValueIsCorrectTypeUsing2ArgConstructor() { KeyConfiguration keyConfiguration = mock(KeyConfiguration.class); when(config.getKeys()).thenReturn(keyConfiguration); - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); - when(keyConfiguration.getHashicorpKeyVaultConfig()).thenReturn(keyVaultConfig); + DefaultKeyVaultConfig keyVaultConfig = mock(DefaultKeyVaultConfig.class); + when(keyConfiguration.getKeyVaultConfig()).thenReturn(keyVaultConfig); - when(keyVaultConfig.getUrl()).thenReturn("http://someurl"); - when(keyVaultConfig.getApprolePath()).thenReturn("approle"); + when(keyVaultConfig.getProperty("url")).thenReturn(Optional.of("http://someurl")); + when(keyVaultConfig.getProperty("approlePath")).thenReturn(Optional.of("approle")); setUpUtilMocks(keyVaultConfig); diff --git a/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtilTest.java b/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtilTest.java index c96fa649f4..d9ca2eecec 100644 --- a/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtilTest.java +++ b/key-vault/hashicorp-key-vault/src/test/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactoryUtilTest.java @@ -1,6 +1,6 @@ package com.quorum.tessera.key.vault.hashicorp; -import com.quorum.tessera.config.HashicorpKeyVaultConfig; +import com.quorum.tessera.config.KeyVaultConfig; import com.quorum.tessera.config.util.EnvironmentVariableProvider; import org.junit.Before; import org.junit.Test; @@ -16,6 +16,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.Optional; import java.util.UUID; import static com.quorum.tessera.config.util.EnvironmentVariables.*; @@ -35,14 +36,16 @@ public void setUp() { @Test public void configureSslUsesKeyStoreAndTrustStoreIfBothProvided() throws Exception { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); Path path = Files.createTempFile(UUID.randomUUID().toString(), ".tmp"); path.toFile().deleteOnExit(); - when(keyVaultConfig.getTlsKeyStorePath()).thenReturn(path); - when(keyVaultConfig.getTlsTrustStorePath()).thenReturn(path); + when(keyVaultConfig.hasProperty("tlsKeyStorePath","tlsTrustStorePath")).thenReturn(true); + + when(keyVaultConfig.getProperty("tlsKeyStorePath")).thenReturn(Optional.of(path.toString())); + when(keyVaultConfig.getProperty("tlsTrustStorePath")).thenReturn(Optional.of(path.toString())); SslConfiguration result = util.configureSsl(keyVaultConfig, envProvider); @@ -52,14 +55,17 @@ public void configureSslUsesKeyStoreAndTrustStoreIfBothProvided() throws Excepti @Test public void configureSslUsesTrustStoreOnlyIfProvided() throws Exception { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); Path path = Files.createTempFile(UUID.randomUUID().toString(), ".tmp"); path.toFile().deleteOnExit(); - when(keyVaultConfig.getTlsKeyStorePath()).thenReturn(null); - when(keyVaultConfig.getTlsTrustStorePath()).thenReturn(path); + when(keyVaultConfig.hasProperty("tlsTrustStorePath")).thenReturn(true); + when(keyVaultConfig.hasProperty("tlsKeyStorePath")).thenReturn(false); + + when(keyVaultConfig.getProperty("tlsKeyStorePath")).thenReturn(Optional.empty()); + when(keyVaultConfig.getProperty("tlsTrustStorePath")).thenReturn(Optional.of(path.toString())); SslConfiguration result = util.configureSsl(keyVaultConfig, envProvider); @@ -69,11 +75,11 @@ public void configureSslUsesTrustStoreOnlyIfProvided() throws Exception { @Test public void configureSslUsesNoKeyStoresIfNoneProvided() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); - when(keyVaultConfig.getTlsKeyStorePath()).thenReturn(null); - when(keyVaultConfig.getTlsTrustStorePath()).thenReturn(null); + when(keyVaultConfig.getProperty("tlsKeyStorePath")).thenReturn(Optional.empty()); + when(keyVaultConfig.getProperty("tlsTrustStorePath")).thenReturn(Optional.empty()); SslConfiguration result = util.configureSsl(keyVaultConfig, envProvider); @@ -100,7 +106,7 @@ public void createClientHttpRequestFactory() { @Test public void configureClientAuthenticationIfAllEnvVarsSetThenAppRoleMethod() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); ClientHttpRequestFactory clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); VaultEndpoint vaultEndpoint = mock(VaultEndpoint.class); @@ -109,7 +115,7 @@ public void configureClientAuthenticationIfAllEnvVarsSetThenAppRoleMethod() { when(envProvider.getEnv(HASHICORP_SECRET_ID)).thenReturn("secret-id"); when(envProvider.getEnv(HASHICORP_TOKEN)).thenReturn("token"); - when(keyVaultConfig.getApprolePath()).thenReturn("approle"); + when(keyVaultConfig.getProperty("approlePath")).thenReturn(Optional.of("approle")); ClientAuthentication result = util.configureClientAuthentication(keyVaultConfig, envProvider, clientHttpRequestFactory, vaultEndpoint); @@ -118,7 +124,7 @@ public void configureClientAuthenticationIfAllEnvVarsSetThenAppRoleMethod() { @Test public void configureClientAuthenticationIfOnlyRoleIdAndSecretIdSetThenAppRoleMethod() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); ClientHttpRequestFactory clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); VaultEndpoint vaultEndpoint = mock(VaultEndpoint.class); @@ -127,7 +133,7 @@ public void configureClientAuthenticationIfOnlyRoleIdAndSecretIdSetThenAppRoleMe when(envProvider.getEnv(HASHICORP_SECRET_ID)).thenReturn("secret-id"); when(envProvider.getEnv(HASHICORP_TOKEN)).thenReturn(null); - when(keyVaultConfig.getApprolePath()).thenReturn("somepath"); + when(keyVaultConfig.getProperty("approlePath")).thenReturn(Optional.of("somepath")); ClientAuthentication result = util.configureClientAuthentication(keyVaultConfig, envProvider, clientHttpRequestFactory, vaultEndpoint); @@ -137,7 +143,7 @@ public void configureClientAuthenticationIfOnlyRoleIdAndSecretIdSetThenAppRoleMe @Test public void configureClientAuthenticationIfOnlyRoleIdSetThenException() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); ClientHttpRequestFactory clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); VaultEndpoint vaultEndpoint = mock(VaultEndpoint.class); @@ -154,7 +160,7 @@ public void configureClientAuthenticationIfOnlyRoleIdSetThenException() { @Test public void configureClientAuthenticationIfOnlySecretIdSetThenException() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); ClientHttpRequestFactory clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); VaultEndpoint vaultEndpoint = mock(VaultEndpoint.class); @@ -171,7 +177,7 @@ public void configureClientAuthenticationIfOnlySecretIdSetThenException() { @Test public void configureClientAuthenticationIfOnlyTokenSetThenTokenMethod() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); ClientHttpRequestFactory clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); VaultEndpoint vaultEndpoint = mock(VaultEndpoint.class); @@ -187,7 +193,7 @@ public void configureClientAuthenticationIfOnlyTokenSetThenTokenMethod() { @Test public void configureClientAuthenticationIfNoEnvVarSetThenException() { - HashicorpKeyVaultConfig keyVaultConfig = mock(HashicorpKeyVaultConfig.class); + KeyVaultConfig keyVaultConfig = mock(KeyVaultConfig.class); EnvironmentVariableProvider envProvider = mock(EnvironmentVariableProvider.class); ClientHttpRequestFactory clientHttpRequestFactory = mock(ClientHttpRequestFactory.class); VaultEndpoint vaultEndpoint = mock(VaultEndpoint.class); From 79ec76ecd471fa7b63818a1010e9bbd7d0a5395d Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Tue, 24 Dec 2019 15:42:39 +0000 Subject: [PATCH 2/5] Remove unused import --- .../HashicorpKeyVaultServiceFactory.java | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java index 3f462c7df3..d9349fe61e 100644 --- a/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java +++ b/key-vault/hashicorp-key-vault/src/main/java/com/quorum/tessera/key/vault/hashicorp/HashicorpKeyVaultServiceFactory.java @@ -13,8 +13,6 @@ import org.springframework.vault.core.VaultTemplate; import org.springframework.vault.support.ClientOptions; import org.springframework.vault.support.SslConfiguration; - -import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.util.NoSuchElementException; @@ -35,8 +33,10 @@ public KeyVaultService create(Config config, EnvironmentVariableProvider envProv return this.create(config, envProvider, util); } - //This method should not be called directly. It has been left package-private to enable injection of util during testing - KeyVaultService create(Config config, EnvironmentVariableProvider envProvider, HashicorpKeyVaultServiceFactoryUtil util) { + // This method should not be called directly. It has been left package-private to enable injection of util during + // testing + KeyVaultService create( + Config config, EnvironmentVariableProvider envProvider, HashicorpKeyVaultServiceFactoryUtil util) { Objects.requireNonNull(config); Objects.requireNonNull(envProvider); Objects.requireNonNull(util); @@ -45,22 +45,38 @@ KeyVaultService create(Config config, EnvironmentVariableProvider envProvider, H final String secretId = envProvider.getEnv(HASHICORP_SECRET_ID); final String authToken = envProvider.getEnv(HASHICORP_TOKEN); - if(roleId == null && secretId == null && authToken == null) { - throw new HashicorpCredentialNotSetException("Environment variables must be set to authenticate with Hashicorp Vault. Set the " + HASHICORP_ROLE_ID + " and " + HASHICORP_SECRET_ID + " environment variables if using the AppRole authentication method. Set the " + HASHICORP_TOKEN + " environment variable if using another authentication method."); - } - else if(isOnlyOneInputNull(roleId, secretId)) { - throw new HashicorpCredentialNotSetException("Only one of the " + HASHICORP_ROLE_ID + " and " + HASHICORP_SECRET_ID + " environment variables to authenticate with Hashicorp Vault using the AppRole method has been set"); + if (roleId == null && secretId == null && authToken == null) { + throw new HashicorpCredentialNotSetException( + "Environment variables must be set to authenticate with Hashicorp Vault. Set the " + + HASHICORP_ROLE_ID + + " and " + + HASHICORP_SECRET_ID + + " environment variables if using the AppRole authentication method. Set the " + + HASHICORP_TOKEN + + " environment variable if using another authentication method."); + } else if (isOnlyOneInputNull(roleId, secretId)) { + throw new HashicorpCredentialNotSetException( + "Only one of the " + + HASHICORP_ROLE_ID + + " and " + + HASHICORP_SECRET_ID + + " environment variables to authenticate with Hashicorp Vault using the AppRole method has been set"); } - KeyVaultConfig keyVaultConfig = Optional.ofNullable(config.getKeys()) - .map(KeyConfiguration::getKeyVaultConfig) - .orElseThrow(() -> new ConfigException(new RuntimeException("Trying to create Hashicorp Vault connection but no Vault configuration provided"))); + KeyVaultConfig keyVaultConfig = + Optional.ofNullable(config.getKeys()) + .map(KeyConfiguration::getKeyVaultConfig) + .orElseThrow( + () -> + new ConfigException( + new RuntimeException( + "Trying to create Hashicorp Vault connection but no Vault configuration provided"))); VaultEndpoint vaultEndpoint; try { URI uri = new URI(keyVaultConfig.getProperty("url").get()); - vaultEndpoint = VaultEndpoint.from(uri); + vaultEndpoint = VaultEndpoint.from(uri); } catch (URISyntaxException | NoSuchElementException | IllegalArgumentException e) { throw new ConfigException(new RuntimeException("Provided Hashicorp Vault url is incorrectly formatted", e)); } @@ -69,16 +85,17 @@ else if(isOnlyOneInputNull(roleId, secretId)) { ClientOptions clientOptions = new ClientOptions(); - ClientHttpRequestFactory clientHttpRequestFactory = util.createClientHttpRequestFactory(clientOptions, sslConfiguration); + ClientHttpRequestFactory clientHttpRequestFactory = + util.createClientHttpRequestFactory(clientOptions, sslConfiguration); - ClientAuthentication clientAuthentication = util.configureClientAuthentication(keyVaultConfig, envProvider, clientHttpRequestFactory, vaultEndpoint); + ClientAuthentication clientAuthentication = + util.configureClientAuthentication( + keyVaultConfig, envProvider, clientHttpRequestFactory, vaultEndpoint); SessionManager sessionManager = new SimpleSessionManager(clientAuthentication); VaultOperations vaultOperations = new VaultTemplate(vaultEndpoint, clientHttpRequestFactory, sessionManager); - return new HashicorpKeyVaultService( - new KeyValueOperationsDelegateFactory(vaultOperations) - ); + return new HashicorpKeyVaultService(new KeyValueOperationsDelegateFactory(vaultOperations)); } @Override From c9cf318fcef33574256552bd81c6a33a60e7a0c2 Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Mon, 30 Dec 2019 13:44:26 +0000 Subject: [PATCH 3/5] Lazy init vault config. --- .../quorum/tessera/config/KeyConfiguration.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java b/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java index c141ccbb46..3c8a948a03 100644 --- a/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java +++ b/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java @@ -53,9 +53,9 @@ public KeyConfiguration( this.keyData = keyData; this.azureKeyVaultConfig = azureKeyVaultConfig; this.hashicorpKeyVaultConfig = hashicorpKeyVaultConfig; - if(null != azureKeyVaultConfig) { + if (null != azureKeyVaultConfig) { this.keyVaultConfig = KeyVaultConfigConverter.convert(azureKeyVaultConfig); - } else if(null != hashicorpKeyVaultConfig) { + } else if (null != hashicorpKeyVaultConfig) { this.keyVaultConfig = KeyVaultConfigConverter.convert(hashicorpKeyVaultConfig); } } @@ -96,14 +96,24 @@ public void setKeyData(List keyData) { public void setAzureKeyVaultConfig(AzureKeyVaultConfig azureKeyVaultConfig) { this.azureKeyVaultConfig = azureKeyVaultConfig; + this.keyVaultConfig = KeyVaultConfigConverter.convert(azureKeyVaultConfig); } public void setHashicorpKeyVaultConfig(HashicorpKeyVaultConfig hashicorpKeyVaultConfig) { this.hashicorpKeyVaultConfig = hashicorpKeyVaultConfig; + this.keyVaultConfig = KeyVaultConfigConverter.convert(hashicorpKeyVaultConfig); } public DefaultKeyVaultConfig getKeyVaultConfig() { - return keyVaultConfig; + if (keyVaultConfig != null) { + return keyVaultConfig; + } + if (null != azureKeyVaultConfig) { + return KeyVaultConfigConverter.convert(azureKeyVaultConfig); + } else if (null != hashicorpKeyVaultConfig) { + return KeyVaultConfigConverter.convert(hashicorpKeyVaultConfig); + } + return null; } public void setKeyVaultConfig(DefaultKeyVaultConfig keyVaultConfig) { From 4d52e4906b008279969ee4df8087f8718baddb01 Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Mon, 30 Dec 2019 14:06:55 +0000 Subject: [PATCH 4/5] remove set keyvault config from azure and hashicorp setters. --- .../main/java/com/quorum/tessera/config/KeyConfiguration.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java b/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java index 3c8a948a03..25560cc26c 100644 --- a/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java +++ b/config/src/main/java/com/quorum/tessera/config/KeyConfiguration.java @@ -96,12 +96,10 @@ public void setKeyData(List keyData) { public void setAzureKeyVaultConfig(AzureKeyVaultConfig azureKeyVaultConfig) { this.azureKeyVaultConfig = azureKeyVaultConfig; - this.keyVaultConfig = KeyVaultConfigConverter.convert(azureKeyVaultConfig); } public void setHashicorpKeyVaultConfig(HashicorpKeyVaultConfig hashicorpKeyVaultConfig) { this.hashicorpKeyVaultConfig = hashicorpKeyVaultConfig; - this.keyVaultConfig = KeyVaultConfigConverter.convert(hashicorpKeyVaultConfig); } public DefaultKeyVaultConfig getKeyVaultConfig() { From 644cbd36093246c40be828ec0a9ef75f4bef3775 Mon Sep 17 00:00:00 2001 From: Mark Lowe Date: Mon, 30 Dec 2019 15:04:21 +0000 Subject: [PATCH 5/5] fix coverage. --- .../tessera/config/KeyConfigurationTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 config/src/test/java/com/quorum/tessera/config/KeyConfigurationTest.java diff --git a/config/src/test/java/com/quorum/tessera/config/KeyConfigurationTest.java b/config/src/test/java/com/quorum/tessera/config/KeyConfigurationTest.java new file mode 100644 index 0000000000..656f0297fd --- /dev/null +++ b/config/src/test/java/com/quorum/tessera/config/KeyConfigurationTest.java @@ -0,0 +1,41 @@ +package com.quorum.tessera.config; + +import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; + +public class KeyConfigurationTest { + + @Test + public void loadKeyVaultConfigFromAzureKeyVaultConfig() throws Exception { + KeyConfiguration keyConfiguration = new KeyConfiguration(); + AzureKeyVaultConfig azureKeyVaultConfig = new AzureKeyVaultConfig(); + keyConfiguration.setAzureKeyVaultConfig(azureKeyVaultConfig); + + KeyVaultConfig result = keyConfiguration.getKeyVaultConfig(); + assertThat(result).isNotNull(); + + + } + + @Test + public void loadKeyVaultConfigFromHashicorpKeyVaultConfig() throws Exception { + KeyConfiguration keyConfiguration = new KeyConfiguration(); + HashicorpKeyVaultConfig hashicorpKeyVaultConfig = new HashicorpKeyVaultConfig(); + keyConfiguration.setHashicorpKeyVaultConfig(hashicorpKeyVaultConfig); + + KeyVaultConfig result = keyConfiguration.getKeyVaultConfig(); + assertThat(result).isNotNull(); + + } + + @Test + public void loadKeyVaultConfigFromNoCOnfg() throws Exception { + KeyConfiguration keyConfiguration = new KeyConfiguration(); + + KeyVaultConfig result = keyConfiguration.getKeyVaultConfig(); + assertThat(result).isNull(); + + } + + +}