繁体   English   中英

当我的程序在集群外运行时,如何使用 AWS 使用 .NET 向 EKS Kube.netes API 进行身份验证?

[英]How can I use AWS to authenticate to the EKS Kubernetes API using .NET when my program is running outside the cluster?

我想开发一个 .NET 程序访问 Kube.netes API 来执行一些管理任务。 我们的 Kube.netes 集群是 EKS,所以我想使用原生 AWS 身份验证方法来生成临时凭证并访问 API,因为出于架构原因我的程序必须在 Kube.netes 之外运行。 我想将 map 一个 AWS 角色转换为一个 Kube.netes 角色,然后使用授予该角色的权限访问 API 并执行给定的任务。

我看到在AWS CLI中有一个叫aws eks get-token的命令,这是Terraform中推荐的获取访问令牌的方法,所以我安装了AWSSDK.EKS ,但不幸的是发现.NET中没有这样的方法查看IAmazonEks上的方法时库的变体。

查看aws eks get-token命令的源代码,我发现我们正在使用 STS 生成预签名的 URL:

def _get_presigned_url(self, k8s_aws_id):
    return self._sts_client.generate_presigned_url(
        'get_caller_identity',
        Params={K8S_AWS_ID_HEADER: k8s_aws_id},
        ExpiresIn=URL_TIMEOUT,
        HttpMethod='GET',
    )

在查看了aws eks get-token的 output 之后,我看到该令牌确实是一个基本 64 位编码的 URL,集群可能会调用它来接收调用者身份,并在授予访问权限之前尝试将其 map 分配给一个角色 - 相当不错的把戏。 实际上,调用此 URL 会按预期产生调用者身份。 作为参考,以下是调用它的方式:

GET https://sts.eu-west-1.amazonaws.com/?Action=GetCallerIdentity&Version=2011-06-15&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=....&X-Amz-SignedHeaders=host%3Bx-k8s-aws-id&X-Amz-Security-Token=...
Host: sts.eu-west-1.amazonaws.com
x-k8s-aws-id: my-cluster-id

但是,很遗憾地看到 C# 相当于generate_presigned_url()AWS.SecurityToken中也不存在!

那么,如何在不调用 AWS CLI 的情况下生成用于 .NET Kube.netes 客户端库的 EKS 安全令牌?

在撰写和研究这个问题时,我偶然发现了我想分享的答案,以节省人们未来的时间。

请注意,我是 Kubernetes 的新手,所以可能无法完全理解所有内容,因此请确保您了解正在发生的事情并进行彻底测试。 此外,请阅读到最后,因为根据您的用例,还有更简单的选择。

在了解aws eks get-token的输出格式后,我意识到这个预签名 URL 看起来很像 S3 中使用的预签名 URL。 我能够使用相同的技术为GetCallerIdentity制作预签名的 URL。 AmazonS3Client.GetPresignedUrl中有大量用于向后兼容的代码,我并不完全理解,因此这可能不适用于每个用例。

但是,此代码片段显示了如何生成令牌并向在 EKS 上运行的 Kubernetes 集群进行身份验证的端到端:

// for reference, these are the using statements. For simplicity however, all code is inline.
using Amazon;
using Amazon.Runtime;
using Amazon.Runtime.Internal;
using Amazon.Runtime.Internal.Auth;
using Amazon.Runtime.Internal.Util;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Internal;
using Amazon.SecurityToken.Model;
using Amazon.SecurityToken.Model.Internal.MarshallTransformations;
using k8s;
using System.Security.Cryptography.X509Certificates;


// Configuration:
const string clusterId = "my-eks-cluster";
const string clusterUrl = "https://0000000.xx.eu-west-1.eks.amazonaws.com";
const string certificateAuthority = "dGhpcyBpcyBub3QgYWN0dWFsbHkgYSBDQQ==...";
const string region = "eu-west-1";


// 60s is what aws eks get-token uses and seems appropriate because it's not too expensive to make a new token.
// I haven't tested to see if there's an upper limit here.
const int credentialAge = 60;

// It's best to retrieve credentials from your local instance profile, profile or wherever:
var credentials = await FallbackCredentialsFactory.GetCredentials().GetCredentialsAsync();


// We don't use the STS client directly, but we stil need some of its variables and internals:
var sts = new AmazonSecurityTokenServiceClient(new AmazonSecurityTokenServiceConfig
{
    AuthenticationRegion = region,
    RegionEndpoint = RegionEndpoint.GetBySystemName(region),
    StsRegionalEndpoints = StsRegionalEndpointsValue.Regional
});
var signer = new AWS4PreSignedUrlSigner();

