Skip to content

Commit

Permalink
add assume-role credential provider
Browse files Browse the repository at this point in the history
Fixes #817
  • Loading branch information
balamurugana committed Aug 22, 2020
1 parent ed3d00d commit 19cc439
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 6 deletions.
2 changes: 1 addition & 1 deletion api/src/main/java/io/minio/Digest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import java.util.Locale;

/** Various global static functions used. */
class Digest {
public class Digest {
/** Private constructor. */
private Digest() {}

Expand Down
8 changes: 7 additions & 1 deletion api/src/main/java/io/minio/MinioClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,13 @@ protected Response execute(
Credentials creds = (provider == null) ? null : provider.fetch();
Request request = createRequest(url, method, headerMap, body, length, creds);
if (creds != null) {
request = Signer.signV4(request, region, creds.accessKey(), creds.secretKey());
request =
Signer.signV4(
request,
region,
creds.accessKey(),
creds.secretKey(),
request.header("x-amz-content-sha256"));
}

if (this.traceStream != null) {
Expand Down
8 changes: 4 additions & 4 deletions api/src/main/java/io/minio/Signer.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
import okhttp3.Request;

/** Amazon AWS S3 signature V4 signer. */
class Signer {
public class Signer {
//
// Excerpts from @lsegal - https://github.com/aws/aws-sdk-js/issues/659#issuecomment-120477258
//
Expand Down Expand Up @@ -115,7 +115,7 @@ class Signer {
* @param secretKey Secret Key string.
* @param prevSignature Previous signature of chunk upload.
*/
public Signer(
private Signer(
Request request,
String contentSha256,
ZonedDateTime date,
Expand Down Expand Up @@ -303,9 +303,9 @@ public static String getChunkSeedSignature(Request request, String region, Strin
}

/** Returns signed request object for given request, region, access key and secret key. */
public static Request signV4(Request request, String region, String accessKey, String secretKey)
public static Request signV4(
Request request, String region, String accessKey, String secretKey, String contentSha256)
throws NoSuchAlgorithmException, InvalidKeyException {
String contentSha256 = request.header("x-amz-content-sha256");
ZonedDateTime date = ZonedDateTime.parse(request.header("x-amz-date"), Time.AMZ_DATE_FORMAT);

Signer signer = new Signer(request, contentSha256, date, region, accessKey, secretKey, null);
Expand Down
172 changes: 172 additions & 0 deletions api/src/main/java/io/minio/credentials/AssumeRoleProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* 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.Digest;
import io.minio.Signer;
import io.minio.Time;
import io.minio.Xml;
import io.minio.errors.XmlParserException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
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 <a
* href="https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html">AssumeRole
* API</a>.
*/
public class AssumeRoleProvider implements Provider {
public static final int DEFAULT_DURATION_SECONDS = (int) TimeUnit.HOURS.toSeconds(1);
private final HttpUrl stsEndpoint;
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,
@Nonnull String accessKey,
@Nonnull String secretKey,
@Nullable Integer durationSeconds,
@Nullable String policy,
@Nullable String region,
@Nullable String roleArn,
@Nullable String roleSessionName,
@Nullable String externalId,
@Nullable OkHttpClient customHttpClient)
throws NoSuchAlgorithmException {
stsEndpoint = Objects.requireNonNull(stsEndpoint, "STS endpoint cannot be empty");
this.stsEndpoint = Objects.requireNonNull(HttpUrl.parse(stsEndpoint), "Invalid STS endpoint");
accessKey = Objects.requireNonNull(accessKey, "Access key must not be null");
if (accessKey.isEmpty()) {
throw new IllegalArgumentException("Access key must not be empty");
}
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");
}

durationSeconds =
(durationSeconds != null && durationSeconds > DEFAULT_DURATION_SECONDS)
? durationSeconds
: DEFAULT_DURATION_SECONDS;

HttpUrl.Builder urlBuilder =
this.stsEndpoint
.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);
}

if (externalId != null) {
urlBuilder.addQueryParameter("ExternalId", externalId);
}

String data = urlBuilder.build().encodedQuery();
this.contentSha256 = Digest.sha256Hash(data);
this.request =
new Request.Builder()
.url(this.stsEndpoint)
.method(
"POST",
RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"), data))
.build();
}

@Override
public synchronized Credentials fetch() {
if (credentials != null && !credentials.isExpired()) {
return credentials;
}

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);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new IllegalStateException("Signature calculation failed", e);
}
}

/** Object representation of response XML of AssumeRole API. */
@Root(name = "AssumeRoleResponse", strict = false)
@Namespace(reference = "https://sts.amazonaws.com/doc/2011-06-15/")
public static class AssumeRoleResponse {
@Path(value = "AssumeRoleResult")
@Element(name = "Credentials")
private Credentials credentials;

public Credentials credentials() {
return credentials;
}
}
}
74 changes: 74 additions & 0 deletions examples/MinioClientWithAssumeRoleProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.
*/

import io.minio.MinioClient;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.credentials.AssumeRoleProvider;
import io.minio.credentials.Provider;

public class MinioClientWithAssumeRoleProvider {
public static void main(String[] args) throws Exception {
// STS endpoint usually point to MinIO server.
String stsEndpoint = "http://STS-HOST:STS-PORT/";

// Access key to fetch credentials from STS endpoint.
String accessKey = "YOUR-ACCESSKEY";

// Secret key to fetch credentials from STS endpoint.
String secretKey = "YOUR-SECRETACCESSKEY";

// Role ARN if available.
String roleArn = "ROLE-ARN";

// Role session name if available.
String roleSessionName = "ROLE-SESSION-NAME";

// External ID if available.
String externalId = "EXTERNAL-ID";

// Policy if available.
String policy = "POLICY";

// Region if available.
String region = "REGION";

Provider provider =
new AssumeRoleProvider(
stsEndpoint,
accessKey,
secretKey,
null,
policy,
region,
roleArn,
roleSessionName,
externalId,
null);

MinioClient minioClient =
MinioClient.builder()
.endpoint("https://MINIO-HOST:MINIO-PORT")
.credentialsProvider(provider)
.build();

// Get information of an object.
StatObjectResponse stat =
minioClient.statObject(
StatObjectArgs.builder().bucket("my-bucketname").object("my-objectname").build());
System.out.println(stat);
}
}

0 comments on commit 19cc439

Please sign in to comment.