Skip to content

Commit

Permalink
feat: support IMDSv2 for ECS metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
PanPanZou committed Dec 23, 2024
1 parent f01a538 commit f79b03d
Show file tree
Hide file tree
Showing 6 changed files with 246 additions and 17 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ on:
jobs:
build:

runs-on: ubuntu-latest
runs-on: ubuntu-20.04

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Setup .NET Core
uses: actions/setup-dotnet@v2
uses: actions/setup-dotnet@v4
with:
dotnet-version: '2.x'
- name: install altcover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,39 @@ public void Fetch2()
() =>
{
var credentials = instance.Fetch();
;
}
);
}

[Fact]
public void FetchWithMetaDataToken()
{
var mock = new Mock<ECSMetadataServiceCredentialsFetcher>
{CallBase = true};

var e = new ArgumentException("test");
mock.Setup(foo => foo.GetResponse(
It.IsAny<HttpRequest>()
)).Throws(e);

var instance = mock.Object;
var ex = Assert.Throws<ClientException>(
() =>
{
var credentials = instance.Fetch();
}
);
Assert.StartsWith("Failed to connect ECS Metadata Service: System.ArgumentException: test", ex.Message);

var v2Fetcher = new ECSMetadataServiceCredentialsFetcher("", true, 900, 1200);
ex = Assert.Throws<ClientException>(
() =>
{
var credentials = v2Fetcher.Fetch();
}
);
Assert.StartsWith("Failed to get token from ECS Metadata Service, and fallback to IMDS v1 is disabled via the disableIMDSv1 configuration is turned on. Original error: Failed to connect ECS Metadata Service: ", ex.Message);
}

[Fact]
public void Fetch3()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
using Aliyun.Acs.Core.Exceptions;
using Aliyun.Acs.Core.Http;
using Aliyun.Acs.Core.Tests.Mock;

using Aliyun.Acs.Core.Utils;
using Moq;

