diff --git a/api/src/main/java/io/minio/credentials/AssumeRoleBaseProvider.java b/api/src/main/java/io/minio/credentials/AssumeRoleBaseProvider.java new file mode 100644 index 000000000..d248ffe59 --- /dev/null +++ b/api/src/main/java/io/minio/credentials/AssumeRoleBaseProvider.java @@ -0,0 +1,86 @@ +/* + * MinIO Java SDK for Amazon S3 Compatible Cloud Storage, (C) 2020 MinIO, Inc. + * + * 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 + * + * https://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 io.minio.credentials; + +import io.minio.errors.XmlParserException; +import java.io.IOException; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +/** Base class to AssumeRole based providers. */ +public abstract class AssumeRoleBaseProvider implements Provider { + private final OkHttpClient httpClient; + private Credentials credentials; + + public AssumeRoleBaseProvider(OkHttpClient customHttpClient) { + this.httpClient = (customHttpClient != null) ? customHttpClient : new OkHttpClient(); + } + + @Override + public synchronized Credentials fetch() { + if (credentials != null && !credentials.isExpired()) { + return credentials; + } + + try (Response response = httpClient.newCall(getRequest()).execute()) { + if (!response.isSuccessful()) { + throw new IllegalStateException( + "STS service failed with HTTP status code " + response.code()); + } + + credentials = parseResponse(response); + return credentials; + } catch (XmlParserException | IOException e) { + throw new IllegalStateException("Unable to parse STS response", e); + } + } + + protected HttpUrl.Builder newUrlBuilder( + HttpUrl url, + String action, + int durationSeconds, + String policy, + String roleArn, + String roleSessionName) { + HttpUrl.Builder urlBuilder = url.newBuilder().addQueryParameter("Version", "2011-06-15"); + + if (durationSeconds > 0) { + urlBuilder.addQueryParameter("DurationSeconds", String.valueOf(durationSeconds)); + } + + if (policy != null) { + urlBuilder.addQueryParameter("Policy", policy); + } + + if (roleArn != null) { + urlBuilder.addQueryParameter("RoleArn", roleArn); + } + + if (roleSessionName != null) { + urlBuilder.addQueryParameter("RoleSessionName", roleSessionName); + } + + return urlBuilder; + } + + protected abstract Request getRequest(); + + protected abstract Credentials parseResponse(Response response) + throws XmlParserException, IOException; +} diff --git a/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java b/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java index ac92754e9..b00bdeb57 100644 --- a/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java +++ b/api/src/main/java/io/minio/credentials/AssumeRoleProvider.java @@ -45,15 +45,13 @@ * href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html">AssumeRole * API. */ -public class AssumeRoleProvider implements Provider { +public class AssumeRoleProvider extends AssumeRoleBaseProvider { public static final int DEFAULT_DURATION_SECONDS = (int) TimeUnit.HOURS.toSeconds(1); private final String accessKey; private final String secretKey; private final String region; - private final OkHttpClient httpClient; private final String contentSha256; private final Request request; - private Credentials credentials; public AssumeRoleProvider( @Nonnull String stsEndpoint, @@ -67,6 +65,7 @@ public AssumeRoleProvider( @Nullable String externalId, @Nullable OkHttpClient customHttpClient) throws NoSuchAlgorithmException { + super(customHttpClient); stsEndpoint = Objects.requireNonNull(stsEndpoint, "STS endpoint cannot be empty"); HttpUrl url = Objects.requireNonNull(HttpUrl.parse(stsEndpoint), "Invalid STS endpoint"); accessKey = Objects.requireNonNull(accessKey, "Access key must not be null"); @@ -76,7 +75,6 @@ public AssumeRoleProvider( this.accessKey = accessKey; this.secretKey = Objects.requireNonNull(secretKey, "Secret key must not be null"); this.region = (region != null) ? region : ""; - this.httpClient = (customHttpClient != null) ? customHttpClient : new OkHttpClient(); if (externalId != null && (externalId.length() < 2 || externalId.length() > 1224)) { throw new IllegalArgumentException("Length of ExternalId must be in between 2 and 1224"); @@ -88,23 +86,7 @@ public AssumeRoleProvider( : DEFAULT_DURATION_SECONDS; HttpUrl.Builder urlBuilder = - url.newBuilder() - .addQueryParameter("Action", "AssumeRole") - .addQueryParameter("Version", "2011-06-15") - .addQueryParameter("DurationSeconds", String.valueOf(durationSeconds)); - - if (roleArn != null) { - urlBuilder.addQueryParameter("RoleArn", roleArn); - } - - if (roleSessionName != null) { - urlBuilder.addQueryParameter("RoleSessionName", roleSessionName); - } - - if (policy != null) { - urlBuilder.addQueryParameter("Policy", policy); - } - + newUrlBuilder(url, "AssumeRole", durationSeconds, policy, roleArn, roleSessionName); if (externalId != null) { urlBuilder.addQueryParameter("ExternalId", externalId); } @@ -121,40 +103,29 @@ public AssumeRoleProvider( } @Override - public synchronized Credentials fetch() { - if (credentials != null && !credentials.isExpired()) { - return credentials; - } - + protected Request getRequest() { try { - Request request = - Signer.signV4( - this.request - .newBuilder() - .header("x-amz-date", ZonedDateTime.now().format(Time.AMZ_DATE_FORMAT)) - .build(), - region, - accessKey, - secretKey, - contentSha256); - try (Response response = httpClient.newCall(request).execute()) { - if (!response.isSuccessful()) { - throw new IllegalStateException( - "STS service failed with HTTP status code " + response.code()); - } - - AssumeRoleResponse result = - Xml.unmarshal(AssumeRoleResponse.class, response.body().charStream()); - credentials = result.credentials(); - return credentials; - } - } catch (XmlParserException | IOException e) { - throw new IllegalStateException("Unable to parse STS response", e); + return Signer.signV4( + this.request + .newBuilder() + .header("x-amz-date", ZonedDateTime.now().format(Time.AMZ_DATE_FORMAT)) + .build(), + region, + accessKey, + secretKey, + contentSha256); } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException("Signature calculation failed", e); } } + @Override + protected Credentials parseResponse(Response response) throws XmlParserException, IOException { + AssumeRoleResponse result = + Xml.unmarshal(AssumeRoleResponse.class, response.body().charStream()); + return result.credentials(); + } + /** Object representation of response XML of AssumeRole API. */ @Root(name = "AssumeRoleResponse", strict = false) @Namespace(reference = "https://sts.amazonaws.com/doc/2011-06-15/") diff --git a/api/src/main/java/io/minio/credentials/ClientGrantsProvider.java b/api/src/main/java/io/minio/credentials/ClientGrantsProvider.java index a390d30fe..5f96f55e0 100644 --- a/api/src/main/java/io/minio/credentials/ClientGrantsProvider.java +++ b/api/src/main/java/io/minio/credentials/ClientGrantsProvider.java @@ -16,10 +16,19 @@ package io.minio.credentials; +import io.minio.Xml; +import io.minio.errors.XmlParserException; +import java.io.IOException; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; +import okhttp3.Response; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Path; +import org.simpleframework.xml.Root; /** * Credential provider using supplier; - private final HttpUrl stsEndpoint; - private final Integer durationSeconds; - private final String policy; - private final String roleArn; - private final String roleSessionName; - private final OkHttpClient httpClient; - private Credentials credentials; + protected final HttpUrl stsEndpoint; + protected final Integer durationSeconds; + protected final String policy; + protected final String roleArn; + protected final String roleSessionName; public WebIdentityClientGrantsProvider( @Nonnull Supplier supplier, @@ -58,6 +48,7 @@ public WebIdentityClientGrantsProvider( @Nullable String roleArn, @Nullable String roleSessionName, @Nullable OkHttpClient customHttpClient) { + super(customHttpClient); this.supplier = Objects.requireNonNull(supplier, "JWT token supplier must not be null"); stsEndpoint = Objects.requireNonNull(stsEndpoint, "STS endpoint cannot be empty"); this.stsEndpoint = Objects.requireNonNull(HttpUrl.parse(stsEndpoint), "Invalid STS endpoint"); @@ -65,10 +56,9 @@ public WebIdentityClientGrantsProvider( this.policy = policy; this.roleArn = roleArn; this.roleSessionName = roleSessionName; - this.httpClient = (customHttpClient != null) ? customHttpClient : new OkHttpClient(); } - private int getDurationSeconds(int expiry) { + protected int getDurationSeconds(int expiry) { if (durationSeconds != null && durationSeconds > 0) { expiry = durationSeconds; } @@ -85,93 +75,11 @@ private int getDurationSeconds(int expiry) { } @Override - public synchronized Credentials fetch() { - if (credentials != null && !credentials.isExpired()) { - return credentials; - } - + protected Request getRequest() { Jwt jwt = supplier.get(); - - HttpUrl.Builder urlBuilder = - stsEndpoint.newBuilder().addQueryParameter("Version", "2011-06-15"); - - int durationSeconds = getDurationSeconds(jwt.expiry()); - if (durationSeconds > 0) { - urlBuilder.addQueryParameter("DurationSeconds", String.valueOf(durationSeconds)); - } - - if (policy != null) { - urlBuilder.addQueryParameter("Policy", policy); - } - - if (isWebIdentity()) { - urlBuilder - .addQueryParameter("Action", "AssumeRoleWithWebIdentity") - .addQueryParameter("WebIdentityToken", jwt.token()); - if (roleArn != null) { - urlBuilder - .addQueryParameter("RoleArn", roleArn) - .addQueryParameter( - "RoleSessionName", - (roleSessionName != null) - ? roleSessionName - : String.valueOf(System.currentTimeMillis())); - } - } else { - urlBuilder - .addQueryParameter("Action", "AssumeRoleWithClientGrants") - .addQueryParameter("Token", jwt.token()); - } - - Request request = - new Request.Builder().url(urlBuilder.build()).method("POST", EMPTY_BODY).build(); - try (Response response = httpClient.newCall(request).execute()) { - if (!response.isSuccessful()) { - throw new IllegalStateException( - "STS service failed with HTTP status code " + response.code()); - } - - if (isWebIdentity()) { - WebIdentityResponse result = - Xml.unmarshal(WebIdentityResponse.class, response.body().charStream()); - credentials = result.credentials(); - } else { - ClientGrantsResponse result = - Xml.unmarshal(ClientGrantsResponse.class, response.body().charStream()); - credentials = result.credentials(); - } - - return credentials; - } catch (XmlParserException | IOException e) { - throw new IllegalStateException("Unable to parse STS response", e); - } - } - - protected abstract boolean isWebIdentity(); - - /** Object representation of response XML of AssumeRoleWithWebIdentity API. */ - @Root(name = "AssumeRoleWithWebIdentityResponse", strict = false) - @Namespace(reference = "https://sts.amazonaws.com/doc/2011-06-15/") - public static class WebIdentityResponse { - @Path(value = "AssumeRoleWithWebIdentityResult") - @Element(name = "Credentials") - private Credentials credentials; - - public Credentials credentials() { - return credentials; - } + HttpUrl.Builder urlBuilder = newUrlBuilder(jwt); + return new Request.Builder().url(urlBuilder.build()).method("POST", EMPTY_BODY).build(); } - /** Object representation of response XML of AssumeRoleWithClientGrants API. */ - @Root(name = "AssumeRoleWithClientGrantsResponse", strict = false) - @Namespace(reference = "https://sts.amazonaws.com/doc/2011-06-15/") - public static class ClientGrantsResponse { - @Path(value = "AssumeRoleWithClientGrantsResult") - @Element(name = "Credentials") - private Credentials credentials; - - public Credentials credentials() { - return credentials; - } - } + protected abstract HttpUrl.Builder newUrlBuilder(Jwt jwt); } diff --git a/api/src/main/java/io/minio/credentials/WebIdentityProvider.java b/api/src/main/java/io/minio/credentials/WebIdentityProvider.java index c6588cab7..00d45d362 100644 --- a/api/src/main/java/io/minio/credentials/WebIdentityProvider.java +++ b/api/src/main/java/io/minio/credentials/WebIdentityProvider.java @@ -16,10 +16,19 @@ package io.minio.credentials; +import io.minio.Xml; +import io.minio.errors.XmlParserException; +import java.io.IOException; import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; +import okhttp3.Response; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.Namespace; +import org.simpleframework.xml.Path; +import org.simpleframework.xml.Root; /** * Credential provider using