From 6ac5165b20ca8d70df0db5d7eddd995001f30ed6 Mon Sep 17 00:00:00 2001 From: Usman Saleem Date: Wed, 8 Jun 2022 13:33:12 +1000 Subject: [PATCH] Add prefix support for AWS secrets fetch (#154) * Add prefix support for AWS secrets fetch * Use @EnabledIfEnvironmentVariable annotation. Use AWS_SECRET* env variable instead of RO_AWS_SECRET* to allow correct initialization of default AWS secret manager. * Add test for map secrets with both prefix and tag filtering * changelog --- CHANGELOG.md | 5 +- .../signers/aws/AwsSecretsManager.java | 15 +- .../tech/pegasys/signers/aws/AwsSecret.java | 54 +++ .../aws/AwsSecretsManagerProviderTest.java | 44 +-- .../signers/aws/AwsSecretsManagerTest.java | 320 +++++++++++++----- .../tech/pegasys/signers/aws/SecretsMaps.java | 84 +++++ 6 files changed, 418 insertions(+), 104 deletions(-) create mode 100644 keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecret.java create mode 100644 keystorage/aws/src/test/java/tech/pegasys/signers/aws/SecretsMaps.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 11288b50..51a87cce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,15 @@ # Changelog +## 2.2.3 +- Added support for prefix filter for AWS secrets manager +--- ## 2.2.2 ### Features Added - Added support for bulk loading of secrets from AWS Secrets Manager ### Bugs Fixed - AWS secrets manager using environment config didn't work when using web identity tokens due to missing sts library. - +--- ## 2.2.1 ### Features Added - Update various dependent libraries versions diff --git a/keystorage/aws/src/main/java/tech/pegasys/signers/aws/AwsSecretsManager.java b/keystorage/aws/src/main/java/tech/pegasys/signers/aws/AwsSecretsManager.java index f162c1bb..15607f1e 100644 --- a/keystorage/aws/src/main/java/tech/pegasys/signers/aws/AwsSecretsManager.java +++ b/keystorage/aws/src/main/java/tech/pegasys/signers/aws/AwsSecretsManager.java @@ -83,9 +83,14 @@ public Optional fetchSecret(final String secretName) { } private ListSecretsIterable listSecrets( - final Collection tagKeys, final Collection tagValues) { + final Collection namePrefixes, + final Collection tagKeys, + final Collection tagValues) { final ListSecretsRequest.Builder listSecretsRequestBuilder = ListSecretsRequest.builder(); final List filters = new ArrayList<>(); + if (!namePrefixes.isEmpty()) { + filters.add(Filter.builder().key(FilterNameStringType.NAME).values(namePrefixes).build()); + } if (!tagKeys.isEmpty()) { filters.add(Filter.builder().key(FilterNameStringType.TAG_KEY).values(tagKeys).build()); } @@ -97,11 +102,12 @@ private ListSecretsIterable listSecrets( } public Collection mapSecrets( + final Collection namePrefixes, final Collection tagKeys, final Collection tagValues, final BiFunction mapper) { final Set result = ConcurrentHashMap.newKeySet(); - listSecrets(tagKeys, tagValues) + listSecrets(namePrefixes, tagKeys, tagValues) .iterator() .forEachRemaining( listSecretsResponse -> { @@ -128,8 +134,9 @@ public Collection mapSecrets( } } catch (final Exception e) { LOG.warn( - "Failed to map secret '{}' to requested object type.", - secretEntry.name()); + "Failed to map secret '{}' to requested object type due to: {}.", + secretEntry.name(), + e.getMessage()); } }); }); diff --git a/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecret.java b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecret.java new file mode 100644 index 00000000..15124e6e --- /dev/null +++ b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecret.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 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.signers.aws; + +import java.util.Objects; + +public class AwsSecret { + private final String secretValue; + private final String tagKey; + private final String tagValue; + + public AwsSecret(final String secretValue, final String tagKey, final String tagValue) { + this.secretValue = secretValue; + this.tagKey = tagKey; + this.tagValue = tagValue; + } + + public String getSecretValue() { + return secretValue; + } + + public String getTagKey() { + return tagKey; + } + + public String getTagValue() { + return tagValue; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AwsSecret that = (AwsSecret) o; + return secretValue.equals(that.secretValue) + && tagKey.equals(that.tagKey) + && tagValue.equals(that.tagValue); + } + + @Override + public int hashCode() { + return Objects.hash(secretValue, tagKey, tagValue); + } +} diff --git a/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerProviderTest.java b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerProviderTest.java index 3ec91c44..4782e65a 100644 --- a/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerProviderTest.java +++ b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerProviderTest.java @@ -14,36 +14,47 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.Optional; + import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable; @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@EnabledIfEnvironmentVariable( + named = "RW_AWS_ACCESS_KEY_ID", + matches = ".*", + disabledReason = "RW_AWS_ACCESS_KEY_ID env variable is required") +@EnabledIfEnvironmentVariable( + named = "RW_AWS_SECRET_ACCESS_KEY", + matches = ".*", + disabledReason = "RW_AWS_SECRET_ACCESS_KEY env variable is required") +@EnabledIfEnvironmentVariable( + named = "AWS_ACCESS_KEY_ID", + matches = ".*", + disabledReason = "AWS_ACCESS_KEY_ID env variable is required") +@EnabledIfEnvironmentVariable( + named = "AWS_SECRET_ACCESS_KEY", + matches = ".*", + disabledReason = "AWS_SECRET_ACCESS_KEY env variable is required") +@EnabledIfEnvironmentVariable( + named = "AWS_REGION", + matches = ".*", + disabledReason = "AWS_REGION env variable is required") class AwsSecretsManagerProviderTest { private final String AWS_ACCESS_KEY_ID = System.getenv("AWS_ACCESS_KEY_ID"); private final String AWS_SECRET_ACCESS_KEY = System.getenv("AWS_SECRET_ACCESS_KEY"); - private final String AWS_REGION = "us-east-2"; + private final String AWS_REGION = + Optional.ofNullable(System.getenv("AWS_REGION")).orElse("us-east-2"); private final String DIFFERENT_AWS_ACCESS_KEY_ID = System.getenv("RW_AWS_ACCESS_KEY_ID"); private final String DIFFERENT_AWS_SECRET_ACCESS_KEY = System.getenv("RW_AWS_SECRET_ACCESS_KEY"); private final String DIFFERENT_AWS_REGION = "us-east-1"; private AwsSecretsManagerProvider awsSecretsManagerProvider; - private void verifyEnvironmentVariables() { - Assumptions.assumeTrue( - DIFFERENT_AWS_ACCESS_KEY_ID != null, "Set RW_AWS_ACCESS_KEY_ID environment variable"); - Assumptions.assumeTrue( - DIFFERENT_AWS_SECRET_ACCESS_KEY != null, - "Set RW_AWS_SECRET_ACCESS_KEY environment variable"); - Assumptions.assumeTrue(AWS_ACCESS_KEY_ID != null, "Set AWS_ACCESS_KEY_ID environment variable"); - Assumptions.assumeTrue( - AWS_SECRET_ACCESS_KEY != null, "Set AWS_SECRET_ACCESS_KEY environment variable"); - } - private AwsSecretsManager createDefaultSecretsManager() { return awsSecretsManagerProvider.createAwsSecretsManager(); } @@ -68,11 +79,6 @@ private AwsSecretsManager createSecretsManagerDifferentKeysDifferentRegion() { DIFFERENT_AWS_ACCESS_KEY_ID, DIFFERENT_AWS_SECRET_ACCESS_KEY, DIFFERENT_AWS_REGION); } - @BeforeAll - void setup() { - verifyEnvironmentVariables(); - } - @BeforeEach void initializeCacheableAwsSecretsManagerProvider() { awsSecretsManagerProvider = new AwsSecretsManagerProvider(4); diff --git a/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerTest.java b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerTest.java index 358da415..a975ac59 100644 --- a/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerTest.java +++ b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/AwsSecretsManagerTest.java @@ -14,19 +14,23 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static tech.pegasys.signers.aws.SecretsMaps.SECRET_NAME_PREFIX_A; import java.util.AbstractMap.SimpleEntry; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assumptions; 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 software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.regions.Region; @@ -43,49 +47,62 @@ import software.amazon.awssdk.services.secretsmanager.model.UpdateSecretRequest; @TestInstance(TestInstance.Lifecycle.PER_CLASS) +@EnabledIfEnvironmentVariable( + named = "RW_AWS_ACCESS_KEY_ID", + matches = ".*", + disabledReason = "RW_AWS_ACCESS_KEY_ID env variable is required") +@EnabledIfEnvironmentVariable( + named = "RW_AWS_SECRET_ACCESS_KEY", + matches = ".*", + disabledReason = "RW_AWS_SECRET_ACCESS_KEY env variable is required") +@EnabledIfEnvironmentVariable( + named = "AWS_ACCESS_KEY_ID", + matches = ".*", + disabledReason = "AWS_ACCESS_KEY_ID env variable is required") +@EnabledIfEnvironmentVariable( + named = "AWS_SECRET_ACCESS_KEY", + matches = ".*", + disabledReason = "AWS_SECRET_ACCESS_KEY env variable is required") +@EnabledIfEnvironmentVariable( + named = "AWS_REGION", + matches = ".*", + disabledReason = "AWS_REGION env variable is required") class AwsSecretsManagerTest { private static final String RW_AWS_ACCESS_KEY_ID = System.getenv("RW_AWS_ACCESS_KEY_ID"); private static final String RW_AWS_SECRET_ACCESS_KEY = System.getenv("RW_AWS_SECRET_ACCESS_KEY"); - private static final String RO_AWS_ACCESS_KEY_ID = System.getenv("RO_AWS_ACCESS_KEY_ID"); - private static final String RO_AWS_SECRET_ACCESS_KEY = System.getenv("RO_AWS_SECRET_ACCESS_KEY"); - private static final String AWS_REGION = "us-east-2"; - - private static final String SECRET_NAME_PREFIX = "signers-aws-integration/"; - private static final String SECRET_NAME1_KEY1_VALA = SECRET_NAME_PREFIX + "secret1"; - private static final String SECRET_NAME2_KEY1_VALB = SECRET_NAME_PREFIX + "secret2"; - private static final String SECRET_NAME3_KEY2_VALC = SECRET_NAME_PREFIX + "secret3"; - private static final String SECRET_NAME4_KEY2_VALB = SECRET_NAME_PREFIX + "secret4"; - private static final String SECRET_VALUE1 = "secret-value1"; - private static final String SECRET_VALUE2 = "secret-value2"; - private static final String SECRET_VALUE3 = "secret-value3"; - private static final String SECRET_VALUE4 = "secret-value4"; + private static final String RO_AWS_ACCESS_KEY_ID = System.getenv("AWS_ACCESS_KEY_ID"); + private static final String RO_AWS_SECRET_ACCESS_KEY = System.getenv("AWS_SECRET_ACCESS_KEY"); + private static final String AWS_REGION = + Optional.ofNullable(System.getenv("AWS_REGION")).orElse("us-east-2"); private AwsSecretsManager awsSecretsManagerDefault; private AwsSecretsManager awsSecretsManagerExplicit; private AwsSecretsManager awsSecretsManagerInvalidCredentials; private SecretsManagerClient testSecretsManagerClient; - void verifyEnvironmentVariables() { - Assumptions.assumeTrue( - RW_AWS_ACCESS_KEY_ID != null, "Set RW_AWS_ACCESS_KEY_ID environment variable"); - Assumptions.assumeTrue( - RW_AWS_SECRET_ACCESS_KEY != null, "Set RW_AWS_SECRET_ACCESS_KEY environment variable"); - Assumptions.assumeTrue( - RO_AWS_ACCESS_KEY_ID != null, "Set RO_AWS_ACCESS_KEY_ID environment variable"); - Assumptions.assumeTrue( - RO_AWS_SECRET_ACCESS_KEY != null, "Set RO_AWS_SECRET_ACCESS_KEY environment variable"); - } + private final SecretsMaps secretsMaps = new SecretsMaps(); @BeforeAll void setup() { - verifyEnvironmentVariables(); initAwsSecretsManagers(); initTestSecretsManagerClient(); createTestSecrets(); } + private void createTestSecrets() { + secretsMaps + .getAllSecretsMap() + .forEach( + (key, awsSecret) -> + createOrUpdateSecret( + key, + awsSecret.getTagKey(), + awsSecret.getTagValue(), + awsSecret.getSecretValue())); + } + @AfterAll void teardown() { if (awsSecretsManagerDefault != null @@ -97,128 +114,278 @@ void teardown() { @Test void fetchSecretWithDefaultManager() { - Optional secret = awsSecretsManagerDefault.fetchSecret(SECRET_NAME1_KEY1_VALA); - assertThat(secret).hasValue(SECRET_VALUE1); + final Map secretsMap = secretsMaps.getAllSecretsMap(); + final String key = secretsMap.keySet().stream().findAny().orElseThrow(); + final Optional secret = awsSecretsManagerDefault.fetchSecret(key); + assertThat(secret).hasValue(secretsMap.get(key).getSecretValue()); } @Test void fetchSecretWithExplicitManager() { - Optional secret = awsSecretsManagerExplicit.fetchSecret(SECRET_NAME1_KEY1_VALA); - assertThat(secret).hasValue(SECRET_VALUE1); + final Map secretsMap = secretsMaps.getAllSecretsMap(); + final String key = secretsMap.keySet().stream().findAny().orElseThrow(); + + final Optional secret = awsSecretsManagerExplicit.fetchSecret(key); + assertThat(secret).hasValue(secretsMap.get(key).getSecretValue()); } @Test void fetchSecretWithInvalidCredentialsReturnsEmpty() { + final Map secretsMap = secretsMaps.getAllSecretsMap(); + final String key = secretsMap.keySet().stream().findAny().orElseThrow(); assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> awsSecretsManagerInvalidCredentials.fetchSecret(SECRET_NAME1_KEY1_VALA)) + .isThrownBy(() -> awsSecretsManagerInvalidCredentials.fetchSecret(key)) .withMessageContaining("Failed to fetch secret from AWS Secrets Manager."); } @Test void fetchingNonExistentSecretReturnsEmpty() { - Optional secret = awsSecretsManagerDefault.fetchSecret("signers-aws-integration/empty"); + Optional secret = awsSecretsManagerDefault.fetchSecret(SECRET_NAME_PREFIX_A + "empty"); assertThat(secret).isEmpty(); } @Test - void emptyTagFiltersReturnAllSecrets() { + void emptyFiltersReturnAllSecrets() { final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( - Collections.emptyList(), Collections.emptyList(), SimpleEntry::new); - - assertThat(secretEntries.stream().map(SimpleEntry::getKey)) - .contains( - SECRET_NAME1_KEY1_VALA, - SECRET_NAME2_KEY1_VALB, - SECRET_NAME3_KEY2_VALC, - SECRET_NAME4_KEY2_VALB); + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + SimpleEntry::new); + + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + assertThat(fetchedKeys).containsAll(secretsMaps.getAllSecretsMap().keySet()); } @Test void nonExistentTagFiltersReturnsEmpty() { final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( - List.of("nonexistent-tag-key"), List.of("nonexistent-tag-value"), SimpleEntry::new); + Collections.emptyList(), + List.of("nonexistent-tag-key"), + List.of("nonexistent-tag-value"), + SimpleEntry::new); + + assertThat(secretEntries).isEmpty(); + } + + @Test + void nonExistentPrefixFilterReturnsEmpty() { + final Collection> secretEntries = + awsSecretsManagerExplicit.mapSecrets( + List.of("nonexistent-secret-prefix"), + Collections.emptyList(), + Collections.emptyList(), + SimpleEntry::new); assertThat(secretEntries).isEmpty(); } + @Test + void nonExistentPrefixFilterWithTagFilterReturnsEmpty() { + final Collection> secretEntries = + awsSecretsManagerExplicit.mapSecrets( + List.of("nonexistent-secret-prefix"), + List.of("tagKey1"), + Collections.emptyList(), + SimpleEntry::new); + + assertThat(secretEntries).isEmpty(); + } + + @Test + void listAndMapSecretsWithPrefix() { + final Collection> secretEntries = + awsSecretsManagerExplicit.mapSecrets( + List.of(SECRET_NAME_PREFIX_A), + Collections.emptyList(), + Collections.emptyList(), + SimpleEntry::new); + + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + assertThat(fetchedKeys).containsAll(secretsMaps.getPrefixASecretsMap().keySet()); + assertThat(fetchedKeys) + .doesNotContainAnyElementsOf(secretsMaps.getPrefixBSecretsMap().keySet()); + } + + @Test + void listAndMapSecretsWithPrefixAndTags() { + final Collection> secretEntries = + awsSecretsManagerExplicit.mapSecrets( + List.of(SECRET_NAME_PREFIX_A), + List.of("tagKey1"), + Collections.emptyList(), + SimpleEntry::new); + + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + final Map>> expectedSecrets = + secretsMaps.getPrefixASecretsMap().entrySet().stream() + .collect( + Collectors.partitioningBy( + entry -> Objects.equals("tagKey1", entry.getValue().getTagKey()))); + + assertThat(fetchedKeys) + .containsAll( + expectedSecrets.get(Boolean.TRUE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); + assertThat(fetchedKeys) + .doesNotContainAnyElementsOf( + expectedSecrets.get(Boolean.FALSE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); + assertThat(fetchedKeys) + .doesNotContainAnyElementsOf(secretsMaps.getPrefixBSecretsMap().keySet()); + } + @Test void listAndMapSecretsWithMatchingTagKeys() { + final Set expectedTagKeys = Set.of("tagKey1"); final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( - List.of("tagKey1"), Collections.emptyList(), SimpleEntry::new); - - assertThat(secretEntries.stream().map(SimpleEntry::getKey)) - .contains(SECRET_NAME1_KEY1_VALA, SECRET_NAME2_KEY1_VALB) - .doesNotContain(SECRET_NAME3_KEY2_VALC, SECRET_NAME4_KEY2_VALB); - assertThat(secretEntries.stream().map(SimpleEntry::getValue)) - .contains(SECRET_VALUE1, SECRET_VALUE2) - .doesNotContain(SECRET_VALUE3, SECRET_VALUE4); + Collections.emptyList(), expectedTagKeys, Collections.emptyList(), SimpleEntry::new); + + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + final Map>> expectedSecrets = + secretsMaps.getAllSecretsMap().entrySet().stream() + .collect( + Collectors.partitioningBy( + entry -> expectedTagKeys.contains(entry.getValue().getTagKey()))); + + assertThat(fetchedKeys) + .containsAll( + expectedSecrets.get(Boolean.TRUE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); + assertThat(fetchedKeys) + .doesNotContainAnyElementsOf( + expectedSecrets.get(Boolean.FALSE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); } @Test void listAndMapSecretsWithMatchingTagValues() { + final Set expectedTagValues = Set.of("tagValB", "tagValC"); + final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( - Collections.emptyList(), List.of("tagValB", "tagValC"), SimpleEntry::new); - - assertThat(secretEntries.stream().map(SimpleEntry::getKey)) - .contains(SECRET_NAME2_KEY1_VALB, SECRET_NAME3_KEY2_VALC, SECRET_NAME4_KEY2_VALB) - .doesNotContain(SECRET_NAME1_KEY1_VALA); - assertThat(secretEntries.stream().map(SimpleEntry::getValue)) - .contains(SECRET_VALUE2, SECRET_VALUE3, SECRET_VALUE4) - .doesNotContain(SECRET_VALUE1); + Collections.emptyList(), Collections.emptyList(), expectedTagValues, SimpleEntry::new); + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + // expected tag values from both maps should have returned + final Map>> expectedSecrets = + secretsMaps.getAllSecretsMap().entrySet().stream() + .collect( + Collectors.partitioningBy( + entry -> expectedTagValues.contains(entry.getValue().getTagValue()))); + + assertThat(fetchedKeys) + .containsAll( + expectedSecrets.get(Boolean.TRUE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); + assertThat(fetchedKeys) + .doesNotContainAnyElementsOf( + expectedSecrets.get(Boolean.FALSE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); } @Test void listAndMapSecretsWithMatchingTagKeysAndValues() { + final Set expectedTagKeys = Set.of("tagKey1"); + final Set expectedTagValues = Set.of("tagValB"); + final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( - List.of("tagKey1"), List.of("tagValB"), SimpleEntry::new); - - assertThat(secretEntries.stream().map(SimpleEntry::getKey)) - .contains(SECRET_NAME2_KEY1_VALB) - .doesNotContain(SECRET_NAME1_KEY1_VALA, SECRET_NAME3_KEY2_VALC, SECRET_NAME4_KEY2_VALB); - assertThat(secretEntries.stream().map(SimpleEntry::getValue)) - .contains(SECRET_VALUE2) - .doesNotContain(SECRET_VALUE1, SECRET_VALUE3, SECRET_VALUE4); + Collections.emptyList(), expectedTagKeys, expectedTagValues, SimpleEntry::new); + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + final Map>> expectedSecrets = + secretsMaps.getAllSecretsMap().entrySet().stream() + .collect( + Collectors.partitioningBy( + entry -> + expectedTagKeys.contains(entry.getValue().getTagKey()) + && expectedTagValues.contains(entry.getValue().getTagValue()))); + + assertThat(fetchedKeys) + .containsAll( + expectedSecrets.get(Boolean.TRUE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); + assertThat(fetchedKeys) + .doesNotContainAnyElementsOf( + expectedSecrets.get(Boolean.FALSE).stream() + .map(Map.Entry::getKey) + .collect(Collectors.toSet())); } @Test void throwsAwayObjectsWhichMapToNull() { + final Map secretsMap = secretsMaps.getAllSecretsMap(); + final String expectedKey = secretsMap.keySet().stream().findAny().orElseThrow(); + final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), (name, value) -> { - if (name.equals(SECRET_NAME1_KEY1_VALA)) { + if (name.equals(expectedKey)) { return null; } return new SimpleEntry<>(name, value); }); + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + final Set expectedKeys = + secretsMap.keySet().stream() + .filter(secretValue -> !Objects.equals(expectedKey, secretValue)) + .collect(Collectors.toSet()); - assertThat(secretEntries.stream().map(SimpleEntry::getKey)) - .contains(SECRET_NAME2_KEY1_VALB, SECRET_NAME3_KEY2_VALC, SECRET_NAME4_KEY2_VALB) - .doesNotContain(SECRET_NAME1_KEY1_VALA); + assertThat(fetchedKeys).containsAll(expectedKeys); + assertThat(fetchedKeys).doesNotContain(expectedKey); } @Test void throwsAwayObjectsThatFailMapper() { + final Map secretsMap = secretsMaps.getAllSecretsMap(); + final String expectedKey = secretsMap.keySet().stream().findAny().orElseThrow(); final Collection> secretEntries = awsSecretsManagerExplicit.mapSecrets( + Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), (name, value) -> { - if (name.equals(SECRET_NAME1_KEY1_VALA)) { + if (name.equals(expectedKey)) { throw new RuntimeException("Arbitrary Failure"); } return new SimpleEntry<>(name, value); }); - assertThat(secretEntries.stream().map(SimpleEntry::getKey)) - .contains(SECRET_NAME2_KEY1_VALB, SECRET_NAME3_KEY2_VALC, SECRET_NAME4_KEY2_VALB) - .doesNotContain(SECRET_NAME1_KEY1_VALA); + final Set fetchedKeys = + secretEntries.stream().map(SimpleEntry::getKey).collect(Collectors.toSet()); + + final Set expectedKeys = + secretsMap.keySet().stream() + .filter(secretValue -> !Objects.equals(expectedKey, secretValue)) + .collect(Collectors.toSet()); + + assertThat(fetchedKeys).containsAll(expectedKeys); + assertThat(fetchedKeys).doesNotContain(expectedKey); } private void initAwsSecretsManagers() { @@ -257,13 +424,6 @@ private void closeAwsSecretsManagers() { awsSecretsManagerInvalidCredentials.close(); } - private void createTestSecrets() { - createOrUpdateSecret(SECRET_NAME1_KEY1_VALA, "tagKey1", "tagValA", SECRET_VALUE1); - createOrUpdateSecret(SECRET_NAME2_KEY1_VALB, "tagKey1", "tagValB", SECRET_VALUE2); - createOrUpdateSecret(SECRET_NAME3_KEY2_VALC, "tagKey2", "tagValC", SECRET_VALUE3); - createOrUpdateSecret(SECRET_NAME4_KEY2_VALB, "tagKey2", "tagValB", SECRET_VALUE4); - } - private void createOrUpdateSecret( final String testSecretName, final String tagKey, diff --git a/keystorage/aws/src/test/java/tech/pegasys/signers/aws/SecretsMaps.java b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/SecretsMaps.java new file mode 100644 index 00000000..9f367519 --- /dev/null +++ b/keystorage/aws/src/test/java/tech/pegasys/signers/aws/SecretsMaps.java @@ -0,0 +1,84 @@ +/* + * Copyright 2022 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.signers.aws; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class SecretsMaps { + static final String SECRET_NAME_PREFIX_A = "signers-aws-integration/a/"; + static final String SECRET_NAME_PREFIX_B = "signers-aws-integration/b/"; + + private final Map prefixASecretsMap; + private final Map prefixBSecretsMap; + private final Map allSecretsMap; + + public SecretsMaps() { + final Map secretMapA = new HashMap<>(); + final Map secretMapB = new HashMap<>(); + final Map allSecretsMap = new HashMap<>(); + + for (int i = 1; i <= 4; i++) { + final AwsSecret awsSecret = computeSecretValue(i); + secretMapA.put(computeMapAKey(i), awsSecret); + secretMapB.put(computeMapBKey(i), awsSecret); + } + allSecretsMap.putAll(secretMapA); + allSecretsMap.putAll(secretMapB); + + this.prefixASecretsMap = Collections.unmodifiableMap(secretMapA); + this.prefixBSecretsMap = Collections.unmodifiableMap(secretMapB); + this.allSecretsMap = Collections.unmodifiableMap(allSecretsMap); + } + + public Map getPrefixASecretsMap() { + return prefixASecretsMap; + } + + public Map getPrefixBSecretsMap() { + return prefixBSecretsMap; + } + + public Map getAllSecretsMap() { + return allSecretsMap; + } + + private static String computeMapAKey(final int i) { + return String.format("%ssecret%d", SECRET_NAME_PREFIX_A, i); + } + + private static String computeMapBKey(final int i) { + return String.format("%ssecret%d", SECRET_NAME_PREFIX_B, i); + } + + private static AwsSecret computeSecretValue(final int i) { + final String value = String.format("secret-value%d", i); + final AwsSecret awsSecret; + switch (i) { + case 1: + awsSecret = new AwsSecret(value, "tagKey1", "tagValA"); + break; + case 2: + awsSecret = new AwsSecret(value, "tagKey1", "tagValB"); + break; + case 3: + awsSecret = new AwsSecret(value, "tagKey2", "tagValC"); + break; + default: + awsSecret = new AwsSecret(value, "tagKey2", "tagValB"); + break; + } + return awsSecret; + } +}