IAM認証のAWS API GatewayにEC2インスタンスからSigV4署名してアクセスするには

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

以前に[同様の記事](https://qiita.com/suzuki-navi/items/4bf03406069761b8257f)を書きましたが、EC2のインスタンスプロファイルからIAMロールにスイッチしてからリクエスト署名する処理になっていました。

スイッチせずにインスタンスプロファイルで直接署名すればいいことがわかりましたので、そのコードをここに残しておきます。


# 前提

IAMロールのアタッチされているEC2インスタンスでC#のコードを実行します。 `~/.aws/config` は不要です。

※前回の記事では、EC2にIAMロールがアタッチされているだけではなく、インスタンスプロファイルからIAMロールにスイッチする権限が必要でした。このような権限が必要なケースが前回の記事のコード以外の場面であるのかよくわからず、おそらく前回の記事はミスリードでした。

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

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

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

```console
$ dotnet --version
3.1.404
```

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


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

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
$ 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` は以下です。

```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()
        {
            InstanceProfileAWSCredentials instanceCredentials = new InstanceProfileAWSCredentials();
            var credentials = await instanceCredentials.GetCredentialsAsync();

            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.Token}, // 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.AccessKey,
                                                        credentials.SecretKey);

            // リクエストヘッダに署名を追加
            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からのレスポンスが表示されます。


# 前回の記事との違い

[前回](https://qiita.com/suzuki-navi/items/4bf03406069761b8257f)は `InstanceProfileAWSCredentials` からassumeRoleしていたのが、今回は `InstanceProfileAWSCredentials` をそのまま使っている点です。

diffを見たほうが早いか。

```diff
@@ -16,21 +16,8 @@
     {
         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 credentials = await instanceCredentials.GetCredentialsAsync();
 
             var uri = new Uri("https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello");
 
@@ -39,7 +26,7 @@
             {
                 {AWS4SignerBase.X_Amz_Content_SHA256, AWS4SignerBase.EMPTY_BODY_SHA256},
                 {"content-type", "text/plain"},
-                {"x-amz-security-token", credentials.SessionToken}, // IAMロールではこれが必要
+                {"x-amz-security-token", credentials.Token}, // IAMロールではこれが必要
             };
 
             // 署名を作成
@@ -53,8 +40,8 @@
             var authorization = signer.ComputeSignature(headers,
                                                         "",   // no query parameters
                                                         AWS4SignerBase.EMPTY_BODY_SHA256,
-                                                        credentials.AccessKeyId,
-                                                        credentials.SecretAccessKey);
+                                                        credentials.AccessKey,
+                                                        credentials.SecretKey);
 
             // リクエストヘッダに署名を追加
             headers.Add("Authorization", authorization);
```


# 関連記事

SigV4署名に関する私の記事

- [AWS RDSのログファイルを高速にダウンロードするスクリプト](https://qiita.com/suzuki-navi/items/fdabc8bc82d7183a05f3) (11/06)
- [IAM認証のAWS API GatewayにC#からIAMロールでSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/4bf03406069761b8257f) (2020/10/19)
- [IAM認証のAWS API GatewayにC#からIAMユーザでSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/385deb31f36a2a66fac0) (2020/10/14)
- [IAM認証のAWS API GatewayにPythonからSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/1ea284c97075f3a34b30) (2020/07/22)

最近API Gatewayの記事ばかり続いています。

- [API GatewayをVPC内のプライベートに変更するとHTTP2非対応になる](https://qiita.com/suzuki-navi/items/35c716c25f6511fef7a5) (12/22)
- [API GatewayをVPC内のプライベートアクセスのみに限定](https://suzuki-navi.hatenablog.com/entry/2020/12/21/081530) (12/21)
- [CloudFormationでAPI Gatewayを変更しても反映されない](https://suzuki-navi.hatenablog.com/entry/2020/12/17/214538) (12/17)
- [AWS::ApiGateway::Stage と AWS::ApiGateway::Deployment の違い](https://suzuki-navi.hatenablog.com/entry/2020/12/16/230323) (12/16)