Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure Key Vault integration for storage of Tessera public/private key pairs #483

Merged
merged 80 commits into from Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
dc2fead
Add POC service to authenticate and fetch secret from Azure Key Vault
chris-j-h Aug 31, 2018
19686c7
Add configuration for KeyVault
chris-j-h Sep 4, 2018
207c56e
Add KeyPairFactory to handle creation of KeyPairs dependent on config
chris-j-h Sep 5, 2018
cf22c57
Add keyVaultId to KeyData to use to lookup secrets from vault
chris-j-h Sep 5, 2018
f9262e3
Allow KeyData unmarshaller to handle case when key vault is used
chris-j-h Sep 5, 2018
f0d4028
Fix compilation errors
chris-j-h Sep 5, 2018
9584c4d
Update existing tests and add new KeyVault related tests
chris-j-h Sep 5, 2018
0c26ca0
Add KeyPairFactory tests
chris-j-h Sep 6, 2018
0fcffd8
Add validation to check key vault url is provided if key data uses vault
chris-j-h Sep 6, 2018
e043aa2
Increase eclipselink version to resolve cyclical dependency issue
chris-j-h Sep 6, 2018
b0e4285
Move key vault classes to tessera-app package
chris-j-h Sep 6, 2018
90b97c3
Move authenticator to util package
chris-j-h Sep 6, 2018
9b429d1
Test change
chris-j-h Sep 7, 2018
bebacfb
Add validation test for null KeyConfiguration
chris-j-h Sep 7, 2018
d55f549
Add to validation and tests for KeyConfiguration and KeyVaultConfig
chris-j-h Sep 7, 2018
60e1563
Merge branch 'master' of https://github.com/jpmorganchase/tessera int…
chris-j-h Sep 11, 2018
b6a2675
Remove public file path only unmarshalling and clean up
chris-j-h Sep 11, 2018
c3e83a9
KeyDataAdapter clean up
chris-j-h Sep 11, 2018
aafa1d1
Code coverage fixes
chris-j-h Sep 11, 2018
36c35e1
Add tests for KeyVaultClientDelegate and KeyVaultService
chris-j-h Sep 12, 2018
c4379fb
Refactor KeyVaultAuthenticator in preparation for testing
chris-j-h Sep 12, 2018
8b04040
Rename key vault classes, wire spring and add further tests
chris-j-h Sep 13, 2018
0169bf6
Update DDLs to include timestamp column
chris-j-h Sep 13, 2018
6f215c5
Add Azure prefix to config elements and add environment variable check
chris-j-h Sep 14, 2018
8c3965b
Move Azure dependencies to tessera-app
chris-j-h Sep 14, 2018
3fce03b
Merge branch 'master' of https://github.com/jpmorganchase/tessera int…
chris-j-h Sep 14, 2018
2526745
Merge branch 'master' into feature/key-vault
melowe Sep 14, 2018
69f507a
Merge from jpmorganchase/tessera master
chris-j-h Sep 17, 2018
b7d66d5
Merge remote-tracking branch 'qe/feature/key-vault' into feature/key-…
chris-j-h Sep 17, 2018
7558a88
Move key vault classes from tessera-app package to tessera-core
chris-j-h Sep 18, 2018
59952a6
Merge branch 'master' into feature/key-vault
chris-j-h Sep 18, 2018
85ab17a
Typo fix
chris-j-h Sep 18, 2018
5cb6251
Merge remote-tracking branch 'qe/feature/key-vault' into feature/key-…
chris-j-h Sep 18, 2018
f55db93
Merge branch 'master' into feature/key-vault
chris-j-h Sep 18, 2018
9a7109b
Merge branch 'master' into feature/key-vault
namtruong Sep 18, 2018
502f26d
Update keygen cli options and add new KeyGenerator to save keys in vault
chris-j-h Sep 21, 2018
87b31fd
Create KeyVault module with Azure impl & remove '.' from cli options
chris-j-h Sep 21, 2018
ae2d329
Revert existing cli option naming to reduce changes needed
chris-j-h Sep 21, 2018
0533665
Add setSecret method to KeyVaultService
chris-j-h Sep 24, 2018
5932bac
Create keygen module to remove cyclical dependency with config and vault
chris-j-h Sep 24, 2018
f497f7a
Rename KeyGeneratorImpl and add VaultKeyGenerator tests
chris-j-h Sep 24, 2018
0994148
Add test for setSecret in AzureKeyVaultService
chris-j-h Sep 24, 2018
58ce044
Add more tests for KeyGeneration
chris-j-h Sep 24, 2018
47e9251
Add public and private key vault IDs to KeyData
chris-j-h Sep 25, 2018
2e0d199
Fixed merge issues
chris-j-h Sep 26, 2018
cd277a8
Throw exception if IDs do not exist in vault & add keydataadapter case
chris-j-h Sep 26, 2018
d42f85a
Fix checkstyle violations
chris-j-h Sep 26, 2018
cea8684
Add ConfigKeyPair tests
chris-j-h Sep 26, 2018
921112e
Add tests for validation of key vault configuration combinations
chris-j-h Sep 26, 2018
3ee7c21
Restore KeyGeneratorFactoryTest after it was lost in previous merge
chris-j-h Sep 26, 2018
ae00e93
Fix checkstyle violations
chris-j-h Sep 26, 2018
03a4df6
Remove duplicated dependency in pom
chris-j-h Sep 27, 2018
5950e19
Disable auto-start of Tessera after keygen if using key vault
chris-j-h Sep 27, 2018
636decd
Remove invalid tests after new keypair types
chris-j-h Sep 27, 2018
8cf0f12
Add validation msg and update validation tests for AzureKeypair
chris-j-h Sep 27, 2018
2455676
Add new keypair type to provide useful msgs for invalid keyconfig combos
chris-j-h Sep 27, 2018
4b1ad84
Reduce code repetition in UnsupportedKeyPairValidator
chris-j-h Sep 27, 2018
b3979db
Remove bean validation from UnsupportedKeyPair
chris-j-h Sep 27, 2018
6f605e3
Add todos for KeyDataValidator cleanup
chris-j-h Sep 27, 2018
16e5375
Merge branch 'improvement/split-keypair-config-types' of https://gith…
chris-j-h Sep 27, 2018
2ae1899
Fix validation messages for UnsupportedKeyPair
chris-j-h Sep 27, 2018
abd7a99
Add tests for coverage
chris-j-h Sep 27, 2018
c34501a
Remove KeyData validation as this is now validated by the ConfigKeyPairs
chris-j-h Sep 27, 2018
f4901d9
Merge branch 'feature/separate-keygen' of https://github.com/QuorumEn…
chris-j-h Sep 27, 2018
90dc09b
Merge from master
chris-j-h Oct 3, 2018
051e744
Merge branch 'feature/unsupported-key-pair-type' of https://github.co…
chris-j-h Oct 3, 2018
bb457af
Move AzureVaultKeyPair marshalling to KeyDataAdapter
chris-j-h Oct 8, 2018
178b776
Merge branch 'master' of https://github.com/jpmorganchase/tessera int…
chris-j-h Oct 8, 2018
79765ba
Add further test cases for KeyVaultConfigValidator
chris-j-h Oct 8, 2018
30402f2
Minor changes and cleanup
chris-j-h Oct 8, 2018
64420ff
Fix checkstyle violations
chris-j-h Oct 9, 2018
5ef5bb2
Reintroduce auto-start of node after keygen if configfile provided
chris-j-h Oct 9, 2018
8a12714
Throw more specific exceptions for Azure Vault
chris-j-h Oct 11, 2018
12c3d08
Remove toString usage for key encoding and use specific test assertions
chris-j-h Oct 11, 2018
2d86793
Remove version info from pom as already defined in root pom
chris-j-h Oct 11, 2018
b611438
Remove usages of Key toString method for base64 encoding
chris-j-h Oct 11, 2018
bf916cb
Encapsulate retrieval of vault credential env-vars for easier testing
chris-j-h Oct 11, 2018
05de8a2
Resolve conflicts after merge of enclave txnmgr change from master
chris-j-h Oct 15, 2018
64dad42
Fetch keys from keyvault in KeyPairConverter, resolve compilation errors
chris-j-h Oct 15, 2018
e3c46e3
Merge branch 'master' into feature/key-vault-with-new-key-types
chris-j-h Oct 15, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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