// All AWS requests in the .NET SDK are turned into an IRequest object, which is the base object
// that is sent to the REST client.
var request = GetCallerIdentityRequestMarshaller.Instance.Marshall(new GetCallerIdentityRequest());
request.Headers["x-k8s-aws-id"] = clusterId;
request.HttpMethod = "GET";
request.OverrideSigningServiceName = "sts";

if (credentials.Token != null)
    request.Parameters["X-Amz-Security-Token"] = credentials.Token;

request.Parameters["X-Amz-Expires"] = Convert.ToString(credentialAge);


// We will now prepare the request as if we were to send it so that we can set other parameters. We only
// seem to set the host and endpoint field but there is a great deal of logic behind these methods so
// possibly some important edge cases are covered.
var endpointResolver = new AmazonSecurityTokenServiceEndpointResolver();
endpointResolver.ProcessRequestHandlers(new Amazon.Runtime.Internal.ExecutionContext(new Amazon.Runtime.Internal.RequestContext(true, new NullSigner())
{
    Request = request,
    ClientConfig = sts.Config
}, null));

// We get a signature for the request using a built-in AWS utility - this is the same thing that we
// do when sending a real REST request.
var result = signer.SignRequest(request, sts.Config, new RequestMetrics(), credentials.AccessKey, credentials.SecretKey);

// We can't append result.ForQueryParameters to the URL like the AWS S3 client, as EKS
// authorisation expects that the results will be URL-encoded:
request.Parameters["X-Amz-Credential"] = $"{result.AccessKeyId}/{result.Scope}";
request.Parameters["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256";
request.Parameters["X-Amz-Date"] = result.ISO8601DateTime;
request.Parameters["X-Amz-SignedHeaders"] = result.SignedHeaders;
request.Parameters["X-Amz-Signature"] = result.Signature;

// Finally we have a signed URL - this can be called like so if you would like to test that it works:
// GET {signedUrl}
// Host: sts.{region}.amazonaws.com
// x-k8s-aws-id: {clusterId}
var signedUrl = AmazonServiceClient.ComposeUrl(request).ToString();

// Now, we just need to format it how EKS expects it:
var encodedUrl = Convert.ToBase64String(Encoding.UTF8.GetBytes(signedUrl));
var eksToken = "k8s-aws-v1." + encodedUrl;


// Now, with our new token we can go ahead and connect to EKS:
var clientConfig = new KubernetesClientConfiguration
{
    AccessToken = eksToken,
    Host = clusterUrl,
    SslCaCerts = new X509Certificate2Collection(new X509Certificate2(Convert.FromBase64String(certificateAuthority)))
};

// If your credentials have the right permissions, you should be able to get a list of your namespaces:
var kubernetesClient = new Kubernetes(clientConfig);

foreach (var ns in kubernetesClient.CoreV1.ListNamespace().Items)
{
    Console.WriteLine(ns.Metadata.Name);
}

如果您需要做一些更复杂的事情或需要将 Kubernetes 功能附加到现有的 .NET 工具,我希望这是一个有用的替代方案。

此外,大量搜索结果围绕着为 S3 生成预签名 URL,我不认为您可以为其他 AWS 端点创建预签名 URL 是常识,因此希望这有助于解决这个特定问题并激发一些其他想法。

备选方案:使用您的本地配置

如果我不提一个更简单的替代方案,那将是我的疏忽,即使用本地 Kubernetes 配置简单地创建一个 Kubernetes 客户端。 然而:

  1. 这将直接调用 awscli,这意味着您必须在您的服务器上安装此依赖项和其他依赖项并保持最新。
  2. 在 Windows 上的开发中使用它时,AWS 窗口会弹出一秒钟,从而抢走焦点,这在处理长期存在的进程时很烦人。
  3. 如果您需要以编程方式一次定位未知数量的 Kubernetes 集群,则无需将它们添加到 Kubernetes 配置文件中。
  4. 如果处理用户输入,则在将用户输入传递给外部进程时会增加安全风险。 如果可能的话,我宁愿不这样做。
  5. 调用外部进程也有轻微的性能开销。

但是,我不能否认它的简单性,所以如果您能够在目标环境中安装 awscli 并配置 Kubernetes,那么这是一个可供您使用的选项:

var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
var kubernetesClient = new Kubernetes(config);

foreach (var ns in kubernetesClient.CoreV1.ListNamespace().Items)
{
    Console.WriteLine(ns.Metadata.Name);
}

首先感谢解决方案 Steve Rukuts Howerver 经过几次测试后,似乎令牌有时未被授权

在 aws cli github 中挖掘了一下之后,似乎 encodedUrl 最后被修剪为 '='

所以在将代码更改为

var encodedUrl = Convert.ToBase64String(Encoding.UTF8.GetBytes(signedUrl)).TrimEnd('=');

它似乎解决了这个问题

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM