AWS Using a Google Cloud service account and AssumeRoleWithWebIdentity to authenticate to AWS in C#

In the last post we looked at how to set up a trust policy and role in AWS so that we can use a Google ID token to authenticate to AWS. But how do we actually use this in C#?

The AWS SDK contains a class AssumeRoleWithWebIdentityCredentials – but on closer look, this class turns out to be not too useful since it expects us to provide the input token (the service account’s ID token) in a file. Assuming that out code runs on Cloud Run or on a VM instance, it would make more sense to:

  1. Call GoogleCredential.GetApplicationDefaultAsync() to get a GoogleCredential object which automatically fetches tokens from the metadata server.
  2. Use the GoogleCredential object to construct an AWS credential.

To get an ID token from a GoogleCredential, we can call GetOidcTokenAsync, which is part of the IOidcTokenProvider interface. So let’s create a class that

  • Accepts an IOidcTokenProvider object as input.
  • Derives from AWSCredentials so that it can be used in the AWS SDK.

Instead of deriving from AWSCredentials directly, we can derive from RefreshingAWSCredentials. As its name suggests, RefreshingAWSCredentials caches credentials and only refreshes them when necessary, limiting the number of times we actually have to call AssumeRoleWithWebIdentityCredentials. All we have to do is implement GenerateNewCredentials and its async counterpart GenerateNewCredentialsAsync:

using Amazon.Runtime;
using Amazon.SecurityToken;
using Google.Apis.Auth.OAuth2;
using System.Threading;
using System.Threading.Tasks;

namespace CredentialUtils.GcpToAzure
{
  public class AwsFederatedServiceAccountCredential : RefreshingAWSCredentials
  {
    public IOidcTokenProvider SourceCredential { get; }
    public string RoleArn { get; }
    public string Audience { get; }

    public int DurationSeconds { get; set; }
    public string RoleSessionName { get; set; }

    public AwsFederatedServiceAccountCredential(
      IOidcTokenProvider sourceCredential, 
      string audience,
      string roleArn)
    {
      this.SourceCredential = sourceCredential;
      this.Audience = audience;
      this.RoleArn = roleArn;
    }

    protected override CredentialsRefreshState GenerateNewCredentials()
    {
      return GenerateNewCredentialsAsync().Result;
    }

    protected override async Task<CredentialsRefreshState> GenerateNewCredentialsAsync()
    {
      //
      // (1) Get a Google-issued ID token token.
      //
      var idTokenSource = await this.SourceCredential.GetOidcTokenAsync(
        OidcTokenOptions.FromTargetAudience(this.Audience));
      var idToken = await idTokenSource.GetAccessTokenAsync();

      //
      // (2) Use token to assume a role and obtain temporary
      //   AWS credentials.
      //
      using (var stsClient = new AmazonSecurityTokenServiceClient())
      {
        var response = await stsClient.AssumeRoleWithWebIdentityAsync(
          new Amazon.SecurityToken.Model.AssumeRoleWithWebIdentityRequest()
          {
            DurationSeconds = this.DurationSeconds,
            RoleArn = this.RoleArn,
            RoleSessionName = this.RoleSessionName,
            WebIdentityToken = idToken
          },
          CancellationToken.None);

        return new CredentialsRefreshState(
          response.Credentials.GetCredentials(),
          response.Credentials.Expiration);
      }
    }
  }
}

We can use this class like so:

var googleCredential = await GoogleCredential.GetApplicationDefaultAsync();
var awsCredentials = new AwsFederatedServiceAccountCredential(
    googleCredential,
    "aws-trust-1", // audience
    "arn:aws:iam::0123456789000:role/google-cloud-myapp"  // role to assume (full ARN)
    )
    {
        DurationSeconds = 900,
        RoleSessionName = "test-session"
    };

Now we have a convenient way to authenticate from Google Cloud to AWS and vice versa.

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home