Skip to content

Commit

Permalink
GCP Secret Manager acceptance test
Browse files Browse the repository at this point in the history
  • Loading branch information
skisel-bt committed Oct 13, 2023
1 parent a9fbda9 commit 617535f
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import tech.pegasys.web3signer.dsl.tls.TlsCertificateDefinition;
import tech.pegasys.web3signer.signing.config.AwsVaultParameters;
import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters;
import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters;
import tech.pegasys.web3signer.signing.config.KeystoresParameters;

import java.nio.file.Path;
Expand All @@ -42,6 +43,7 @@ public class SignerConfiguration {
private final boolean metricsEnabled;
private final Optional<AzureKeyVaultParameters> azureKeyVaultParameters;
private final Optional<AwsVaultParameters> awsSecretsManagerParameters;
private final Optional<GcpSecretManagerParameters> gcpSecretManagerParameters;
private final Optional<KeystoresParameters> keystoresParameters;
private final Optional<TlsOptions> serverTlsOptions;
private final Optional<TlsCertificateDefinition> overriddenCaTrustStore;
Expand Down Expand Up @@ -90,6 +92,7 @@ public SignerConfiguration(
final boolean metricsEnabled,
final Optional<AzureKeyVaultParameters> azureKeyVaultParameters,
final Optional<AwsVaultParameters> awsSecretsManagerParameters,
final Optional<GcpSecretManagerParameters> gcpSecretManagerParameters,
final Optional<KeystoresParameters> keystoresParameters,
final Optional<TlsOptions> serverTlsOptions,
final Optional<TlsCertificateDefinition> overriddenCaTrustStore,
Expand Down Expand Up @@ -134,6 +137,7 @@ public SignerConfiguration(
this.metricsEnabled = metricsEnabled;
this.azureKeyVaultParameters = azureKeyVaultParameters;
this.awsSecretsManagerParameters = awsSecretsManagerParameters;
this.gcpSecretManagerParameters = gcpSecretManagerParameters;
this.keystoresParameters = keystoresParameters;
this.serverTlsOptions = serverTlsOptions;
this.overriddenCaTrustStore = overriddenCaTrustStore;
Expand Down Expand Up @@ -225,6 +229,10 @@ public Optional<AwsVaultParameters> getAwsParameters() {
return awsSecretsManagerParameters;
}

public Optional<GcpSecretManagerParameters> getGcpParameters() {
return gcpSecretManagerParameters;
}

public Optional<KeystoresParameters> getKeystoresParameters() {
return keystoresParameters;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import tech.pegasys.web3signer.dsl.tls.TlsCertificateDefinition;
import tech.pegasys.web3signer.signing.config.AwsVaultParameters;
import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters;
import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters;
import tech.pegasys.web3signer.signing.config.KeystoresParameters;

import java.nio.file.Path;
Expand Down Expand Up @@ -51,6 +52,7 @@ public class SignerConfigurationBuilder {
private String mode;
private AzureKeyVaultParameters azureKeyVaultParameters;
private AwsVaultParameters awsVaultParameters;
private GcpSecretManagerParameters gcpSecretManagerParameters;
private Map<String, String> web3SignerEnvironment;
private Duration startupTimeout =
Boolean.getBoolean("debugSubProcess") ? Duration.ofHours(1) : Duration.ofSeconds(30);
Expand Down Expand Up @@ -148,6 +150,12 @@ public SignerConfigurationBuilder withAwsParameters(final AwsVaultParameters aws
return this;
}

public SignerConfigurationBuilder withGcpParameters(
final GcpSecretManagerParameters gcpSecretManagerParameters) {
this.gcpSecretManagerParameters = gcpSecretManagerParameters;
return this;
}

public SignerConfigurationBuilder withKeystoresParameters(
final KeystoresParameters keystoresParameters) {
this.keystoresParameters = keystoresParameters;
Expand Down Expand Up @@ -332,6 +340,7 @@ public SignerConfiguration build() {
metricsEnabled,
Optional.ofNullable(azureKeyVaultParameters),
Optional.ofNullable(awsVaultParameters),
Optional.ofNullable(gcpSecretManagerParameters),
Optional.ofNullable(keystoresParameters),
Optional.ofNullable(serverTlsOptions),
Optional.ofNullable(overriddenCaTrustStore),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static tech.pegasys.web3signer.signing.config.KeystoresParameters.KEYSTORES_PASSWORD_FILE;
import static tech.pegasys.web3signer.signing.config.KeystoresParameters.KEYSTORES_PATH;

import tech.pegasys.web3signer.commandline.PicoCliGcpSecretManagerParameters;
import tech.pegasys.web3signer.core.config.ClientAuthConstraints;
import tech.pegasys.web3signer.core.config.TlsOptions;
import tech.pegasys.web3signer.core.config.client.ClientTlsOptions;
Expand All @@ -40,6 +41,7 @@
import tech.pegasys.web3signer.dsl.utils.DatabaseUtil;
import tech.pegasys.web3signer.signing.config.AwsVaultParameters;
import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters;
import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters;
import tech.pegasys.web3signer.signing.config.KeystoresParameters;

import java.io.IOException;
Expand Down Expand Up @@ -152,6 +154,9 @@ public List<String> createCmdLineParams() {
.getAwsParameters()
.ifPresent(
awsParams -> yamlConfig.append(awsSecretsManagerBulkLoadingOptions(awsParams)));
signerConfig
.getGcpParameters()
.ifPresent(gcpParameters -> yamlConfig.append(gcpBulkLoadingOptions(gcpParameters)));

final CommandArgs subCommandArgs = createSubCommandArgs();
params.addAll(subCommandArgs.params);
Expand Down Expand Up @@ -574,6 +579,31 @@ private String awsSecretsManagerBulkLoadingOptions(final AwsVaultParameters awsV
return yamlConfig.toString();
}

private String gcpBulkLoadingOptions(
final GcpSecretManagerParameters gcpSecretManagerParameters) {
final StringBuilder yamlConfig = new StringBuilder();
yamlConfig.append(
String.format(
YAML_BOOLEAN_FMT,
"eth2." + PicoCliGcpSecretManagerParameters.GCP_SECRETS_ENABLED_OPTION.substring(2),
gcpSecretManagerParameters.isEnabled()));
if (gcpSecretManagerParameters.getProjectId() != null) {
yamlConfig.append(
String.format(
YAML_STRING_FMT,
"eth2." + PicoCliGcpSecretManagerParameters.GCP_PROJECT_ID_OPTION.substring(2),
gcpSecretManagerParameters.getProjectId()));
}
if (gcpSecretManagerParameters.getFilter().isPresent()) {
yamlConfig.append(
String.format(
YAML_STRING_FMT,
"eth2." + PicoCliGcpSecretManagerParameters.GCP_SECRETS_FILTER_OPTION.substring(2),
gcpSecretManagerParameters.getFilter().get()));
}
return yamlConfig.toString();
}

private String awsKmsBulkLoadingOptions(final AwsVaultParameters awsVaultParameters) {
final StringBuilder yamlConfig = new StringBuilder();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import static tech.pegasys.web3signer.signing.config.KeystoresParameters.KEYSTORES_PASSWORD_FILE;
import static tech.pegasys.web3signer.signing.config.KeystoresParameters.KEYSTORES_PATH;

import tech.pegasys.web3signer.commandline.PicoCliGcpSecretManagerParameters;
import tech.pegasys.web3signer.core.config.ClientAuthConstraints;
import tech.pegasys.web3signer.core.config.TlsOptions;
import tech.pegasys.web3signer.core.config.client.ClientTlsOptions;
Expand All @@ -40,6 +41,7 @@
import tech.pegasys.web3signer.dsl.utils.DatabaseUtil;
import tech.pegasys.web3signer.signing.config.AwsVaultParameters;
import tech.pegasys.web3signer.signing.config.AzureKeyVaultParameters;
import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters;
import tech.pegasys.web3signer.signing.config.KeystoresParameters;

import java.nio.file.Path;
Expand Down Expand Up @@ -129,6 +131,9 @@ public List<String> createCmdLineParams() {
signerConfig
.getAwsParameters()
.ifPresent(awsParams -> params.addAll(awsSecretsManagerBulkLoadingOptions(awsParams)));
signerConfig
.getGcpParameters()
.ifPresent(gcpParams -> params.addAll(gcpSecretManagerBulkLoadingOptions(gcpParams)));
} else if (signerConfig.getMode().equals("eth1")) {
params.add("--downstream-http-port");
params.add(Integer.toString(signerConfig.getDownstreamHttpPort()));
Expand Down Expand Up @@ -310,6 +315,28 @@ private Collection<String> createEth2Args() {
return params;
}

private Collection<String> gcpSecretManagerBulkLoadingOptions(
final GcpSecretManagerParameters gcpSecretManagerParameters) {
final List<String> params = new ArrayList<>();
params.add(
PicoCliGcpSecretManagerParameters.GCP_SECRETS_ENABLED_OPTION
+ "="
+ gcpSecretManagerParameters.isEnabled());
if (gcpSecretManagerParameters.getProjectId() != null) {
params.add(
PicoCliGcpSecretManagerParameters.GCP_PROJECT_ID_OPTION
+ "="
+ gcpSecretManagerParameters.getProjectId());
}
if (gcpSecretManagerParameters.getFilter().isPresent()) {
params.add(
PicoCliGcpSecretManagerParameters.GCP_SECRETS_FILTER_OPTION
+ "="
+ gcpSecretManagerParameters.getFilter().get());
}
return params;
}

private Collection<String> awsSecretsManagerBulkLoadingOptions(
final AwsVaultParameters awsVaultParameters) {
final List<String> params = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* Copyright 2023 ConsenSys AG.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package tech.pegasys.web3signer.tests.bulkloading;

import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static tech.pegasys.web3signer.core.config.HealthCheckNames.KEYS_CHECK_GCP_BULK_LOADING;
import static tech.pegasys.web3signer.dsl.utils.HealthCheckResultUtil.getHealtcheckKeysLoaded;
import static tech.pegasys.web3signer.dsl.utils.HealthCheckResultUtil.getHealthcheckErrorCount;
import static tech.pegasys.web3signer.dsl.utils.HealthCheckResultUtil.getHealthcheckStatusValue;

import tech.pegasys.teku.bls.BLSKeyPair;
import tech.pegasys.web3signer.GcpSecretManagerUtil;
import tech.pegasys.web3signer.dsl.signer.SignerConfigurationBuilder;
import tech.pegasys.web3signer.signing.KeyType;
import tech.pegasys.web3signer.signing.config.GcpSecretManagerParameters;
import tech.pegasys.web3signer.signing.config.GcpSecretManagerParametersBuilder;
import tech.pegasys.web3signer.tests.AcceptanceTestBase;

import java.io.IOException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

import io.restassured.http.ContentType;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

@EnabledIfEnvironmentVariable(
named = "GCP_PROJECT_ID",
matches = ".*",
disabledReason = "GCP_PROJECT_ID env variable is required")
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // same instance is shared across test methods
public class GcpSecretManagerAcceptanceTest extends AcceptanceTestBase {
private static final Logger LOG = LogManager.getLogger();
private static final String GCP_PROJECT_ID = System.getenv("GCP_PROJECT_ID");

private GcpSecretManagerUtil gcpSecretManagerUtil;
private final List<BLSKeyPair> blsKeyPairs = new ArrayList<>();
private final List<String> secretNames = new ArrayList<>();

@BeforeAll
void setupGcpResources() throws IOException {
gcpSecretManagerUtil = new GcpSecretManagerUtil(GCP_PROJECT_ID);
final SecureRandom secureRandom = new SecureRandom();

for (int i = 0; i < 4; i++) {
final BLSKeyPair blsKeyPair = BLSKeyPair.random(secureRandom);
String secretName =
gcpSecretManagerUtil.createSecret(
"Secret%d-%s".formatted(i, blsKeyPair.getPublicKey().toString()),
blsKeyPair.getSecretKey().toBytes().toHexString());
blsKeyPairs.add(blsKeyPair);
secretNames.add(secretName);
}
}

@ParameterizedTest(name = "{index} - Using config file: {0}")
@ValueSource(booleans = {true, false})
void secretsAreLoadedFromGCPSecretManagerAndReportedByPublicApi(final boolean useConfigFile) {
final GcpSecretManagerParameters gcpSecretManagerParameters =
GcpSecretManagerParametersBuilder.aGcpParameters()
.withEnabled(true)
.withProjectId(GCP_PROJECT_ID)
.withFilter("name:Secret0 OR name:Secret1")
.build();

final SignerConfigurationBuilder configBuilder =
new SignerConfigurationBuilder()
.withUseConfigFile(useConfigFile)
.withMode("eth2")
.withGcpParameters(gcpSecretManagerParameters);

startSigner(configBuilder.build());

final String healthCheckJsonBody = signer.healthcheck().body().asString();
int keysLoaded = getHealtcheckKeysLoaded(healthCheckJsonBody, KEYS_CHECK_GCP_BULK_LOADING);

assertThat(keysLoaded).isEqualTo(2);

signer
.callApiPublicKeys(KeyType.BLS)
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body(
"",
containsInAnyOrder(
blsKeyPairs.get(0).getPublicKey().toString(),
blsKeyPairs.get(1).getPublicKey().toString()),
"",
hasSize(2));
}

@Test
void healthCheckErrorCountWhenInvalidCredentialsAreUsed() {
final boolean useConfigFile = false;
final GcpSecretManagerParameters invalidGcpParams =
GcpSecretManagerParametersBuilder.aGcpParameters()
.withEnabled(true)
.withProjectId("NON_EXISTING_PROJECT")
.build();

final SignerConfigurationBuilder configBuilder =
new SignerConfigurationBuilder()
.withUseConfigFile(useConfigFile)
.withMode("eth2")
.withGcpParameters(invalidGcpParams);

startSigner(configBuilder.build());

final String healthCheckJsonBody = signer.healthcheck().body().asString();

int keysLoaded = getHealtcheckKeysLoaded(healthCheckJsonBody, KEYS_CHECK_GCP_BULK_LOADING);
int errorCount = getHealthcheckErrorCount(healthCheckJsonBody, KEYS_CHECK_GCP_BULK_LOADING);

assertThat(keysLoaded).isEqualTo(0);
assertThat(errorCount).isEqualTo(1);
assertThat(getHealthcheckStatusValue(healthCheckJsonBody)).isEqualTo("DOWN");
}

@AfterAll
void cleanUpAwsResources() {
if (gcpSecretManagerUtil != null) {
secretNames.forEach(
secretName -> {
try {
gcpSecretManagerUtil.deleteSecret(secretName);
} catch (final RuntimeException e) {
LOG.warn(
"Unexpected error while deleting key {}{}: {}",
gcpSecretManagerUtil.getSecretsManagerPrefix(),
secretName,
e.getMessage());
}
});
gcpSecretManagerUtil.close();
}
}
}
2 changes: 1 addition & 1 deletion gradle/versions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ dependencyManagement {
dependency 'io.rest-assured:rest-assured:4.4.0'

// explicit declaring to override older versions with vulnerabilities
dependencySet(group: 'com.google.protobuf', version: '3.21.12') {
dependencySet(group: 'com.google.protobuf', version: '3.24.4') {
/*
com.google.protobuf:protobuf-java*:3.11.4 -> 3.19.4 // CVE-2022-3171
\--- io.jaegertracing:jaeger-proto:0.7.0
Expand Down
1 change: 1 addition & 0 deletions signing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,6 @@ dependencies {
testFixturesImplementation 'software.amazon.awssdk:auth'
testFixturesImplementation 'software.amazon.awssdk:secretsmanager'
testFixturesImplementation 'software.amazon.awssdk:kms'
testFixturesImplementation 'com.google.cloud:google-cloud-secretmanager'
testFixturesImplementation project(":common")
}
Loading

0 comments on commit 617535f

Please sign in to comment.