IAM認証のAWS API GatewayにC#からIAMロールでSigV4署名してアクセスするには

IAM認証を使っているAWSのAPI Gatewayは、APIリクエスト時にSigV4署名が必要です。

[前回の記事](https://qiita.com/suzuki-navi/items/385deb31f36a2a66fac0)では、IAMユーザで認証するAPI Gateway呼び出しをC#で書きました。

今回は、EC2インスタンスにアタッチされているIAMロールでの認証です。

2020/12/23追記
EC2からはもっとシンプルに書いたほうがよさそうなので、改訂版の記事を書きました。
→ [IAM認証のAWS API GatewayにEC2インスタンスからSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/21b5840608ad67548f0b)


# 前提

IAMロールのアタッチされているEC2インスタンスでC#のコードを実行します。`~/.aws/config` に以下のようにIAMロールが指定されているものとします。

```
[profile default]
role_arn = arn:aws:iam::999999999999:role/ROLENAME
credential_source = Ec2InstanceMetadata
```

API GatewayのリソースポリシーにはこのIAMロールからのAPIアクセスを許可してあるものとします。

動作確認した環境はUbuntu 20.04です。

C#の環境は以下の通り。

```console
$ dotnet --version
3.1.402
```

以下は私の記事でして、このとおりC#をほとんど始めて触っています。C#の流儀と違うところがあったらごめんなさい。

- [Ubuntu 20.04にC#をインストールしてAWS SDKを使ってみる](https://suzuki-navi.hatenablog.com/entry/2020/10/08/215128)

本記事でのライブラリ等は2020/10/19時点のものです。


# サンプルコードダウンロード

SigV4署名するC#のサンプルコードはAWS公式サイトにありますので、それをダウンロードし、必要なディレクトリのみ残します。

この手順の詳細は[前回の記事](https://qiita.com/suzuki-navi/items/385deb31f36a2a66fac0)を参照。

```console
$ mkdir sample
$ cd sample
$ mkdir tmp
$ cd tmp
$ wget https://docs.aws.amazon.com/AmazonS3/latest/API/samples/AmazonS3SigV4_Samples_CSharp.zip
$ unzip AmazonS3SigV4_Samples_CSharp.zip
$ cd ..
$ mv tmp/AWSSignatureV4-S3-Sample/Signers ./
$ mv tmp/AWSSignatureV4-S3-Sample/Util ./
$ rm -r tmp
```

サンプルソースコードのnamespaceを全置換しておきます。(このコマンドの説明は [sedコマンドでディレクトリ内の全ファイルをテキスト全置換するには](https://suzuki-navi.hatenablog.com/entry/2020/10/10/185131))

```console
$ grep -rl AWSSignatureV4_S3_Sample Signers | xargs sed -i 's/AWSSignatureV4_S3_Sample/Sample/g'
$ grep -rl AWSSignatureV4_S3_Sample Util | xargs sed -i 's/AWSSignatureV4_S3_Sample/Sample/g'
```


# C#のプロジェクト作成

`dotnet`コマンドでプロジェクトを作成します。

```console
$ dotnet new console
```

以下のようなディレクトリ構成になります。

```console
$ tree
.
├── obj
│   ├── project.assets.json
│   ├── project.nuget.cache
│   ├── sample.csproj.nuget.dgspec.json
│   ├── sample.csproj.nuget.g.props
│   └── sample.csproj.nuget.g.targets
├── Program.cs
├── sample.csproj
├── Signers
│   ├── AWS4SignerBase.cs
│   ├── AWS4SignerForAuthorizationHeader.cs
│   ├── AWS4SignerForChunkedUpload.cs
│   ├── AWS4SignerForPOST.cs
│   └── AWS4SignerForQueryParameterAuth.cs
└── Util
    └── HttpHelpers.cs

3 directories, 13 files
```

`sample.csproj`に以下のように `RootNamespace` の項目を追加します。サンプルダウンロード後に全置換したnamespaceを指定します。

```xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <RootNamespace>Sample</RootNamespace>
  </PropertyGroup>

</Project>
```

必要なパッケージをダウンロードします。 

```console
$ dotnet add package AWSSDK.SecurityToken
```


# C#のソースコード

`Program.cs` は以下です。

2020/12/23追記：もう少しシンプルなコード → [IAM認証のAWS API GatewayにEC2インスタンスからSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/21b5840608ad67548f0b)

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;

using Sample.Signers;
using Sample.Util;

namespace Sample
{
    class Program
    {
        private static async Task Run()
        {
            // ~/.aws/credentials からRoleArnを読み取る
            SharedCredentialsFile sharedFile = new SharedCredentialsFile();
            sharedFile.TryGetProfile("default", out CredentialProfile credentialProfile);
            string roleArn = credentialProfile.Options.RoleArn;

            // IAMロールにassumeする
            InstanceProfileAWSCredentials instanceCredentials = new InstanceProfileAWSCredentials();
            AmazonSecurityTokenServiceClient stsClient = new AmazonSecurityTokenServiceClient(instanceCredentials);
            AssumeRoleRequest assumeRoleRequest = new AssumeRoleRequest
            {
                RoleArn = roleArn,
                RoleSessionName = "test_session",
            };
            var assumeRoleResponse = await stsClient.AssumeRoleAsync(assumeRoleRequest);
            var credentials = assumeRoleResponse.Credentials;

            var uri = new Uri("https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello");

            // 署名するためのソースとなるヘッダ情報
            var headers = new Dictionary<string, string>
            {
                {AWS4SignerBase.X_Amz_Content_SHA256, AWS4SignerBase.EMPTY_BODY_SHA256},
                {"content-type", "text/plain"},
                {"x-amz-security-token", credentials.SessionToken}, // IAMロールではこれが必要
            };

            // 署名を作成
            var signer = new AWS4SignerForAuthorizationHeader
            {
                EndpointUri = uri,
                HttpMethod = "GET",
                Service = "execute-api",
                Region = "ap-northeast-1"
            };
            var authorization = signer.ComputeSignature(headers,
                                                        "",   // no query parameters
                                                        AWS4SignerBase.EMPTY_BODY_SHA256,
                                                        credentials.AccessKeyId,
                                                        credentials.SecretAccessKey);

            // リクエストヘッダに署名を追加
            headers.Add("Authorization", authorization);

            // リクエスト実行
            // HttpHelpers はUtilで定義
            HttpHelpers.InvokeHttpRequest(uri, "GET", headers, null);
        }

        static void Main(string[] args)
        {
            Run().Wait();
        }
    }
}
```

`uri`はAPI GatewayのAPIのURLを入れます。


# 実行

以下のコマンドで実行できます。

```console
$ dotnet run
```

ダウンロードしたサンプルコードの`Signers`と`Util`にデバッグ用出力があるので、いろいろ表示されますが、最後にAPI Gatewayからのレスポンスが表示されます。


# 情報源

C#でIAMロールにAssumeする方法がわからず、今回のテーマは難儀でした。わかってしまえば大したことないのですが。

参考にした情報は、前回の記事に加えて、AWS SDK for .NETのAPIレファレンスとPython boto3のソースコードと、参考として`awscurl`というコマンドのソースコードです。

AWS SDK for .NETのドキュメントは、説明が少なく、メソッドのシグニチャはわかってもどう使ったらいいのかがわかりませんでした。IAMロールでの署名をしているはずの`awscurl`のソースコードを読み、`awscurl`が呼び出している`boto3`のソースコードも読むことで、AWSのAPIをどう呼び出しているのかを把握し、同じことをC#で書くことで実装できました。

- 前回の自分の記事
    - [IAM認証のAWS API GatewayにPythonからSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/1ea284c97075f3a34b30)
    - [IAM認証のAWS API GatewayにC#からIAMユーザでSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/385deb31f36a2a66fac0)
- AWS SDK for .NETのAPIレファレンス
    - [AWS SDK for .NET V3 API Documentation](https://docs.aws.amazon.com/sdkfornet/v3/apidocs/)
- Pythonのboto3のソース
    - https://github.com/boto/botocore/blob/1.18.15/botocore/session.py
    - https://github.com/boto/botocore/blob/1.18.15/botocore/credentials.py
- `awscurl`コマンドのソース
    - https://github.com/okigan/awscurl/blob/v0.20/awscurl/awscurl.py

boto3はAWSのAPIを呼び出しているだけだと思っていましたが、使いやすいように多くの機能をPythonで実装していることを実感しました。boto3とは違って、AWS SDK for .NETは単にAWS API呼び出しをそのままメソッドにしているだけのように見えます。