Skip to content

Commit

Permalink
add IAM AWS credential provider
Browse files Browse the repository at this point in the history
  • Loading branch information
balamurugana committed Aug 21, 2020
1 parent ed3d00d commit 75aea94
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 0 deletions.
211 changes: 211 additions & 0 deletions api/src/main/java/io/minio/credentials/IamAwsProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
/*
* 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 com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.minio.messages.ResponseDate;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;
import javax.annotation.Nullable;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
* Credential provider using <a
* href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html">IAM roles
* for Amazon EC2</a>.
*/
public class IamAwsProvider extends EnvironmentProvider {
// Custom endpoint to fetch IAM role credentials.
private final HttpUrl customEndpoint;
private final OkHttpClient httpClient;
private final ObjectMapper mapper;
private Credentials credentials;

public IamAwsProvider(@Nullable String customEndpoint, @Nullable OkHttpClient customHttpClient) {
this.customEndpoint =
(customEndpoint != null)
? Objects.requireNonNull(HttpUrl.parse(customEndpoint), "Invalid custom endpoint")
: null;
this.httpClient = (customHttpClient != null) ? customHttpClient : new OkHttpClient();
this.mapper = new ObjectMapper();
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
}

private void checkLoopbackHost(HttpUrl url) {
try {
for (InetAddress addr : InetAddress.getAllByName(url.host())) {
if (!addr.isLoopbackAddress()) {
throw new IllegalArgumentException(url.host() + " is not loopback only host");
}
}
} catch (UnknownHostException e) {
throw new IllegalStateException("Host in " + url + " is not loopback address");
}
}

private Credentials fetchCredentials(HttpUrl url) {
try (Response response =
httpClient.newCall(new Request.Builder().url(url).method("GET", null).build()).execute()) {
if (!response.isSuccessful()) {
throw new IllegalStateException(url + " failed with HTTP status code " + response.code());
}

EcsCredentials creds = mapper.readValue(response.body().charStream(), EcsCredentials.class);
if (!"Success".equals(creds.code())) {
throw new IllegalStateException(url + " failed with message " + creds.message());
}
return creds.toCredentials();
} catch (IOException e) {
throw new IllegalStateException("Unable to parse response", e);
}
}

private Jwt getToken() {
String tokenFile = getProperty("AWS_WEB_IDENTITY_TOKEN_FILE");
try {
byte[] data = Files.readAllBytes(Paths.get(tokenFile));
return new Jwt(new String(data, StandardCharsets.UTF_8), 0);
} catch (IOException e) {
throw new IllegalStateException("Error in reading file " + tokenFile, e);
}
}

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

HttpUrl url = this.customEndpoint;
if (getProperty("AWS_WEB_IDENTITY_TOKEN_FILE") != null) {
if (url == null) {
String region = getProperty("AWS_REGION");
url =
HttpUrl.parse(
(region == null)
? "https://sts.amazonaws.com"
: "https://sts." + region + ".amazonaws.com");
}

Provider provider =
new WebIdentityProvider(
() -> getToken(),
url.toString(),
null,
null,
getProperty("AWS_ROLE_ARN"),
getProperty("AWS_ROLE_SESSION_NAME"),
httpClient);
credentials = provider.fetch();
return credentials;
}

if (getProperty("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != null) {
if (url == null) {
url =
new HttpUrl.Builder()
.scheme("http")
.host("169.254.170.2")
.addPathSegments(getProperty("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"))
.build();
}
} else if (getProperty("AWS_CONTAINER_CREDENTIALS_FULL_URI") != null) {
if (url == null) {
url = HttpUrl.parse(getProperty("AWS_CONTAINER_CREDENTIALS_FULL_URI"));
}
checkLoopbackHost(url);
} else {
if (url == null) {
url = HttpUrl.parse("http://169.254.169.254/latest/meta-data/iam/security-credentials/");
} else {
url =
new HttpUrl.Builder()
.scheme(url.scheme())
.host(url.host())
.addPathSegments("latest/meta-data/iam/security-credentials/")
.build();
}

String[] roleNames = null;
try (Response response =
httpClient
.newCall(new Request.Builder().url(url).method("GET", null).build())
.execute()) {
if (!response.isSuccessful()) {
throw new IllegalStateException(url + " failed with HTTP status code " + response.code());
}

roleNames = response.body().string().split("\\R");
} catch (IOException e) {
throw new IllegalStateException("Unable to parse response", e);
}

if (roleNames.length == 0) {
throw new IllegalStateException("No IAM roles attached to EC2 service " + url);
}

url = url.newBuilder().addPathSegment(roleNames[0]).build();
}

credentials = fetchCredentials(url);
return credentials;
}

public static class EcsCredentials {
@JsonProperty("AccessKeyID")
private String accessKey;

@JsonProperty("SecretAccessKey")
private String secretKey;

@JsonProperty("Token")
private String sessionToken;

@JsonProperty("Expiration")
private ResponseDate expiration;

@JsonProperty("Code")
private String code;

@JsonProperty("Message")
private String message;

public String code() {
return this.code;
}

public String message() {
return this.message;
}

public Credentials toCredentials() {
return new Credentials(accessKey, secretKey, sessionToken, expiration);
}
}
}
39 changes: 39 additions & 0 deletions examples/MinioClientWithIamAwsProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.IamAwsProvider;
import io.minio.credentials.Provider;

public class MinioClientWithIamAwsProvider {
public static void main(String[] args) throws Exception {
Provider provider = new IamAwsProvider(null, 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 75aea94

Please sign in to comment.