From c1b088ba593f482dee90f530298cc515f5300b0c Mon Sep 17 00:00:00 2001 From: Valerij Fredriksen Date: Fri, 18 Oct 2024 09:59:23 +0200 Subject: [PATCH] ZtsClient: Support getting Azure temporary credentials --- .../athenz/api/AwsTemporaryCredentials.java | 32 +++-------------- .../athenz/api/AzureTemporaryCredentials.java | 13 +++++++ .../athenz/client/zms/DefaultZmsClient.java | 6 +--- .../athenz/client/zts/DefaultZtsClient.java | 27 ++++++++++++++ .../vespa/athenz/client/zts/ZtsClient.java | 20 +++++++++-- ...AwsTemporaryCredentialsResponseEntity.java | 4 +-- ...ureTemporaryCredentialsResponseEntity.java | 35 +++++++++++++++++++ .../TemporaryCredentialsResponse.java | 9 +++++ 8 files changed, 109 insertions(+), 37 deletions(-) create mode 100644 vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AzureTemporaryCredentials.java create mode 100644 vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AzureTemporaryCredentialsResponseEntity.java create mode 100644 vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/TemporaryCredentialsResponse.java diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsTemporaryCredentials.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsTemporaryCredentials.java index b3b415704ae5..0c954f5704f4 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsTemporaryCredentials.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsTemporaryCredentials.java @@ -6,32 +6,8 @@ /** * @author mortent */ -public class AwsTemporaryCredentials { - private final String accessKeyId; - private final String secretAccessKey; - private final String sessionToken; - private final Instant expiration; - - public AwsTemporaryCredentials(String accessKeyId, String secretAccessKey, String sessionToken, Instant expiration) { - this.accessKeyId = accessKeyId; - this.secretAccessKey = secretAccessKey; - this.sessionToken = sessionToken; - this.expiration = expiration; - } - - public String accessKeyId() { - return accessKeyId; - } - - public String secretAccessKey() { - return secretAccessKey; - } - - public String sessionToken() { - return sessionToken; - } - - public Instant expiration() { - return expiration; - } +public record AwsTemporaryCredentials(String accessKeyId, + String secretAccessKey, + String sessionToken, + Instant expiration) { } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AzureTemporaryCredentials.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AzureTemporaryCredentials.java new file mode 100644 index 000000000000..69103aea3a20 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AzureTemporaryCredentials.java @@ -0,0 +1,13 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.api; + +import java.time.Instant; + +/** + * @author freva + */ +public record AzureTemporaryCredentials(String azureSubscription, + String azureTenant, + String accessToken, + Instant expiration) { +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java index f054abf2bd14..47f7961c38d5 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zms/DefaultZmsClient.java @@ -1,7 +1,6 @@ // Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.athenz.client.zms; -import com.fasterxml.jackson.databind.ObjectMapper; import com.yahoo.athenz.auth.util.Crypto; import com.yahoo.security.KeyUtils; import com.yahoo.vespa.athenz.api.AthenzAssertion; @@ -52,8 +51,6 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import static com.yahoo.yolean.Exceptions.uncheck; - /** * @author bjorncs @@ -231,10 +228,9 @@ public AthenzDomainMeta getDomainMeta(AthenzDomain domain) { @Override public void updateDomain(AthenzDomain domain, String mainKey, Map attributes) { - String domainMeta = uncheck(() -> new ObjectMapper().writeValueAsString(attributes)); HttpUriRequest request = RequestBuilder.put() .setUri(zmsUrl.resolve("domain/%s/meta/system/%s".formatted(domain.getName(), mainKey))) - .setEntity(new StringEntity(domainMeta, ContentType.APPLICATION_JSON)) + .setEntity(toJsonStringEntity(attributes)) .build(); execute(request, response -> readEntity(response, Void.class)); } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java index 57759d620d7e..a573368e1aba 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/DefaultZtsClient.java @@ -9,6 +9,7 @@ import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AwsRole; import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials; +import com.yahoo.vespa.athenz.api.AzureTemporaryCredentials; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.api.ZToken; import com.yahoo.vespa.athenz.client.ErrorHandler; @@ -16,6 +17,7 @@ import com.yahoo.vespa.athenz.client.zms.bindings.AccessResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.AccessTokenResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.AwsTemporaryCredentialsResponseEntity; +import com.yahoo.vespa.athenz.client.zts.bindings.AzureTemporaryCredentialsResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.IdentityRefreshRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.IdentityResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.InstanceIdentityCredentials; @@ -24,6 +26,7 @@ import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateRequestEntity; import com.yahoo.vespa.athenz.client.zts.bindings.RoleCertificateResponseEntity; import com.yahoo.vespa.athenz.client.zts.bindings.RoleTokenResponseEntity; +import com.yahoo.vespa.athenz.client.zts.bindings.TemporaryCredentialsResponse; import com.yahoo.vespa.athenz.client.zts.bindings.TenantDomainsResponseEntity; import com.yahoo.vespa.athenz.client.zts.utils.IdentityCsrGenerator; import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider; @@ -39,6 +42,7 @@ import java.security.cert.X509Certificate; import java.time.Duration; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -228,6 +232,29 @@ public AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDom }); } + @Override + public AzureTemporaryCredentials getAzureTemporaryCredentials(AthenzRole athenzRole, String azureIdentityId) { + return getExternalTemporaryCredentials("azure", athenzRole.domain(), + Map.of("athenzRoleName", athenzRole.roleName(), "azureClientId", azureIdentityId), + AzureTemporaryCredentialsResponseEntity.class); + } + + @Override + public AzureTemporaryCredentials getAzureTemporaryCredentials(AthenzRole athenzRole, String azureResourceGroup, String azureIdentityName) { + return getExternalTemporaryCredentials("azure", athenzRole.domain(), + Map.of("athenzRoleName", athenzRole.roleName(), "azureResourceGroup", azureResourceGroup, "azureClientName", azureIdentityName), + AzureTemporaryCredentialsResponseEntity.class); + } + + private > T getExternalTemporaryCredentials(String provider, AthenzDomain domain, Map attributes, Class responseClass) { + URI uri = ztsUrl.resolve("external/%s/domain/%s/creds".formatted(provider, domain.getName())); + RequestBuilder requestBuilder = RequestBuilder.post(uri) + .setEntity(toJsonStringEntity(Map.of("clientId", "%s.%s".formatted(domain.getName(), provider), "attributes", attributes))); + + HttpUriRequest request = requestBuilder.build(); + return execute(request, response -> readEntity(response, responseClass)).credentials(); + } + @Override public boolean hasAccess(AthenzResourceName resource, String action, AthenzIdentity identity) { URI uri = ztsUrl.resolve(String.format("access/%s/%s?principal=%s", diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java index 301efc208888..dafde3994994 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/ZtsClient.java @@ -9,6 +9,7 @@ import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AwsRole; import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials; +import com.yahoo.vespa.athenz.api.AzureTemporaryCredentials; import com.yahoo.vespa.athenz.api.ZToken; import java.security.KeyPair; @@ -167,7 +168,7 @@ default AthenzAccessToken getAccessToken(AthenzDomain domain) { List getTenantDomains(AthenzIdentity providerIdentity, AthenzIdentity userIdentity, String roleName); /** - * Get aws temporary credentials + * Get AWS temporary credentials * * @param awsRole AWS role to get credentials for * @param externalId External Id to get credentials, or null if not required @@ -178,7 +179,7 @@ default AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDo } /** - * Get aws temporary credentials + * Get AWS temporary credentials * * @param awsRole AWS role to get credentials for * @param duration Duration for which the credentials should be valid, or null to use default @@ -187,6 +188,21 @@ default AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDo */ AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole, Duration duration, String externalId); + /** + * @param athenzRole Athenz role to use when assuming credentials + * @param azureIdentityId Client ID of the Azure identity to assume + * @return Azure temporary credentials + */ + AzureTemporaryCredentials getAzureTemporaryCredentials(AthenzRole athenzRole, String azureIdentityId); + + /** + * @param athenzRole Athenz role to use when assuming credentials + * @param azureResourceGroup Azure resource group that contains the target identity + * @param azureIdentityName Name of the Azure Identity to assume + * @return Azure temporary credentials + */ + AzureTemporaryCredentials getAzureTemporaryCredentials(AthenzRole athenzRole, String azureResourceGroup, String azureIdentityName); + /** * Check access to resource for a given principal * diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AwsTemporaryCredentialsResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AwsTemporaryCredentialsResponseEntity.java index d368d5867162..fffe2484d260 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AwsTemporaryCredentialsResponseEntity.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AwsTemporaryCredentialsResponseEntity.java @@ -11,8 +11,8 @@ * @author mortent */ @JsonIgnoreProperties(ignoreUnknown = true) -public class AwsTemporaryCredentialsResponseEntity { - private AwsTemporaryCredentials credentials; +public class AwsTemporaryCredentialsResponseEntity implements TemporaryCredentialsResponse { + private final AwsTemporaryCredentials credentials; public AwsTemporaryCredentialsResponseEntity( @JsonProperty("accessKeyId") String accessKeyId, diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AzureTemporaryCredentialsResponseEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AzureTemporaryCredentialsResponseEntity.java new file mode 100644 index 000000000000..fc201bf45c8a --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AzureTemporaryCredentialsResponseEntity.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 com.yahoo.vespa.athenz.client.zts.bindings; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.yahoo.vespa.athenz.api.AzureTemporaryCredentials; + +import java.time.Instant; + +/** + * @author freva + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AzureTemporaryCredentialsResponseEntity implements TemporaryCredentialsResponse { + private final AzureTemporaryCredentials credentials; + + public AzureTemporaryCredentialsResponseEntity( + @JsonProperty("attributes") Attributes attributes, + @JsonProperty("expiration") Instant expiration) { + this.credentials = new AzureTemporaryCredentials( + attributes.azureSubscription(), + attributes.azureTenant(), + attributes.accessToken(), + expiration); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public record Attributes(@JsonProperty("azureSubscription") String azureSubscription, + @JsonProperty("azureTenant") String azureTenant, + @JsonProperty("accessToken") String accessToken) { } + + public AzureTemporaryCredentials credentials() { + return credentials; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/TemporaryCredentialsResponse.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/TemporaryCredentialsResponse.java new file mode 100644 index 000000000000..01b3809e6327 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/TemporaryCredentialsResponse.java @@ -0,0 +1,9 @@ +// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.client.zts.bindings; + +/** + * @author freva + */ +public interface TemporaryCredentialsResponse { + T credentials(); +}