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 Sep 3, 2020
1 parent e2f86e5 commit 6a9a454
Show file tree
Hide file tree
Showing 10 changed files with 481 additions and 127 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 @@ -1034,7 +1034,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.signV4S3(
request,
region,
creds.accessKey(),
creds.secretKey(),
request.header("x-amz-content-sha256"));
}

if (this.traceStream != null) {
Expand Down
61 changes: 44 additions & 17 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 All @@ -132,8 +132,14 @@ public Signer(
this.prevSignature = prevSignature;
}

private void setScope() {
this.scope = this.date.format(Time.SIGNER_DATE_FORMAT) + "/" + this.region + "/s3/aws4_request";
private void setScope(String serviceName) {
this.scope =
this.date.format(Time.SIGNER_DATE_FORMAT)
+ "/"
+ this.region
+ "/"
+ serviceName
+ "/aws4_request";
}

private void setCanonicalHeaders() {
Expand Down Expand Up @@ -240,7 +246,8 @@ private void setChunkStringToSign() throws NoSuchAlgorithmException {
+ this.contentSha256;
}

private void setSigningKey() throws NoSuchAlgorithmException, InvalidKeyException {
private void setSigningKey(String serviceName)
throws NoSuchAlgorithmException, InvalidKeyException {
String aws4SecretKey = "AWS4" + this.secretKey;

byte[] dateKey =
Expand All @@ -250,7 +257,8 @@ private void setSigningKey() throws NoSuchAlgorithmException, InvalidKeyExceptio

byte[] dateRegionKey = sumHmac(dateKey, this.region.getBytes(StandardCharsets.UTF_8));

byte[] dateRegionServiceKey = sumHmac(dateRegionKey, "s3".getBytes(StandardCharsets.UTF_8));
byte[] dateRegionServiceKey =
sumHmac(dateRegionKey, serviceName.getBytes(StandardCharsets.UTF_8));

this.signingKey =
sumHmac(dateRegionServiceKey, "aws4_request".getBytes(StandardCharsets.UTF_8));
Expand Down Expand Up @@ -278,9 +286,9 @@ public static String getChunkSignature(
String chunkSha256, ZonedDateTime date, String region, String secretKey, String prevSignature)
throws NoSuchAlgorithmException, InvalidKeyException {
Signer signer = new Signer(null, chunkSha256, date, region, null, secretKey, prevSignature);
signer.setScope();
signer.setScope("s3");
signer.setChunkStringToSign();
signer.setSigningKey();
signer.setSigningKey("s3");
signer.setSignature();

return signer.signature;
Expand All @@ -293,32 +301,51 @@ public static String getChunkSeedSignature(Request request, String region, Strin
ZonedDateTime date = ZonedDateTime.parse(request.header("x-amz-date"), Time.AMZ_DATE_FORMAT);

Signer signer = new Signer(request, contentSha256, date, region, null, secretKey, null);
signer.setScope();
signer.setScope("s3");
signer.setCanonicalRequest();
signer.setStringToSign();
signer.setSigningKey();
signer.setSigningKey("s3");
signer.setSignature();

return signer.signature;
}

/** 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)
private static Request signV4(
String serviceName,
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);
signer.setScope();
signer.setScope(serviceName);
signer.setCanonicalRequest();
signer.setStringToSign();
signer.setSigningKey();
signer.setSigningKey(serviceName);
signer.setSignature();
signer.setAuthorization();

return request.newBuilder().header("Authorization", signer.authorization).build();
}

/** Returns signed request of given request for S3 service. */
public static Request signV4S3(
Request request, String region, String accessKey, String secretKey, String contentSha256)
throws NoSuchAlgorithmException, InvalidKeyException {
return signV4("s3", request, region, accessKey, secretKey, contentSha256);
}

/** Returns signed request of given request for STS service. */
public static Request signV4Sts(
Request request, String region, String accessKey, String secretKey, String contentSha256)
throws NoSuchAlgorithmException, InvalidKeyException {
return signV4("sts", request, region, accessKey, secretKey, contentSha256);
}

private void setPresignCanonicalRequest(int expires) throws NoSuchAlgorithmException {
this.canonicalHeaders = new TreeMap<>();
this.canonicalHeaders.put("host", this.request.headers().get("Host"));
Expand Down Expand Up @@ -367,10 +394,10 @@ public static HttpUrl presignV4(
ZonedDateTime date = ZonedDateTime.parse(request.header("x-amz-date"), Time.AMZ_DATE_FORMAT);

Signer signer = new Signer(request, contentSha256, date, region, accessKey, secretKey, null);
signer.setScope();
signer.setScope("s3");
signer.setPresignCanonicalRequest(expires);
signer.setStringToSign();
signer.setSigningKey();
signer.setSigningKey("s3");
signer.setSignature();

return signer
Expand All @@ -397,7 +424,7 @@ public static String postPresignV4(
throws NoSuchAlgorithmException, InvalidKeyException {
Signer signer = new Signer(null, null, date, region, null, secretKey, null);
signer.stringToSign = stringToSign;
signer.setSigningKey();
signer.setSigningKey("s3");
signer.setSignature();

return signer.signature;
Expand Down
96 changes: 96 additions & 0 deletions api/src/main/java/io/minio/credentials/AssumeRoleBaseProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* 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 java.util.Arrays;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
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) {
// HTTP/1.1 is only supported in default client because of HTTP/2 in OkHttpClient cause 5
// minutes timeout on program exit.
this.httpClient =
(customHttpClient != null)
? customHttpClient
: new OkHttpClient().newBuilder().protocols(Arrays.asList(Protocol.HTTP_1_1)).build();
}

@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("Action", action)
.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;
}
Loading

0 comments on commit 6a9a454

Please sign in to comment.