diff options
author | Morten Tokle <mortent@oath.com> | 2019-02-11 09:36:23 +0100 |
---|---|---|
committer | Morten Tokle <mortent@oath.com> | 2019-02-12 10:33:19 +0100 |
commit | 6d2c6b9c3b36e8bc1efd38cc2c4debffabbd3a3a (patch) | |
tree | 920a2beb2f142bab0077cbe64fedfd17e02d9e04 | |
parent | b44d662b4fb2e71c0da0484760194b9d0bc4d21a (diff) |
Add support for AWS temp credentials
7 files changed, 176 insertions, 3 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java index 486acea593b..8bb5ad12468 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/mock/ZtsClientMock.java @@ -5,6 +5,8 @@ import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.api.AwsRole; +import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials; import com.yahoo.vespa.athenz.api.ZToken; import com.yahoo.vespa.athenz.client.zts.Identity; import com.yahoo.vespa.athenz.client.zts.InstanceIdentity; @@ -83,6 +85,11 @@ public class ZtsClientMock implements ZtsClient { } @Override + public AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole, Duration duration, String externalId) { + throw new UnsupportedOperationException(); + } + + @Override public void close() { } diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsRole.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsRole.java new file mode 100644 index 00000000000..55305df8d26 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsRole.java @@ -0,0 +1,44 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.athenz.api; + +import java.net.URLEncoder; +import java.util.Objects; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * @author mortent + */ +public class AwsRole { + + private final String name; + private final String encodedName; + + public AwsRole(String awsRoleName) { + this.name = awsRoleName; + // Encoded twice for zts compatibility + this.encodedName = URLEncoder.encode(URLEncoder.encode(this.name, UTF_8), UTF_8); + } + + public String encodedName() { + return encodedName; + } + + public String name() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AwsRole awsRole = (AwsRole) o; + return Objects.equals(name, awsRole.name) && + Objects.equals(encodedName, awsRole.encodedName); + } + + @Override + public int hashCode() { + return Objects.hash(name, encodedName); + } +} 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 new file mode 100644 index 00000000000..886d97630af --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/api/AwsTemporaryCredentials.java @@ -0,0 +1,37 @@ +// Copyright 2019 Oath Inc. 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 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; + } +} diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java index 7ff9db327d3..bda7e41c19b 100644 --- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/common/ClientBase.java @@ -16,7 +16,6 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpRequestRetryHandler; import org.apache.http.impl.client.HttpClientBuilder; -import org.eclipse.jetty.http.HttpStatus; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; @@ -59,7 +58,7 @@ public abstract class ClientBase implements AutoCloseable { } protected <T> T readEntity(HttpResponse response, Class<T> entityType) throws IOException { - if (HttpStatus.isSuccess(response.getStatusLine().getStatusCode())) { + if (isSuccess(response.getStatusLine().getStatusCode())) { if (entityType.equals(Void.class)) { return null; } else { @@ -71,6 +70,10 @@ public abstract class ClientBase implements AutoCloseable { } } + private boolean isSuccess(int statusCode) { + return statusCode>=200 && statusCode<300; + } + private static CloseableHttpClient createHttpClient(String userAgent, Supplier<SSLContext> sslContextSupplier) { return HttpClientBuilder.create() .setRetryHandler(new DefaultHttpRequestRetryHandler(3, /*requestSentRetryEnabled*/true)) 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 9eef2ff9903..05395947fc1 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 @@ -5,9 +5,12 @@ import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.api.AwsRole; +import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.athenz.api.ZToken; import com.yahoo.vespa.athenz.client.common.ClientBase; +import com.yahoo.vespa.athenz.client.zts.bindings.AwsTemporaryCredentialsResponseEntity; 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; @@ -31,6 +34,7 @@ import java.security.KeyPair; import java.security.cert.X509Certificate; import java.time.Duration; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import static java.util.stream.Collectors.toList; @@ -171,6 +175,23 @@ public class DefaultZtsClient extends ClientBase implements ZtsClient { }); } + @Override + public AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole, Duration duration, String externalId) { + URI uri = ztsUrl.resolve( + String.format("domain/%s/role/%s/creds", athenzDomain.getName(), awsRole.encodedName())); + RequestBuilder requestBuilder = RequestBuilder.get(uri); + + // Add optional durationSeconds and externalId parameters + Optional.ofNullable(duration).ifPresent(d -> requestBuilder.addParameter("durationSeconds", Long.toString(duration.getSeconds()))); + Optional.ofNullable(externalId).ifPresent(s -> requestBuilder.addParameter("externalId", s)); + + HttpUriRequest request = requestBuilder.build(); + return execute(request, response -> { + AwsTemporaryCredentialsResponseEntity entity = readEntity(response, AwsTemporaryCredentialsResponseEntity.class); + return entity.credentials(); + }); + } + private InstanceIdentity getInstanceIdentity(HttpResponse response) throws IOException { InstanceIdentityCredentials entity = readEntity(response, InstanceIdentityCredentials.class); return entity.getServiceToken() != null 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 0ca2ea2fe69..7b77fccfed6 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 @@ -1,12 +1,14 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.athenz.client.zts; +import com.yahoo.security.Pkcs10Csr; import com.yahoo.vespa.athenz.api.AthenzDomain; import com.yahoo.vespa.athenz.api.AthenzIdentity; import com.yahoo.vespa.athenz.api.AthenzRole; import com.yahoo.vespa.athenz.api.AthenzService; +import com.yahoo.vespa.athenz.api.AwsRole; +import com.yahoo.vespa.athenz.api.AwsTemporaryCredentials; import com.yahoo.vespa.athenz.api.ZToken; -import com.yahoo.security.Pkcs10Csr; import java.security.KeyPair; import java.security.cert.X509Certificate; @@ -108,5 +110,36 @@ public interface ZtsClient extends AutoCloseable { */ List<AthenzDomain> getTenantDomains(AthenzIdentity providerIdentity, AthenzIdentity userIdentity, String roleName); + /** + * Get aws temporary credentials + * + * @param awsRole AWS role to get credentials for + * @return AWS temporary credentials + */ + default AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole) { + return getAwsTemporaryCredentials(athenzDomain, awsRole, null, null); + } + + /** + * Get aws temporary credentials + * + * @param awsRole AWS role to get credentials for + * @param externalId External Id to get credentials, or <code>null</code> if not required + * @return AWS temporary credentials + */ + default AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole, String externalId) { + return getAwsTemporaryCredentials(athenzDomain, awsRole, null, externalId); + } + + /** + * Get aws temporary credentials + * + * @param awsRole AWS role to get credentials for + * @param duration Duration for which the credentials should be valid, or <code>null</code> to use default + * @param externalId External Id to get credentials, or <code>null</code> if not required + * @return AWS temporary credentials + */ + AwsTemporaryCredentials getAwsTemporaryCredentials(AthenzDomain athenzDomain, AwsRole awsRole, Duration duration, String externalId); + void close(); } 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 new file mode 100644 index 00000000000..50b02730277 --- /dev/null +++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/client/zts/bindings/AwsTemporaryCredentialsResponseEntity.java @@ -0,0 +1,28 @@ +// Copyright 2019 Oath Inc. 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.AwsTemporaryCredentials; + +import java.time.Instant; + +/** + * @author mortent + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AwsTemporaryCredentialsResponseEntity { + private AwsTemporaryCredentials credentials; + + public AwsTemporaryCredentialsResponseEntity( + @JsonProperty("accessKeyId") String accessKeyId, + @JsonProperty("secretAccessKey") String secretAccessKey, + @JsonProperty("sessionToken") String sessionToken, + @JsonProperty("expiration") Instant expiration) { + this.credentials = new AwsTemporaryCredentials(accessKeyId, secretAccessKey, sessionToken, expiration); + } + + public AwsTemporaryCredentials credentials() { + return credentials; + } +} |