using Xunit;
Expand All @@ -33,6 +33,21 @@ namespace Aliyun.Acs.Core.Tests.Units.Auth
{
public class InstanceProfileCredentialsProviderTest
{
[Fact]
public void BuilderTest()
{
var cache = AuthUtils.DisableECSMetaData;
AuthUtils.DisableECSMetaData = true;
var ex = Assert.Throws<ArgumentException>(() => new InstanceProfileCredentialsProvider.Builder()
.RoleName("test")
.ReadTimeout(2000)
.ConnectTimeout(2000)
.DisableIMDSv1(false)
.Build());
Assert.Equal("IMDS credentials is disabled.", ex.Message);
AuthUtils.DisableECSIMDSv1 = cache;
}

[Fact]
public void GetCredentials1()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,48 @@ namespace Aliyun.Acs.Core.Auth
public class ECSMetadataServiceCredentialsFetcher : AlibabaCloudCredentialsProvider
{
private const string URL_IN_ECS_METADATA = "/latest/meta-data/ram/security-credentials/";
private const int DEFAULT_TIMEOUT_IN_MILLISECONDS = 5000;
private const string URL_IN_ECS_METADATA_TOKEN = "/latest/api/token";
private const int DEFAULT_TIMEOUT_IN_MILLISECONDS = 1000;

private const string ECS_METADAT_FETCH_ERROR_MSG =
"Failed to get RAM session credentials from ECS metadata service.";

// stands for 3600 s
private const int DEFAULT_ECS_SESSION_TOKEN_DURATION_SECONDS = 3600;
private int connectionTimeoutInMilliseconds;
private string credentialUrl;
private string metadataServiceHost = "100.100.100.200";
private string roleName;
private readonly bool disableIMDSv1;
private const int metadataTokenDuration = 21600;
private int connectionTimeout;
private int readTimeout;

public ECSMetadataServiceCredentialsFetcher()
{
connectionTimeoutInMilliseconds = DEFAULT_TIMEOUT_IN_MILLISECONDS;
this.connectionTimeout = DEFAULT_TIMEOUT_IN_MILLISECONDS;
this.readTimeout = DEFAULT_TIMEOUT_IN_MILLISECONDS;
this.disableIMDSv1 = false;
}

public ECSMetadataServiceCredentialsFetcher(string roleName, bool? disableIMDSv1, int? connectionTimeout, int? readTimeout)
{
this.roleName = roleName;
this.disableIMDSv1 = disableIMDSv1 != null && (bool)disableIMDSv1;
this.connectionTimeout = connectionTimeout == null ? 1000 : connectionTimeout.Value;
this.readTimeout = readTimeout == null ? 1000 : readTimeout.Value;
}

public AlibabaCloudCredentials GetCredentials()
{
return Fetch();
}

[Obsolete]
public void SetRoleName(string roleName)
{
if (string.IsNullOrEmpty(roleName))
{
throw new ArgumentNullException("You must specifiy a valid role name.");
throw new ArgumentNullException("You must specify a valid role name.");
}

this.roleName = roleName;
Expand All @@ -75,33 +90,54 @@ private void SetCredentialUrl()
credentialUrl = "http://" + metadataServiceHost + URL_IN_ECS_METADATA + roleName;
}

[Obsolete]
public void WithECSMetadataServiceHost(string host)
{
metadataServiceHost = host;
SetCredentialUrl();
}

[Obsolete]
public void WithConnectionTimeout(int milliseconds)
{
connectionTimeoutInMilliseconds = milliseconds;
this.connectionTimeout = milliseconds;
this.readTimeout = milliseconds;
}

[Obsolete]
public string GetMetadata()
{
var request = new HttpRequest(credentialUrl);
return GetMetadata(credentialUrl);
}

private string GetMetadata(string url)
{
var request = new HttpRequest(url);
request.Method = MethodType.GET;
request.SetConnectTimeoutInMilliSeconds(connectionTimeoutInMilliseconds);
request.SetConnectTimeoutInMilliSeconds(this.connectionTimeout);
request.SetReadTimeoutInMilliSeconds(this.readTimeout);
var metadataToken = this.GetMetadataToken();

if (metadataToken != null)
{
request.Headers.Add("X-aliyun-ecs-metadata-token", metadataToken);
}

HttpResponse response;
try
{
response = GetResponse(request);
}
catch (WebException e)
catch (Exception e)
{
throw new ClientException("Failed to connect ECS Metadata Service: " + e);
}

if (404 == response.Status)
{
throw new ClientException("The role name was not found in the instance.");
}

if (response.Status != 200)
{
throw new ClientException(ECS_METADAT_FETCH_ERROR_MSG + " HttpCode=" + response.Status);
Expand All @@ -110,12 +146,60 @@ public string GetMetadata()
return Encoding.UTF8.GetString(response.Content);
}

private string GetMetadataToken()
{
try
{
HttpRequest httpRequest = new HttpRequest("http://" + metadataServiceHost + URL_IN_ECS_METADATA_TOKEN)
{
Method = MethodType.PUT
};
httpRequest.SetConnectTimeoutInMilliSeconds(this.connectionTimeout);
httpRequest.SetReadTimeoutInMilliSeconds(this.readTimeout);
httpRequest.Headers.Add("X-aliyun-ecs-metadata-token-ttl-seconds", metadataTokenDuration.ToString());

HttpResponse httpResponse;
try
{
httpResponse = GetResponse(httpRequest);
}
catch (Exception ex)
{
throw new ClientException("Failed to connect ECS Metadata Service: " + ex);
}
if (httpResponse != null && httpResponse.Status != 200)
{
throw new ClientException("Failed to get token from ECS Metadata Service. HttpCode=" + httpResponse.Status + ", ResponseMessage=" + httpResponse.GetHttpContentString());
}
return httpResponse.GetHttpContentString();
}
catch (Exception ex)
{
return ThrowErrorOrReturn(ex);
}
}

private string ThrowErrorOrReturn(Exception e)
{
if (this.disableIMDSv1)
{
throw new ClientException("Failed to get token from ECS Metadata Service, and fallback to IMDS v1 is disabled via the disableIMDSv1 configuration is turned on. Original error: " + e.Message);
}
return null;
}

public virtual InstanceProfileCredentials Fetch()
{
Dictionary<string, string> dic;
var roleName = this.roleName;
if (string.IsNullOrEmpty(this.roleName))
{
roleName = GetMetadata("http://" + metadataServiceHost + URL_IN_ECS_METADATA);
}

try
{
var jsonContent = GetMetadata();
var jsonContent = GetMetadata("http://" + metadataServiceHost + URL_IN_ECS_METADATA + roleName);

IReader reader = new JsonReader();
dic = reader.Read(jsonContent, "");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ public InstanceProfileCredentialsProvider(string roleName)
fetcher.SetRoleName(roleName);

Check warning on line 40 in aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

'ECSMetadataServiceCredentialsFetcher.SetRoleName(string)' is obsolete

Check warning on line 40 in aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

'ECSMetadataServiceCredentialsFetcher.SetRoleName(string)' is obsolete
}

private InstanceProfileCredentialsProvider(Builder builder)
{
if (AuthUtils.DisableECSMetaData)
{
throw new ArgumentException("IMDS credentials is disabled.");
}

this.roleName = builder.roleName ?? AuthUtils.EnvironmentEcsMetaDataDisabled;
var disableIMDSv1 = builder.disableIMDSv1 ?? AuthUtils.DisableECSIMDSv1;
this.fetcher = new ECSMetadataServiceCredentialsFetcher(
roleName,
disableIMDSv1,
builder.connectTimeout,
builder.readTimeout);
}


public virtual AlibabaCloudCredentials GetCredentials()
{
try
Expand Down Expand Up @@ -86,5 +103,42 @@ public void withFetcher(ECSMetadataServiceCredentialsFetcher fetcher)
this.fetcher = fetcher;
this.fetcher.SetRoleName(roleName);

Check warning on line 104 in aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

'ECSMetadataServiceCredentialsFetcher.SetRoleName(string)' is obsolete

Check warning on line 104 in aliyun-net-sdk-core/Auth/Provider/InstanceProfileCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

'ECSMetadataServiceCredentialsFetcher.SetRoleName(string)' is obsolete
}

public class Builder
{
internal string roleName;
internal bool? disableIMDSv1;
internal int? connectTimeout;
internal int? readTimeout;

public Builder RoleName(string roleName)
{
this.roleName = roleName;
return this;
}

public Builder DisableIMDSv1(bool? disableIMDSv1)
{
this.disableIMDSv1 = disableIMDSv1;
return this;
}

public Builder ConnectTimeout(int? connectTimeout)
{
this.connectTimeout = connectTimeout;
return this;
}

public Builder ReadTimeout(int? readTimeout)
{
this.readTimeout = readTimeout;
return this;
}

public InstanceProfileCredentialsProvider Build()
{
return new InstanceProfileCredentialsProvider(this);
}
}
}
}
Loading

0 comments on commit f79b03d

Please sign in to comment.