Я хотел бы разработать программу .NET, которая обращается к API Kubernetes для выполнения некоторых административных задач. Наш кластер Kubernetes — это EKS, поэтому я хотел бы использовать собственный метод аутентификации AWS для создания временных учетных данных и доступа к API, поскольку моя программа должна работать вне Kubernetes по архитектурным причинам. Я хотел бы сопоставить роль AWS с ролью Kubernetes, а затем использовать права, предоставленные этой роли, для доступа к API и выполнения заданных задач.
Я увидел, что в интерфейсе командной строки AWS есть команда под названием 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, я вижу, что токен действительно представляет собой URL-адрес в кодировке base 64, который, предположительно, будет вызываться кластером для получения идентификатора вызывающего абонента и попытки сопоставить его с ролью перед предоставлением доступа — довольно хороший трюк. Действительно, обращение к этому 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
Однако, к сожалению, в AWS.SecurityToken также не существует C#-эквивалента generate_presigned_url()!
Итак, как мне сгенерировать токен безопасности EKS для использования с клиентской библиотекой .NET Kubernetes, не обращаясь к интерфейсу командной строки AWS?





При написании и исследовании этого вопроса я наткнулся на ответ, которым я хотел бы поделиться, чтобы сэкономить время людей в будущем.
Обратите внимание, что я новичок в Kubernetes, поэтому могу не все полностью понять, поэтому, пожалуйста, убедитесь, что вы понимаете, что происходит, и тщательно протестируйте. Кроме того, пожалуйста, прочитайте до конца, так как есть более простые альтернативы в зависимости от вашего варианта использования.
После того, как я понял формат вывода aws eks get-token, я понял, что этот предварительно подписанный URL-адрес очень похож на предварительно подписанный URL-адрес, используемый в S3. Я смог использовать ту же технику, чтобы создать предварительно подписанный URL-адрес для GetCallerIdentity. В AmazonS3Client.GetPresignedUrl есть много кода для обратной совместимости, который я не полностью понимаю, поэтому это может не работать для каждого отдельного варианта использования.
Однако в этом фрагменте кода показано, как создать токен и пройти аутентификацию в кластере Kubernetes, работающем на EKS:
// 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.
Кроме того, большое количество результатов поиска связано с созданием предварительно подписанных URL-адресов для S3, и я не думаю, что общеизвестно, что вы можете создавать предварительно подписанные URL-адреса для других конечных точек AWS, поэтому, надеюсь, это поможет решить эту конкретную проблему и породит некоторые другие идеи.
Было бы упущением с моей стороны не упомянуть гораздо более простую альтернативу — просто создать клиент Kubernetes, используя вашу локальную конфигурацию Kubernetes. Однако:
Однако я не могу отрицать простоту, поэтому этот вариант доступен вам, если вы можете установить awscli в своей целевой среде и настроить Kubernetes:
var config = KubernetesClientConfiguration.BuildConfigFromConfigFile();
var kubernetesClient = new Kubernetes(config);
foreach (var ns in kubernetesClient.CoreV1.ListNamespace().Items)
{
Console.WriteLine(ns.Metadata.Name);
}
Несколько раз протестировав решение Стива Рукутса, кажется, что токен иногда не авторизован.
Немного покопавшись в github aws cli, кажется, что encodedUrl обрезано для '=' в конце.
Итак, после изменения кода на:
var encodedUrl = Convert.ToBase64String(Encoding.UTF8.GetBytes(signedUrl)).TrimEnd('=');
Вроде решил проблему