Skip to content

Commit

Permalink
feat: add oidc credentials provider
Browse files Browse the repository at this point in the history
  • Loading branch information
飞澋 committed Aug 30, 2024
1 parent fabe3a9 commit 80c32b3
Show file tree
Hide file tree
Showing 7 changed files with 338 additions and 9 deletions.
2 changes: 2 additions & 0 deletions aliyun-net-sdk-core.Tests/Units/Reader/JsonReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* under the License.
*/

using System;
using Aliyun.Acs.Core.Reader;
using Aliyun.Acs.Core.Transform;

Expand All @@ -36,6 +37,7 @@ public void Read()
{
var json = new JsonReader();
var result = json.Read(jsonStr, itemsTitle);
Console.WriteLine("result----{0}", result);
var context = new UnmarshallerContext();
context.ResponseDictionary = result;
Assert.Equal("value", context.StringValue(itemsTitle + ".key"));
Expand Down
135 changes: 135 additions & 0 deletions aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using Aliyun.Acs.Core.Utils;
using Aliyun.Acs.Core.Exceptions;
using Aliyun.Acs.Core.Reader;

using Aliyun.Acs.Core.Http;

namespace Aliyun.Acs.Core.Auth
{
public class OIDCCredentialsProvider : AlibabaCloudCredentialsProvider
{
private string RoleArn { get; set; }
private string OIDCProviderArn { get; set; }
private string OIDCTokenFilePath { get; set; }
private string RoleSessionName { get; set; }
private string Policy { get; set; }

private readonly string stsEndpoint;

private readonly long durationSeconds;

private BasicSessionCredentials credentials;

Check warning on line 23 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.credentials' is never used

Check warning on line 23 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.credentials' is never used

Check warning on line 23 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.credentials' is never used

Check warning on line 23 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.credentials' is never used
private IAcsClient stsClient;

Check warning on line 24 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.stsClient' is never used

Check warning on line 24 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.stsClient' is never used

Check warning on line 24 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.stsClient' is never used

Check warning on line 24 in aliyun-net-sdk-core/Auth/Provider/OIDCCredentialsProvider.cs

View workflow job for this annotation

GitHub Actions / build

The field 'OIDCCredentialsProvider.stsClient' is never used

public OIDCCredentialsProvider(string roleArn, string oidcProviderArn, string oidcTokenFilePath, string roleSessionName, string regionId)
{
RoleArn = ParameterHelper.ValidateEnvNotNull(roleArn, "ALIBABA_CLOUD_ROLE_ARN", "roleArn", "roleArn does not exist and env ALIBABA_CLOUD_ROLE_ARN is null.");
OIDCProviderArn = ParameterHelper.ValidateEnvNotNull(oidcProviderArn, "ALIBABA_CLOUD_OIDC_PROVIDER_ARN", "oidcProviderArn", "OIDCProviderArn must not be null.");
OIDCTokenFilePath = ParameterHelper.ValidateEnvNotNull(oidcTokenFilePath, "ALIBABA_CLOUD_OIDC_TOKEN_FILE", "oidcTokenFilePath", "OIDCTokenFilePath must not be null.");

if (!string.IsNullOrEmpty(roleSessionName))
{
RoleSessionName = roleSessionName;
}
else
{
RoleSessionName = Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ROLE_SESSION_NAME");
}

if (string.IsNullOrEmpty(RoleSessionName))
{
RoleSessionName = "DEFAULT_ROLE_SESSION_NAME_FOR_C#_SDK_V1";
}

if (string.IsNullOrEmpty(regionId))
{
stsEndpoint = "https://sts.aliyuncs.com/";
}
else
{
stsEndpoint = string.Format("https://sts.{0}.aliyuncs.com/", regionId);
}

durationSeconds = 3600;
}

public string InvokeAssumeRoleWithOIDC()
{
var queries = new Dictionary<string, string>
{
{ "Action", "AssumeRoleWithOIDC" },
{ "Format", "JSON" },
{ "Version", "2015-04-01" },
{ "Timestamp", ParameterHelper.FormatIso8601Date(DateTime.UtcNow) }
};

string url;
try
{
url = stsEndpoint + "?" + ParameterHelper.GetFormData(queries);
}
catch (Exception ex)
{
throw new ClientException("AssumeRoleWithOIDC failed: " + ex.Message);
}

var httpRequest = new HttpRequest(url)
{
Method = MethodType.POST,
ContentType = FormatType.FORM,
};

httpRequest.SetConnectTimeoutInMilliSeconds(1000);
httpRequest.SetReadTimeoutInMilliSeconds(3000);

var oidcToken = AuthUtils.GetOIDCToken(OIDCTokenFilePath);
if (oidcToken == null)
{
throw new ClientException("Read OIDC token failed");
}

var body = new Dictionary<string, string>
{
{ "DurationSeconds", durationSeconds.ToString() },
{ "RoleArn", RoleArn },
{ "OIDCProviderArn", OIDCProviderArn },
{ "OIDCToken", oidcToken },
{ "RoleSessionName", RoleSessionName },
{ "Policy", Policy }
};

var content = ParameterHelper.GetFormDataWithoutNullOrEmpty(body);
httpRequest.SetContent(content, "UTF-8", FormatType.FORM);

HttpResponse httpResponse;
try
{
httpResponse = HttpResponse.GetResponse(httpRequest);
}
catch (Exception ex)
{
throw new ClientException("AssumeRoleWithOIDC failed " + ex.Message);
}

if (!httpResponse.isSuccess())
{
var responseBody = httpResponse.GetHttpContentString();
var jsonReader = new JsonReader();
var requestID = jsonReader.Read(responseBody, "RequestId");
var msg = jsonReader.Read(responseBody, "Message");
var code = jsonReader.Read(responseBody, "Code");
var message = string.Format("{0}(RequestID: {1}, Code: {2})", msg, requestID, code);
throw new ClientException("AssumeRoleWithOIDC failed: " + message);
}

return httpResponse.GetHttpContentString();
}

public AlibabaCloudCredentials GetCredentials()
{
throw new NotImplementedException();
}
}
}
30 changes: 26 additions & 4 deletions aliyun-net-sdk-core/Http/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,29 @@ namespace Aliyun.Acs.Core.Http
public class HttpRequest
{
private int timeout = 100000;
protected static readonly string UserAgent = "User-Agent";
private static readonly string DefaultUserAgent;

static HttpRequest()
{
DefaultUserAgent = new UserAgent().GetDefaultUserAgent();
}

public HttpRequest()
{
Headers = new Dictionary<string, string>
{
{ UserAgent, DefaultUserAgent }
};
}

public HttpRequest(string strUrl)
{
Url = strUrl;
Headers = new Dictionary<string, string>();
Headers = new Dictionary<string, string>
{
{ UserAgent, DefaultUserAgent }
};
}

public HttpRequest(string strUrl, Dictionary<string, string> tmpHeaders)
Expand All @@ -45,6 +59,14 @@ public HttpRequest(string strUrl, Dictionary<string, string> tmpHeaders)
if (null != tmpHeaders)
{
Headers = tmpHeaders;
Headers[UserAgent] = DefaultUserAgent;
}
else
{
Headers = new Dictionary<string, string>
{
{ UserAgent, DefaultUserAgent }
};
}
}

Expand Down Expand Up @@ -117,9 +139,9 @@ public void SetContent(byte[] content, string encoding, FormatType? format)
DictionaryUtil.Pop(Headers, "Content-Type");

DictionaryUtil.Add(Headers, "Content-MD5", strMd5);
if(this.Method.ToString() == "POST" || this.Method.ToString() == "PUT")
{
DictionaryUtil.Add(Headers, "Content-Length", contentLen);
if(this.Method.ToString() == "POST" || this.Method.ToString() == "PUT")
{
DictionaryUtil.Add(Headers, "Content-Length", contentLen);
}
DictionaryUtil.Add(Headers, "Content-Type", ParameterHelper.FormatTypeToString(type));

Expand Down
24 changes: 24 additions & 0 deletions aliyun-net-sdk-core/Http/HttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ public HttpResponse()
ContentType = format;
}

public string GetHttpContentString()
{
string stringContent = string.Empty;
if (this.Content != null)
{
try
{
if (string.IsNullOrWhiteSpace(this.Encoding))
{
stringContent = Convert.ToBase64String(this.Content);
}
else
{
stringContent = System.Text.Encoding.GetEncoding(Encoding).GetString(this.Content);
}
}
catch
{
throw new ClientException("Can not parse response due to unsupported encoding.");
}
}
return stringContent;
}

private static void ParseHttpResponse(HttpResponse httpResponse, HttpWebResponse httpWebResponse)
{
httpResponse.Content = ReadContent(httpResponse, httpWebResponse);
Expand Down
9 changes: 7 additions & 2 deletions aliyun-net-sdk-core/Http/UserAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ namespace Aliyun.Acs.Core.Http
{
public class UserAgent
{
private static string DEFAULT_MESSAGE;
public static string DEFAULT_MESSAGE;

private readonly List<string> excludedList = new List<string>();
private readonly Dictionary<string, string> userAgent = new Dictionary<string, string>();
Expand All @@ -46,7 +46,12 @@ public UserAgent()

DEFAULT_MESSAGE = "Alibaba Cloud (" + OSVersion + ") ";
DEFAULT_MESSAGE += ClientVersion;
DEFAULT_MESSAGE += " Core/" + CoreVersion;
DEFAULT_MESSAGE += " Credentials/" + CoreVersion;
}

internal string GetDefaultUserAgent()
{
return DEFAULT_MESSAGE;
}

public void SetTheValue()
Expand Down
73 changes: 73 additions & 0 deletions aliyun-net-sdk-core/Utils/AuthUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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;
using System.Text;
using System.IO;
using System.Security;
using System.Threading.Tasks;
using Aliyun.Acs.Core.Exceptions;

namespace Aliyun.Acs.Core.Utils
{
public class AuthUtils
{
private static volatile string oidcToken;

static AuthUtils authUtils = new AuthUtils();

AuthUtils()
{
}


public static string GetOIDCToken(string OIDCTokenFilePath)
{
byte[] buffer;
if (!File.Exists(OIDCTokenFilePath))
{
throw new ClientException("OIDCTokenFilePath " + OIDCTokenFilePath + " does not exist.");
}
try
{
using (var inStream = new FileStream(OIDCTokenFilePath, FileMode.Open, FileAccess.Read))
{
buffer = new byte[inStream.Length];
inStream.Read(buffer, 0, buffer.Length);
}
oidcToken = Encoding.UTF8.GetString(buffer);
}
catch (UnauthorizedAccessException)
{
throw new ClientException("OIDCTokenFilePath " + OIDCTokenFilePath + " cannot be read.");
}
catch (SecurityException)
{
throw new ClientException("Security Exception: Do not have the required permission. " + "OIDCTokenFilePath " + OIDCTokenFilePath);
}
catch (IOException e)
{
Console.WriteLine(e.StackTrace);
}
return oidcToken;
}


}
}
Loading

0 comments on commit 80c32b3

Please sign in to comment.