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

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

S3にSigV4署名でアクセスする方法は、AWS公式の以下のページにJavaとC#でのサンプルがあります。

- [Examples: Signature Calculations in AWS Signature Version 4 - Amazon Simple Storage Service](https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html)

これをベースにIAM認証を使っているAPI GatewayにC#でアクセスしてみます。

Pythonならば、以前の記事([IAM認証のAWS API GatewayにPythonからSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/1ea284c97075f3a34b30))で似たことをしました。


# 前提

IAMユーザのアクセスキーが `~/.aws/credentials` に設定されていて、そのIAMユーザでAPI Gatewayにアクセスするものとします。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/14時点のものです。


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

AWS公式のS3アクセスのサンプルをダウンロードします。

```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 ..

$ tree
.
└── tmp
    ├── AmazonS3SigV4_Samples_CSharp.zip
    ├── AWSSignatureV4-S3-Sample
    │   ├── App.config
    │   ├── AWSSignatureV4-S3-Sample.csproj
    │   ├── GetS3ObjectSample.cs
    │   ├── POSTExampleForm.html
    │   ├── PostS3ObjectSample.cs
    │   ├── PresignedUrlSample.cs
    │   ├── Program.cs
    │   ├── Properties
    │   │   └── AssemblyInfo.cs
    │   ├── PutS3ObjectChunkedSample.cs
    │   ├── PutS3ObjectSample.cs
    │   ├── Signers
    │   │   ├── AWS4SignerBase.cs
    │   │   ├── AWS4SignerForAuthorizationHeader.cs
    │   │   ├── AWS4SignerForChunkedUpload.cs
    │   │   ├── AWS4SignerForPOST.cs
    │   │   └── AWS4SignerForQueryParameterAuth.cs
    │   └── Util
    │       └── HttpHelpers.cs
    └── AWSSignatureV4-S3-Sample.sln

5 directories, 18 files
```

IAMユーザを使ってS3アクセスするサンプルです。今回はこのうち、`Signers` と `Util` というディレクトリだけ使います。

```console
$ mv tmp/AWSSignatureV4-S3-Sample/Signers ./
$ mv tmp/AWSSignatureV4-S3-Sample/Util ./
$ rm -r tmp
```

このサンプルはnamespaceが `AWSSignatureV4_S3_Sample` となっています。今回はS3ではないので、適当な名前 `Sample` に全置換します。(このコマンドの説明は [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'
```

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

```console
$ tree
.
├── Signers
│   ├── AWS4SignerBase.cs
│   ├── AWS4SignerForAuthorizationHeader.cs
│   ├── AWS4SignerForChunkedUpload.cs
│   ├── AWS4SignerForPOST.cs
│   └── AWS4SignerForQueryParameterAuth.cs
└── Util
    └── HttpHelpers.cs

2 directories, 6 files
```


# 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>
```

必要なパッケージをダウンロードします。 `AWSSDK.Core` だけで大丈夫です。今回はHTTP(S)でAPIアクセスするだけですので、API Gatewayのパッケージは不要です。

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


# C#のソースコード

`Program.cs` は以下です。

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

using Amazon.Runtime.CredentialManagement;

using Sample.Signers;
using Sample.Util;

namespace Sample
{
    class Program
    {
        private static void Run()
        {
            // ~/.aws/credentials からアクセスキー、シークレットキーを読み取る
            var sharedFile = new SharedCredentialsFile();
            sharedFile.TryGetProfile("default", out CredentialProfile credentialProfile);

            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"}
            };

            // 署名を作成
            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,
                                                        credentialProfile.Options.AccessKey,
                                                        credentialProfile.Options.SecretKey);

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

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

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

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


# 実行

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

```console
$ dotnet run
```

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


# 2020/10/19 追記

IAMロールの場合の記事を書きました。

- [IAM認証のAWS API GatewayにC#からIAMロールでSigV4署名してアクセスするには](https://qiita.com/suzuki-navi/items/4bf03406069761b8257f)