Skip to content

Commit

Permalink
Merge pull request #483 from QuorumEngineering/feature/key-vault-with…
Browse files Browse the repository at this point in the history
…-new-key-types

Azure Key Vault integration for storage of Tessera public/private key pairs
  • Loading branch information
melowe authored Oct 15, 2018
2 parents de57253 + e3c46e3 commit e6b2053
Show file tree
Hide file tree
Showing 69 changed files with 1,958 additions and 172 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ public CliResult execute(String... args) throws Exception {

new PidFileParser().parse(line);

return new CliResult(0, line.hasOption("keygen"), config);
boolean suppressStartup = line.hasOption("keygen") && Objects.isNull(config);

return new CliResult(0, suppressStartup, config);

} catch (ParseException exp) {
throw new CliException(exp.getMessage());
Expand Down Expand Up @@ -131,7 +133,6 @@ private Options buildBaseOptions() {
.argName("PATH")
.build());

//If keygen then we require the path to the private key config path
options.addOption(
Option.builder("keygen")
.desc("Use this option to generate public/private keypair")
Expand All @@ -140,8 +141,8 @@ private Options buildBaseOptions() {

options.addOption(
Option.builder("filename")
.desc("Path to private key config for generation of missing key files")
.hasArg(true)
.desc("Comma-separated list of paths to save generated key files. Can also be used with keyvault. Number of args equals number of key-pairs generated.")
.hasArgs()
.optionalArg(false)
.argName("PATH")
.build());
Expand All @@ -161,6 +162,15 @@ private Options buildBaseOptions() {
.numberOfArgs(1)
.build());

options.addOption(
Option.builder("keygenvaulturl")
.desc("Base url for Azure Key Vault")
.hasArg()
.optionalArg(false)
.argName("STRING")
.build()
);

options.addOption(
Option.builder("pidfile")
.desc("Path to pid file")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ public Config parse(final CommandLine commandLine) throws IOException {

Config config = null;

if (commandLine.hasOption("configfile")) {
final boolean isGeneratingWithKeyVault = commandLine.hasOption("keygen") && commandLine.hasOption("keygenvaulturl");

if (commandLine.hasOption("configfile") && !isGeneratingWithKeyVault) {
final Path path = Paths.get(commandLine.getOptionValue("configfile"));

if (!Files.exists(path)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.quorum.tessera.config.cli.parsers;

import com.quorum.tessera.config.ArgonOptions;
import com.quorum.tessera.config.KeyVaultConfig;
import com.quorum.tessera.config.keypairs.ConfigKeyPair;
import com.quorum.tessera.config.util.JaxbUtil;
import com.quorum.tessera.key.generation.KeyGenerator;
import com.quorum.tessera.key.generation.KeyGeneratorFactory;
import com.quorum.tessera.config.util.EnvironmentVariableProvider;
import org.apache.commons.cli.CommandLine;

import java.io.IOException;
Expand All @@ -21,16 +23,20 @@

public class KeyGenerationParser implements Parser<List<ConfigKeyPair>> {

private final KeyGenerator generator = KeyGeneratorFactory.newFactory().create();
private final KeyGeneratorFactory factory = KeyGeneratorFactory.newFactory();

public List<ConfigKeyPair> parse(final CommandLine commandLine) throws IOException {

final ArgonOptions options = this.argonOptions(commandLine).orElse(null);
final ArgonOptions argonOptions = this.argonOptions(commandLine).orElse(null);
final KeyVaultConfig keyVaultConfig = this.keyVaultConfig(commandLine).orElse(null);
final EnvironmentVariableProvider envProvider = new EnvironmentVariableProvider();

final KeyGenerator generator = factory.create(keyVaultConfig, envProvider);

if (commandLine.hasOption("keygen")) {
return this.filenames(commandLine)
.stream()
.map(name -> generator.generate(name, options))
.map(name -> generator.generate(name, argonOptions))
.collect(Collectors.toList());
}

Expand Down Expand Up @@ -66,4 +72,13 @@ private List<String> filenames(final CommandLine commandLine) {

}

private Optional<KeyVaultConfig> keyVaultConfig(CommandLine commandLine) {
if(commandLine.hasOption("keygenvaulturl")) {
final String vaultUrl = commandLine.getOptionValue("keygenvaulturl");

return Optional.of(new KeyVaultConfig(vaultUrl));
}
return Optional.empty();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public void withConstraintViolations() throws Exception {
}

@Test
public void keygen() throws Exception {
public void keygenWithConfig() throws Exception {

KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator();

Expand All @@ -133,7 +133,7 @@ public void keygen() throws Exception {
assertThat(result).isNotNull();
assertThat(result.getStatus()).isEqualTo(0);
assertThat(result.getConfig()).isNotNull();
assertThat(result.isSuppressStartup()).isTrue();
assertThat(result.isSuppressStartup()).isFalse();

verify(keyGenerator).generate(anyString(), eq(null));
verifyNoMoreInteractions(keyGenerator);
Expand Down Expand Up @@ -344,5 +344,38 @@ public void updatingPasswordsDoesntProcessOtherOptions() throws Exception {
verifyZeroInteractions(MockKeyGeneratorFactory.getMockKeyGenerator());
System.setIn(oldIn);
}


@Test
public void suppressStartupForKeygenOption() throws Exception {
final CliResult cliResult = cliDelegate.execute("-keygen");

assertThat(cliResult.isSuppressStartup()).isTrue();
}

@Test
public void allowStartupForKeygenAndConfigfileOptions() throws Exception {
final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator();
final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""));
when(keyGenerator.generate(anyString(), eq(null))).thenReturn(keypair);

final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));

final CliResult cliResult = cliDelegate.execute("-keygen", "-configfile", configFile.toString());

assertThat(cliResult.isSuppressStartup()).isFalse();
}

@Test
public void suppressStartupForKeygenAndVaultUrlAndConfigfileOptions() throws Exception {
final KeyGenerator keyGenerator = MockKeyGeneratorFactory.getMockKeyGenerator();
final FilesystemKeyPair keypair = new FilesystemKeyPair(Paths.get(""), Paths.get(""));
when(keyGenerator.generate(anyString(), eq(null))).thenReturn(keypair);

final Path configFile = createAndPopulatePaths(getClass().getResource("/sample-config.json"));
final String vaultUrl = "https://test.vault.azure.net";

final CliResult cliResult = cliDelegate.execute("-keygen", "-keygenvaulturl", vaultUrl, "-configfile", configFile.toString());

assertThat(cliResult.isSuppressStartup()).isTrue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public void buildOptions() {
// "keys.keyData.publicKey",
"keys.keyData.privateKeyPath",
// "keys.keyData.publicKeyPath",
"keys.azureKeyVaultConfig.url",
"alwaysSendTo",
"unixSocketFile",
"useWhiteList",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.quorum.tessera.config.cli.parsers;

import com.quorum.tessera.config.Config;
import org.apache.commons.cli.CommandLine;
import org.junit.Before;
import org.junit.Test;


import java.io.FileNotFoundException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ConfigurationParserTest {

private ConfigurationParser configParser;
private CommandLine commandLine;

@Before
public void setUp() {
configParser = new ConfigurationParser();
commandLine = mock(CommandLine.class);
}

@Test
public void providingKeygenAndVaultOptionsThenConfigfileNotParsed() throws Exception {
when(commandLine.hasOption("configfile")).thenReturn(true);
when(commandLine.hasOption("keygen")).thenReturn(true);
when(commandLine.hasOption("keygenvaulturl")).thenReturn(true);

Config result = configParser.parse(commandLine);

assertThat(result).isNull();
}

@Test
public void providingKeygenOptionThenConfigfileIsParsed() {
when(commandLine.hasOption("configfile")).thenReturn(true);
when(commandLine.hasOption("keygen")).thenReturn(true);
when(commandLine.hasOption("keygenvaulturl")).thenReturn(false);

when(commandLine.getOptionValue("configfile")).thenReturn("some/path");

Throwable throwable = catchThrowable(() -> configParser.parse(commandLine));

//FileNotFoundException thrown as "some/path" does not exist
assertThat(throwable).isInstanceOf(FileNotFoundException.class);
}

@Test
public void providingVaultOptionThenConfigfileIsParsed() {
when(commandLine.hasOption("configfile")).thenReturn(true);
when(commandLine.hasOption("keygen")).thenReturn(false);
when(commandLine.hasOption("keygenvaulturl")).thenReturn(true);

when(commandLine.getOptionValue("configfile")).thenReturn("some/path");

Throwable throwable = catchThrowable(() -> configParser.parse(commandLine));

//FileNotFoundException thrown as "some/path" does not exist
assertThat(throwable).isInstanceOf(FileNotFoundException.class);
}

@Test
public void providingNeitherKeygenOptionsThenConfigfileIsParsed() {
when(commandLine.hasOption("configfile")).thenReturn(true);
when(commandLine.hasOption("keygen")).thenReturn(false);
when(commandLine.hasOption("keygenvaulturl")).thenReturn(false);

when(commandLine.getOptionValue("configfile")).thenReturn("some/path");

Throwable throwable = catchThrowable(() -> configParser.parse(commandLine));

//FileNotFoundException thrown as "some/path" does not exist
assertThat(throwable).isInstanceOf(FileNotFoundException.class);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import com.quorum.tessera.config.ArgonOptions;
import com.quorum.tessera.config.keypairs.ConfigKeyPair;
import com.quorum.tessera.key.generation.KeyGenerator;
import com.quorum.tessera.config.keys.MockKeyGeneratorFactory;
import com.quorum.tessera.key.generation.KeyGenerator;
import org.apache.commons.cli.CommandLine;
import org.junit.Test;
import org.mockito.ArgumentCaptor;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
Expand Down Expand Up @@ -93,7 +92,7 @@ public void keygenWithNoName() throws Exception {
}

@Test
public void keygenNotGivenReturnsEmptyList() throws IOException {
public void keygenNotGivenReturnsEmptyList() throws Exception {

final CommandLine commandLine = mock(CommandLine.class);
when(commandLine.hasOption("keygen")).thenReturn(false);
Expand All @@ -108,4 +107,24 @@ public void keygenNotGivenReturnsEmptyList() throws IOException {
verifyZeroInteractions(keyGenerator);
}

@Test
public void vaultUrlOptionIsChecked() throws Exception {
final CommandLine commandLine = mock(CommandLine.class);
when(commandLine.hasOption("keygenvaulturl")).thenReturn(true);

this.parser.parse(commandLine);

verify(commandLine).getOptionValue("keygenvaulturl");
}

@Test
public void noVaultUrlOptionDoesNotThrowException() throws Exception {
final CommandLine commandLine = mock(CommandLine.class);
when(commandLine.hasOption("keygenvaulturl")).thenReturn(false);

this.parser.parse(commandLine);

verify(commandLine, times(0)).getOptionValue("keygenvaulturl");
}

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.quorum.tessera.config.keys;

import com.quorum.tessera.config.KeyVaultConfig;
import com.quorum.tessera.key.generation.KeyGenerator;
import com.quorum.tessera.key.generation.KeyGeneratorFactory;
import com.quorum.tessera.config.util.EnvironmentVariableProvider;

import static org.mockito.Mockito.mock;

Expand All @@ -15,7 +17,7 @@ public enum KeyGeneratorHolder {
}

@Override
public KeyGenerator create() {
public KeyGenerator create(KeyVaultConfig keyVaultConfig, EnvironmentVariableProvider envProvider) {
return getMockKeyGenerator();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public KeyConfiguration build() {
privateKeyPasswordFilePath = null;
}

return new KeyConfiguration(privateKeyPasswordFilePath, null, keyData);
return new KeyConfiguration(privateKeyPasswordFilePath, null, keyData, null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public void ifPublicAndPrivateKeyListAreEmptyThenKeyConfigurationIsAllNulls() th
KeyConfiguration result = tomlConfigFactory.createKeyDataBuilder(configData).build();
assertThat(result).isNotNull();

KeyConfiguration expected = new KeyConfiguration(null, null, Collections.emptyList());
KeyConfiguration expected = new KeyConfiguration(null, null, Collections.emptyList(), null);
assertThat(result).isEqualTo(expected);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public static ConfigBuilder builderWithValidValues() {
.sslClientTlsCertificatePath("sslClientTlsCertificatePath")
.sslServerTlsCertificatePath("sslServerTlsCertificatePath")
.keyData(new KeyConfiguration(null, Collections.emptyList(),
Collections.singletonList(new FilesystemKeyPair(Paths.get("public"), Paths.get("private")))));
Collections.singletonList(new FilesystemKeyPair(Paths.get("public"), Paths.get("private"))), null));
}

public static ConfigBuilder builderWithNullValues() {
Expand Down Expand Up @@ -91,7 +91,7 @@ public static ConfigBuilder builderWithNullValues() {
.sslClientTlsCertificatePath("sslClientTlsCertificatePath")
.sslServerTlsCertificatePath("sslServerTlsCertificatePath")
.keyData(new KeyConfiguration(null, Collections.emptyList(),
Collections.singletonList(new FilesystemKeyPair(Paths.get("public"), Paths.get("private")))));
Collections.singletonList(new FilesystemKeyPair(Paths.get("public"), Paths.get("private"))), null));
}

public static JsonObject createUnlockedPrivateKey() {
Expand Down
5 changes: 5 additions & 0 deletions config/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@
<artifactId>encryption-jnacl</artifactId>
</dependency>

<dependency>
<groupId>com.quorum.tessera</groupId>
<artifactId>key-vault-api</artifactId>
</dependency>

<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
Expand Down
2 changes: 2 additions & 0 deletions config/src/main/java/com/quorum/tessera/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.quorum.tessera.config.adapters.PathAdapter;
import com.quorum.tessera.config.constraints.ValidBase64;
import com.quorum.tessera.config.constraints.ValidKeyConfiguration;
import com.quorum.tessera.config.constraints.ValidKeyVaultConfiguration;
import com.quorum.tessera.config.constraints.ValidPath;

import javax.validation.Valid;
Expand Down Expand Up @@ -40,6 +41,7 @@ public class Config extends ConfigItem {
@NotNull
@XmlElement(required = true)
@ValidKeyConfiguration
@ValidKeyVaultConfiguration
@XmlJavaTypeAdapter(KeyConfigurationAdapter.class)
private final KeyConfiguration keys;

Expand Down
Loading

0 comments on commit e6b2053

Please sign in to comment.