Kit.Core/LibExternal/Minio/Credentials/IAMAWSProvider.cs

256 lines
9.5 KiB
C#

/*
* MinIO .NET Library for Amazon S3 Compatible Cloud Storage,
* (C) 2021 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
*
* http://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.
*/
using System.Net;
using System.Text.Json;
using Minio.DataModel;
using Minio.Exceptions;
using Minio.Handlers;
using Minio.Helper;
/*
* IAM roles for Amazon EC2
* http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html
* The Credential provider for attaching an IAM rule.
*/
namespace Minio.Credentials;
public class IAMAWSProvider : IClientProvider
{
public IAMAWSProvider()
{
Client = null;
}
public IAMAWSProvider(string endpoint, IMinioClient client)
{
if (!string.IsNullOrWhiteSpace(endpoint))
{
CustomEndPoint = new Uri(endpoint);
if (string.IsNullOrWhiteSpace(CustomEndPoint.Authority))
throw new ArgumentNullException(nameof(endpoint),
"Endpoint field " + nameof(CustomEndPoint) + " is invalid.");
}
Client = client ?? throw new ArgumentNullException(nameof(client));
CustomEndPoint = new Uri(endpoint);
}
internal Uri CustomEndPoint { get; set; }
internal AccessCredentials Credentials { get; set; }
internal IMinioClient Client { get; set; }
public AccessCredentials GetCredentials()
{
Validate();
var url = CustomEndPoint;
if (CustomEndPoint is null)
{
var region = Environment.GetEnvironmentVariable("AWS_REGION");
if (string.IsNullOrWhiteSpace(region))
url = RequestUtil.MakeTargetURL("sts.amazonaws.com", true);
else
url = RequestUtil.MakeTargetURL("sts." + region + ".amazonaws.com", true);
}
var provider = new WebIdentityProvider()
.WithSTSEndpoint(url)
.WithRoleAction("AssumeRoleWithWebIdentity")
.WithDurationInSeconds(null)
.WithPolicy(null)
.WithRoleARN(Environment.GetEnvironmentVariable("AWS_ROLE_ARN"))
.WithRoleSessionName(Environment.GetEnvironmentVariable("AWS_ROLE_SESSION_NAME"));
Credentials = provider.GetCredentials();
return Credentials;
}
public async ValueTask<AccessCredentials> GetCredentialsAsync()
{
if (Credentials?.AreExpired() == false) return Credentials;
var url = CustomEndPoint;
var awsTokenFile = Environment.GetEnvironmentVariable("AWS_WEB_IDENTITY_TOKEN_FILE");
if (!string.IsNullOrWhiteSpace(awsTokenFile))
{
Credentials = GetAccessCredentials(awsTokenFile);
return Credentials;
}
var containerRelativeUri = Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI");
var containerFullUri = Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_FULL_URI");
var isURLEmpty = url is null;
if (!string.IsNullOrWhiteSpace(containerRelativeUri) && isURLEmpty)
{
url = RequestUtil.MakeTargetURL("169.254.170.2" + "/" + containerRelativeUri, false);
}
else if (!string.IsNullOrWhiteSpace(containerFullUri) && isURLEmpty)
{
var fullUri = new Uri(containerFullUri);
url = RequestUtil.MakeTargetURL(fullUri.AbsolutePath,
string.Equals(fullUri.Scheme, "https", StringComparison.OrdinalIgnoreCase));
}
else
{
url = await GetIamRoleNamedURL().ConfigureAwait(false);
}
Credentials = await GetAccessCredentials(url).ConfigureAwait(false);
return Credentials;
}
internal AccessCredentials GetAccessCredentials(string tokenFile)
{
Validate();
var url = CustomEndPoint;
if (url is null || string.IsNullOrWhiteSpace(url.Authority))
{
var region = Environment.GetEnvironmentVariable("AWS_REGION");
var urlStr = region is null ? "https://sts.amazonaws.com" : "https://sts." + region + ".amazonaws.com";
url = new Uri(urlStr);
}
var provider = new WebIdentityProvider()
.WithJWTSupplier(() =>
{
var tokenContents = File.ReadAllText(tokenFile);
return new JsonWebToken(tokenContents, 0);
})
.WithSTSEndpoint(url)
.WithDurationInSeconds(null)
.WithPolicy(null)
.WithRoleARN(Environment.GetEnvironmentVariable("AWS_ROLE_ARN"))
.WithRoleSessionName(Environment.GetEnvironmentVariable("AWS_ROLE_SESSION_NAME"));
Credentials = provider.GetCredentials();
return Credentials;
}
public async Task<AccessCredentials> GetAccessCredentials(Uri url)
{
if (url is null)
throw new ArgumentNullException(nameof(url));
Validate();
using var request = new HttpRequestMessage(HttpMethod.Get, url.ToString());
var requestBuilder = new HttpRequestMessageBuilder(HttpMethod.Get, url);
requestBuilder.AddQueryParameter("location", "");
using var response =
await Client.ExecuteTaskAsync(Enumerable.Empty<IApiResponseErrorHandler>(), requestBuilder)
.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(response.Content) ||
!HttpStatusCode.OK.Equals(response.StatusCode))
throw new CredentialsProviderException("IAMAWSProvider",
"Credential Get operation failed with HTTP Status code: " + response.StatusCode);
/*
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error,
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Error = null
};*/
var credentials = JsonSerializer.Deserialize<ECSCredentials>(response.Content);
if (credentials.Code?.Equals("success", StringComparison.OrdinalIgnoreCase) == false)
throw new CredentialsProviderException("IAMAWSProvider",
"Credential Get operation failed with code: " + credentials.Code + " and message " +
credentials.Message);
Credentials = credentials.GetAccessCredentials();
return Credentials;
}
public async Task<string> GetIamRoleNameAsync(Uri url)
{
Validate();
var requestBuilder = new HttpRequestMessageBuilder(HttpMethod.Get, url);
requestBuilder.AddQueryParameter("location", "");
using var response =
await Client.ExecuteTaskAsync(Enumerable.Empty<IApiResponseErrorHandler>(), requestBuilder)
.ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(response.Content) ||
!HttpStatusCode.OK.Equals(response.StatusCode))
throw new CredentialsProviderException("IAMAWSProvider",
"Credential Get operation failed with HTTP Status code: " + response.StatusCode);
var roleNames = response.Content.Split('\n');
if (roleNames.Length <= 0)
throw new CredentialsProviderException("IAMAWSProvider",
"No IAM roles are attached to AWS service at " + url);
var index = 0;
foreach (var item in roleNames) roleNames[index++] = item.Trim();
return roleNames[0];
}
public async Task<Uri> GetIamRoleNamedURL()
{
Validate();
var url = CustomEndPoint;
string newUrlStr;
if (url is null || string.IsNullOrWhiteSpace(url.Authority))
{
url = new Uri("http://169.254.169.254/latest/meta-data/iam/security-credentials/");
newUrlStr = "http://169.254.169.254/latest/meta-data/iam/security-credentials/";
}
else
{
var urlStr = url.Scheme + "://" + url.Authority + "/latest/meta-data/iam/security-credentials/";
url = new Uri(urlStr);
newUrlStr = urlStr;
}
var roleName = await GetIamRoleNameAsync(url).ConfigureAwait(false);
newUrlStr += roleName;
return new Uri(newUrlStr);
}
public IAMAWSProvider WithMinioClient(IMinioClient minio)
{
Client = minio;
if (Credentials is null ||
string.IsNullOrWhiteSpace(Credentials.AccessKey) || string.IsNullOrWhiteSpace(Credentials.SecretKey))
Credentials = GetCredentialsAsync().AsTask().GetAwaiter().GetResult();
return this;
}
public IAMAWSProvider WithEndpoint(string endpoint)
{
if (string.IsNullOrEmpty(endpoint))
throw new ArgumentException($"'{nameof(endpoint)}' cannot be null or empty.", nameof(endpoint));
if (endpoint.Contains("https", StringComparison.OrdinalIgnoreCase) ||
endpoint.Contains("http", StringComparison.OrdinalIgnoreCase))
CustomEndPoint = new Uri(endpoint);
else
CustomEndPoint = RequestUtil.MakeTargetURL(endpoint, true);
return this;
}
public void Validate()
{
if (Client is null)
throw new InvalidOperationException(nameof(Client) +
" should be assigned for the operation to continue.");
}
}