From d8f70cd2bbd982d97c01cfbcf3b11db1a9e9d0c4 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Thu, 31 Oct 2024 10:38:27 +0100 Subject: [PATCH 1/5] Introduce aws path and a role with a path --- .../java/ai/vespa/secret/aws/AwsPath.java | 52 +++++++++++++++++++ .../java/ai/vespa/secret/aws/AwsRolePath.java | 50 ++++++++++++++++++ .../java/ai/vespa/secret/aws/AwsPathTest.java | 35 +++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsPath.java create mode 100644 jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java create mode 100644 jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AwsPathTest.java diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsPath.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsPath.java new file mode 100644 index 00000000000..aa5994c888d --- /dev/null +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsPath.java @@ -0,0 +1,52 @@ +/* + * // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + * + */ + +package ai.vespa.secret.aws; + +import ai.vespa.validation.PatternedStringWrapper; + +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.joining; + +/** + * @author gjoranv + */ +public class AwsPath extends PatternedStringWrapper { + + // The limit for a path in AWS is 512 chars, but we can limit ourselves to + // "/tenant-secret../". + // Here, we assume 128 chars tenant name and max 30 chars system name + private static final Pattern namePattern = Pattern.compile("/[/.a-zA-Z0-9_-]{0,173}"); + + + private AwsPath(String name) { + super(name, namePattern, "AWS path"); + } + + /** Creates a new path from the given elements, adding the leading and trailing slashes */ + public static AwsPath of(String... elements) { + if (elements != null && elements.length > 0 && elements[0] != null && ! elements[0].isEmpty()) { + return new AwsPath( + Stream.of(elements) + .map(String::toLowerCase) + // TODO: consider using '.' as delimiter, to prevent deep paths + .collect(joining("/", "/", "/"))); + + } + return new AwsPath("/"); + } + + public static AwsPath root() { + return new AwsPath("/"); + } + + /** Creates a new instance from the given string, that is assumed to contain the leading and trailing slashes */ + public static AwsPath fromAwsPathString(String path) { + return new AwsPath(path); + } + +} diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java new file mode 100644 index 00000000000..a3ebd78b4a8 --- /dev/null +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java @@ -0,0 +1,50 @@ +/* + * // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + * + */ + +package ai.vespa.secret.aws; + +import com.yahoo.vespa.athenz.api.AwsRole; + +import java.util.Objects; + +/** + * An AWS role with path. We use paths because AWS roles can only be up to 64 chars long. + * Note that AWS role names must be unique across paths within an account. + * + * @author gjoranv + */ +public record AwsRolePath(AwsPath path, AwsRole role) { + + public AwsRolePath { + Objects.requireNonNull(path, "path cannot be null"); + Objects.requireNonNull(role, "role cannot be null"); + } + + public static AwsRolePath fromStrings(String path, String roleName) { + if (roleName == null || roleName.isEmpty()) { + throw new IllegalArgumentException("roleName cannot be null or empty"); + } + return new AwsRolePath(AwsPath.fromAwsPathString(path), new AwsRole(roleName)); + } + + public static AwsRolePath atRoot(String roleName) { + return new AwsRolePath(AwsPath.of(), new AwsRole(roleName)); + } + + public String fullName() { + return "%s%s".formatted(path.value(), role.name()); + } + + // Only for compatibility with existing APIs in AwsCredentials + public AwsRole fullRole() { + return new AwsRole(fullName()); + } + + @Override + public String toString() { + return "AwsRolePath{" + path + ", " + role.name() + '}'; + } + +} diff --git a/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AwsPathTest.java b/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AwsPathTest.java new file mode 100644 index 00000000000..33b4d9cafdf --- /dev/null +++ b/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AwsPathTest.java @@ -0,0 +1,35 @@ +/* + * // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + * + */ + +package ai.vespa.secret.aws; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author gjoranv + */ +public class AwsPathTest { + + @Test + void it_starts_and_ends_with_slash() { + var path = AwsPath.of("a", "b", "c"); + assertEquals("/a/b/c/", path.value()); + } + + @Test + void it_is_lowercased() { + var path = AwsPath.of("A", "B", "C"); + assertEquals("/a/b/c/", path.value()); + } + + @Test + void empty_path_yields_single_slash() { + var path = AwsPath.of(); + assertEquals("/", path.value()); + } + +} From 0f9cc9fb8021ce225c717bd71e317cdbe5f3de50 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Fri, 1 Nov 2024 00:06:09 +0100 Subject: [PATCH 2/5] Replace AwsRole with AwsRolePath --- .../ai/vespa/secret/aws/AsmSecretReader.java | 3 +-- .../vespa/secret/aws/AsmSecretStoreBase.java | 19 +++++++------ .../secret/aws/AsmTenantSecretReader.java | 7 +++-- .../java/ai/vespa/secret/aws/AthenzUtil.java | 27 +++++++++++++++---- .../aws/testutil/AsmSecretReaderTester.java | 6 ++--- .../aws/testutil/AsmSecretTesterBase.java | 6 ++--- .../secret/aws/AsmTenantSecretReaderTest.java | 6 ++--- 7 files changed, 44 insertions(+), 30 deletions(-) diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretReader.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretReader.java index d79dfbd1b03..c085cb6f83c 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretReader.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretReader.java @@ -11,7 +11,6 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.yahoo.vespa.athenz.api.AthenzDomain; -import com.yahoo.vespa.athenz.api.AwsRole; import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient; import com.yahoo.vespa.athenz.client.zts.ZtsClient; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; @@ -64,7 +63,7 @@ private AsmSecretReader(ZtsClient ztsClient, AthenzDomain domain) { } // For testing - public AsmSecretReader(Function clientAndCredentialsSupplier) { + public AsmSecretReader(Function clientAndCredentialsSupplier) { super(clientAndCredentialsSupplier); cache = initCache(); ztsClientCloser = () -> {}; diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java index b4c6b4b2d1f..445b39d4d24 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java @@ -4,7 +4,6 @@ import ai.vespa.secret.model.VaultName; import com.yahoo.component.AbstractComponent; import com.yahoo.vespa.athenz.api.AthenzDomain; -import com.yahoo.vespa.athenz.api.AwsRole; import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials; import com.yahoo.vespa.athenz.aws.AwsCredentials; import com.yahoo.vespa.athenz.client.zts.ZtsClient; @@ -13,11 +12,11 @@ import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; -import java.util.stream.Collectors; /** * Base class for AWS Secrets Manager read or write clients. @@ -28,9 +27,9 @@ public abstract class AsmSecretStoreBase extends AbstractComponent implements Au public static final String AWSCURRENT = "AWSCURRENT"; - private final Function clientAndCredentialsSupplier; + private final Function clientAndCredentialsSupplier; - private final ConcurrentMap clientMap = new ConcurrentHashMap<>(); + private final ConcurrentMap clientMap = new ConcurrentHashMap<>(); public AsmSecretStoreBase(ZtsClient ztsClient, AthenzDomain athenzDomain) { @@ -41,12 +40,12 @@ public AsmSecretStoreBase(ZtsClient ztsClient, AthenzDomain athenzDomain) { } // For testing - protected AsmSecretStoreBase(Function clientAndCredentialsSupplier) { + protected AsmSecretStoreBase(Function clientAndCredentialsSupplier) { this.clientAndCredentialsSupplier = clientAndCredentialsSupplier; } /** Returns the AWS role associated with the given vault. */ - protected abstract AwsRole awsRole(VaultName vault); + protected abstract AwsRolePath awsRole(VaultName vault); protected SecretsManagerClient getClient(VaultName vault) { @@ -55,11 +54,11 @@ protected SecretsManagerClient getClient(VaultName vault) { return clientMap.get(awsRole); } - private static AwsCredentialsProvider getAwsSessionCredsProvider(AwsRole role, + private static AwsCredentialsProvider getAwsSessionCredsProvider(AwsRolePath role, ZtsClient ztsClient, AthenzDomain athenzDomain) { - AwsCredentials credentials = new AwsCredentials(ztsClient, athenzDomain, role); + AwsCredentials credentials = new AwsCredentials(ztsClient, athenzDomain, role.fullRole()); return () -> { AwsTemporaryCredentials temporary = credentials.get(); return AwsSessionCredentials.create(temporary.accessKeyId(), @@ -86,8 +85,8 @@ public void deconstruct() { } // Only for testing - public Set clientRoleNames() { - return clientMap.keySet().stream().map(AwsRole::name).collect(Collectors.toSet()); + public Set clientRoleNames() { + return new HashSet<>(clientMap.keySet()); } } diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmTenantSecretReader.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmTenantSecretReader.java index 37de942b294..ac6d8caa187 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmTenantSecretReader.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmTenantSecretReader.java @@ -5,7 +5,6 @@ import ai.vespa.secret.model.Key; import ai.vespa.secret.model.VaultName; import com.yahoo.component.annotation.Inject; -import com.yahoo.vespa.athenz.api.AwsRole; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; @@ -29,7 +28,7 @@ public AsmTenantSecretReader(AsmSecretConfig config, ServiceIdentityProvider ide } // For testing - AsmTenantSecretReader(Function clientAndCredentialsSupplier, + AsmTenantSecretReader(Function clientAndCredentialsSupplier, String system, String tenant) { super(clientAndCredentialsSupplier); this.system = system; @@ -37,8 +36,8 @@ public AsmTenantSecretReader(AsmSecretConfig config, ServiceIdentityProvider ide } @Override - protected AwsRole awsRole(VaultName vault) { - return new AwsRole(AthenzUtil.resourceEntityName(system, tenant, vault)); + protected AwsRolePath awsRole(VaultName vault) { + return AthenzUtil.awsReaderRole(system, tenant, vault); } @Override diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java index a676184729a..8dae25f079d 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java @@ -3,12 +3,14 @@ import ai.vespa.secret.model.Role; import ai.vespa.secret.model.VaultName; +import com.yahoo.vespa.athenz.api.AwsRole; /** * Tenant secret store constants and functions used across modules and repos. * Note that we cannot use SystemName and TenantName from config-provision here, * as that bundle is not available in tenant containers. + * * @author gjoranv */ public class AthenzUtil { @@ -16,18 +18,33 @@ public class AthenzUtil { // Serves as a namespace for resources in athenz and AWS public static final String PREFIX = "tenant-secret"; + /* tenant-secret.. */ public static String roleAndPolicyPrefix(String systemName, String tenantName) { - return "%s.%s.%s".formatted(PREFIX, systemName, tenantName).toLowerCase(); + return String.join(".", PREFIX, systemName, tenantName).toLowerCase(); } + /* tenant-secret....reader */ public static String resourceEntityName(String system, String tenant, VaultName vault) { // Note that the domain name is added by AthenzDomainName, such that actual resource name will be // e.g. vespa.external.tenant-secret: - // The aws role name is: tenant-secret....reader - return "%s.%s.%s".formatted(roleAndPolicyPrefix(system, tenant), - vault.value(), - Role.READER.value()) + return "%s.%s".formatted(roleAndPolicyPrefix(system, tenant), + readerRoleName(vault)) .toLowerCase(); } + /* Path: /tenant-secret/// */ + public static AwsPath awsPath(String systemName, String tenantName) { + return AwsPath.of(PREFIX, systemName, tenantName); + } + + /* Path: /tenant-secret/// + Role: .reader */ + public static AwsRolePath awsReaderRole(String systemName, String tenantName, VaultName vault) { + return new AwsRolePath(awsPath(systemName, tenantName), new AwsRole(readerRoleName(vault))); + } + + /* .reader */ + private static String readerRoleName(VaultName vault) { + return "%s.%s".formatted(vault.value(), Role.READER.value()); + } + } diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretReaderTester.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretReaderTester.java index dc5fa432f5d..94b1e7ce726 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretReaderTester.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretReaderTester.java @@ -5,9 +5,9 @@ package ai.vespa.secret.aws.testutil; +import ai.vespa.secret.aws.AwsRolePath; import ai.vespa.secret.model.Key; import ai.vespa.secret.model.SecretVersionState; -import com.yahoo.vespa.athenz.api.AwsRole; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest; import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse; import software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException; @@ -30,14 +30,14 @@ public void put(Key key, SecretVersion... versions) { secrets.put(awsSecretIdMapper.apply(key), List.of(versions)); } - public MockSecretsReader newClient(AwsRole awsRole) { + public MockSecretsReader newClient(AwsRolePath awsRole) { return new MockSecretsReader(awsRole); } public class MockSecretsReader extends MockSecretsManagerClient { - MockSecretsReader(AwsRole awsRole) { + MockSecretsReader(AwsRolePath awsRole) { super(awsRole); } diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretTesterBase.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretTesterBase.java index fdb80fd8634..1cb2de4d131 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretTesterBase.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/testutil/AsmSecretTesterBase.java @@ -5,9 +5,9 @@ package ai.vespa.secret.aws.testutil; +import ai.vespa.secret.aws.AwsRolePath; import ai.vespa.secret.model.Key; import ai.vespa.secret.model.SecretVersionState; -import com.yahoo.vespa.athenz.api.AwsRole; import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; @@ -58,10 +58,10 @@ public List clients() { public abstract class MockSecretsManagerClient implements SecretsManagerClient { - public final AwsRole awsRole; + public final AwsRolePath awsRole; public boolean isClosed = false; - protected MockSecretsManagerClient(AwsRole awsRole) { + protected MockSecretsManagerClient(AwsRolePath awsRole) { this.awsRole = awsRole; clients.add(this); } diff --git a/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AsmTenantSecretReaderTest.java b/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AsmTenantSecretReaderTest.java index a19b2ed1887..ecea026a03f 100644 --- a/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AsmTenantSecretReaderTest.java +++ b/jdisc-cloud-aws/src/test/java/ai/vespa/secret/aws/AsmTenantSecretReaderTest.java @@ -46,9 +46,9 @@ AsmTenantSecretReader secretReader() { @Test void it_creates_one_credentials_and_client_per_vault_and_closes_them() { var vault1 = VaultName.of("vault1"); - var awsRole1 = "tenant-secret.publiccd.tenant1.vault1.reader"; + var awsRole1 = AwsRolePath.fromStrings("/tenant-secret/publiccd/tenant1/", "vault1.reader"); var vault2 = VaultName.of("vault2"); - var awsRole2 = "tenant-secret.publiccd.tenant1.vault2.reader"; + var awsRole2 = AwsRolePath.fromStrings("/tenant-secret/publiccd/tenant1/", "vault2.reader"); var secret1 = new SecretVersion("1", SecretVersionState.CURRENT, "secret1"); var secret2 = new SecretVersion("2", SecretVersionState.CURRENT, "secret2"); @@ -59,7 +59,7 @@ void it_creates_one_credentials_and_client_per_vault_and_closes_them() { tester.put(key1, secret1); tester.put(key2, secret2); - try (var reader = secretReader()){ + try (var reader = secretReader()) { reader.getSecret(key1); reader.getSecret(key2); From 2ab013d6f32636d2dcbc95274f4ee1518cb94572 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Mon, 4 Nov 2024 18:03:25 +0100 Subject: [PATCH 3/5] minor: indent, comments and renaming --- .../java/ai/vespa/secret/model/VaultName.java | 6 +++--- .../java/ai/vespa/secret/aws/AthenzUtil.java | 17 +++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/container-disc/src/main/java/ai/vespa/secret/model/VaultName.java b/container-disc/src/main/java/ai/vespa/secret/model/VaultName.java index 32392b396b6..32fdf05920b 100644 --- a/container-disc/src/main/java/ai/vespa/secret/model/VaultName.java +++ b/container-disc/src/main/java/ai/vespa/secret/model/VaultName.java @@ -9,13 +9,13 @@ */ public class VaultName extends PatternedStringWrapper { - private static final Pattern namePattern = Pattern.compile("[.a-zA-Z0-9_-]{1,64}"); + private static final Pattern namePattern = Pattern.compile("[.a-zA-Z0-9_-]{1,64}"); - private VaultName(String name) { + private VaultName(String name) { super(name, namePattern, "Vault name"); } - public static VaultName of(String name) { + public static VaultName of(String name) { return new VaultName(name); } diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java index 8dae25f079d..21005ed21fe 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AthenzUtil.java @@ -23,12 +23,12 @@ public static String roleAndPolicyPrefix(String systemName, String tenantName) { return String.join(".", PREFIX, systemName, tenantName).toLowerCase(); } - /* tenant-secret....reader */ + /* tenant-secret....reader */ public static String resourceEntityName(String system, String tenant, VaultName vault) { // Note that the domain name is added by AthenzDomainName, such that actual resource name will be // e.g. vespa.external.tenant-secret: return "%s.%s".formatted(roleAndPolicyPrefix(system, tenant), - readerRoleName(vault)) + athenzReaderRoleName(vault)) .toLowerCase(); } @@ -37,13 +37,18 @@ public static AwsPath awsPath(String systemName, String tenantName) { return AwsPath.of(PREFIX, systemName, tenantName); } - /* Path: /tenant-secret/// + Role: .reader */ + /* + * Path: /tenant-secret/// + Role: .reader + * + * We use vaultId instead of vaultName because vaultName is not unique across tenants, + * and role names must be unique across paths within an account. + */ public static AwsRolePath awsReaderRole(String systemName, String tenantName, VaultName vault) { - return new AwsRolePath(awsPath(systemName, tenantName), new AwsRole(readerRoleName(vault))); + return new AwsRolePath(awsPath(systemName, tenantName), new AwsRole(athenzReaderRoleName(vault))); } - /* .reader */ - private static String readerRoleName(VaultName vault) { + /* .reader */ + private static String athenzReaderRoleName(VaultName vault) { return "%s.%s".formatted(vault.value(), Role.READER.value()); } From 7cfbe45b13105a538fbad97e45a7fbea4176a429 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Mon, 4 Nov 2024 18:12:49 +0100 Subject: [PATCH 4/5] Move VaultId to container-disc --- .../java/ai/vespa/secret/model/VaultId.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 container-disc/src/main/java/ai/vespa/secret/model/VaultId.java diff --git a/container-disc/src/main/java/ai/vespa/secret/model/VaultId.java b/container-disc/src/main/java/ai/vespa/secret/model/VaultId.java new file mode 100644 index 00000000000..1ec5c27f676 --- /dev/null +++ b/container-disc/src/main/java/ai/vespa/secret/model/VaultId.java @@ -0,0 +1,21 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.secret.model; + +/** + * Internally created id for a vault. Usually a UUID. + * + * @author gjoranv + */ +public record VaultId(String value) { + + public VaultId { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException("Version id cannot be null or empty"); + } + } + + public static VaultId of(String value) { + return new VaultId(value); + } + +} From da3423c77f1bb4e029571287843ec00ed319dd61 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Wed, 6 Nov 2024 17:33:54 +0100 Subject: [PATCH 5/5] Athenz resource referring to an AWS role cannot include leading '/' --- .../ai/vespa/secret/aws/AsmSecretStoreBase.java | 2 +- .../main/java/ai/vespa/secret/aws/AwsRolePath.java | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java index 445b39d4d24..9a31e143bbf 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AsmSecretStoreBase.java @@ -58,7 +58,7 @@ private static AwsCredentialsProvider getAwsSessionCredsProvider(AwsRolePath rol ZtsClient ztsClient, AthenzDomain athenzDomain) { - AwsCredentials credentials = new AwsCredentials(ztsClient, athenzDomain, role.fullRole()); + AwsCredentials credentials = new AwsCredentials(ztsClient, athenzDomain, role.athenzAwsRole()); return () -> { AwsTemporaryCredentials temporary = credentials.get(); return AwsSessionCredentials.create(temporary.accessKeyId(), diff --git a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java index a3ebd78b4a8..5b46c16b36e 100644 --- a/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java +++ b/jdisc-cloud-aws/src/main/java/ai/vespa/secret/aws/AwsRolePath.java @@ -33,13 +33,14 @@ public static AwsRolePath atRoot(String roleName) { return new AwsRolePath(AwsPath.of(), new AwsRole(roleName)); } - public String fullName() { - return "%s%s".formatted(path.value(), role.name()); + // When used as an Athenz resource name, the leading '/' must be removed + public String athenzResourceName() { + return fullName().substring(1); } // Only for compatibility with existing APIs in AwsCredentials - public AwsRole fullRole() { - return new AwsRole(fullName()); + public AwsRole athenzAwsRole() { + return new AwsRole(athenzResourceName()); } @Override @@ -47,4 +48,8 @@ public String toString() { return "AwsRolePath{" + path + ", " + role.name() + '}'; } + private String fullName() { + return "%s%s".formatted(path.value(), role.name()); + } + }