IAM Role Anywhere の AWS CLI設定

IAM Role Anywhereの構築を行ったあと、AWS CLIの設定をする際にWindowsの証明書ストアにある証明書をつかうまでに少し悩んだのでその設定方法を書いておきます。

具体的には `.aws/config` の書き方および `aws_signing_helper` のオプションです。
以下の記事にあるように `aws_signing_helper` が証明書ストアをサポートしたので証明書ストアから読み込んでAWS CLIで使います。

https://aws.amazon.com/jp/about-aws/whats-new/2023/07/iam-roles-anywhere-credential-helper-os-certificate-stores/

証明書ストアのサポートは `1.0.5` からのようですが、今回設定したのは `1.1.1` です。
参考になれば幸いです。


今回 CA は ADCS です。クラスメソッドさんの記事が参考になりますが、aws_signing_helperが証明書ストアを使用できるようになったため、証明書テンプレートでエクスポート可能にする必要はありません。

https://dev.classmethod.jp/articles/iam-roles-anywhere-with-adcs/

## 環境構築 (トラストアンカー、プロファイル、ロール)

CloudFormationで行いました。証明書はそれぞれの環境に合わせてください。

```iam-anywhere.yaml
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  IAMRoleAnywhereTrustAnchor:
    Type: AWS::RolesAnywhere::TrustAnchor
    Properties:
      Name: "MY-TRUST-ANCHOR"
      Enabled: true
      Source:
        SourceData:
          X509CertificateData: |
            -----BEGIN CERTIFICATE-----
            PEM OF ADCS
            -----END CERTIFICATE-----
        SourceType: "CERTIFICATE_BUNDLE"

  IAMRoleAnywhereRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: "AnywhereRole"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "rolesanywhere.amazonaws.com"
            Action:
              - "sts:AssumeRole"
              - "sts:TagSession"
              - "sts:SetSourceIdentity"
            Condition:
              ArnEquals:
                "aws:SourceArn": !GetAtt IAMRoleAnywhereTrustAnchor.TrustAnchorArn
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

  IAMRoleAnywhereProfile:
    Type: AWS::RolesAnywhere::Profile
    Properties:
      Enabled: true
      Name: "AnywhereProfile"
      RoleArns:
        - !GetAtt IAMRoleAnywhereRole.Arn

Outputs:
  RoleArn:
    Description: "The ARN of the RoleAnywhere Role"
    Value: !GetAtt IAMRoleAnywhereRole.Arn
  ProfileArn:
    Description: "The ARN of the RoleAnywhere Profile"
    Value: !GetAtt IAMRoleAnywhereProfile.ProfileArn
  TrustAnchorArn:
    Description: "The ARN of the RoleAnywhere TrustAnchor"
    Value: !GetAtt IAMRoleAnywhereTrustAnchor.TrustAnchorArn
```

## セレクタの設定

ここから `aws_signing_helper` の話になります。

https://github.com/aws/rolesanywhere-credential-helper?tab=readme-ov-file#read-certificate-data

cert-selector flag の章をみると 証明書ストアから証明書を選ぶには `--cert-selector` を使用して指定するようです。

```cmd
--cert-selector Key=x509Subject,Value=CN=Subject Key=x509Issuer,Value=CN=Issuer Key=x509Serial,Value=15D19632234BF759A32802C0DA88F9E8AFC8702D
```

`file:` でファイルを指定する方式もあるようですが今回はコマンドラインオプションに指定します。
またセレクタの指定に `x509Serial` があるのでそれを使用することにします。[^1]

[^1]: PowerShellだと `cert:\CURRENT_USER\MY\${Thumbprint}` で指定するのでserialじゃなくてThumbprintの方がうれしいなぁとか若干思いました。

`--cert-selector` を確認するのに `sign-string` コマンドが便利なのでそちらをつかって確認しましょう。
`sign-string` ではAWS側でIAM Role Anywhereの設定をしなくても `--cert-selector` で証明書ストアから読み込めたかを確認できます。

まず `certmgr.msc` を開いて使用する証明書のシリアル番号を調べます。(ちなみにこの証明書は鍵をエクスポートできない証明書です)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19245/09c86fda-f188-2305-8c87-2d848344fd2b.png)

このシリアル番号を使用して、実行してみます。

```cmd
>aws_signing_helper sign-string --format text --cert-selector \
  Key=x509Serial,Value=2b000381af26496a4d5cd390ff0000000381af
865984511f956149088dfdebca3d839545995f1609f99a4de040ee10d57756c
...(中略)...
cb5e76da5a089827f112d36ca7d9f4c40e10ee3bf1e0e04a571c75b9b015a71
54f7123f
```

よさそうです。

また見つからなかった場合は `2024/06/25 10:48:03 no matching certs found in cert store` となりました。

:::note warn
実は`x509Issuer`での指定も試してみたのですがうまくいきませんでした。
以下のようにIssuerが CNだけではなく、DCなども含んでいたためではないかと思っていますが、
いくつか試してみましたが、どのように指定するのかわかりませんでした。

`aws_signing_helper read-certificate-data` で一覧が取得でき、cert-selectorも表示されるようなのですが、自分の証明書ストアだと一意に決まらないセレクタも提供されていました。

実用上は`x509Serial`で十分な気もしますが、知っている方いれば教えてください。
(だいぶ理解できたので追記を書きました)

```
CN = EXAMPLE-CA
DC = example
DC = com
```
:::


`--cert-selector` がわかったのでこれを用いて `.aws/config` を設定します。
見やすさの為、改行していますが、実際には１行で書いています。

```.aws/config
[profile anywhere]
region = ap-northeast-1
output = json
credential_process = aws_signing_helper credential-process \
 --cert-selector "Key=x509Serial,Value=2b000381af26496a4d5cd390ff0000000381af" \
 --trust-anchor-arn "<YOUR_TRUST_ANCHOR_ARN>" --profile-arn "<YOUR-PROFILE-ARN>" \
 --role-arn "<YOUR-ROLE-ARN>"
```

`aws s3 ls --profile anywhere` として実行できます。

ADCSを使用する場合、できればエクスポート不可な証明書テンプレートでやりたいですよね。
これでエクスポート不可にしてもつかえますね。

## 追記 : x509Subject, x509Issuer の書き方

READMEにも書いてありましたが、[RFC2253](https://www.rfc-editor.org/rfc/rfc2253.html) に従って書くようです。ただし、手で書くには厳しい内容だなと思いました。

例えば、PowerShellなどで以下のようなSubjectの証明書があるとします。
実際にはADCSの既定の証明書テンプレート `ユーザー` で作ったものでした。(メアドやドメインなど変えています)

```powershell
Get-Item cert:\CURRENT_USER\My\xxxxxxxx | fl Subject
Subject : E=who@example.com, CN=xxxxxx, OU=Employees, DC=example, DC=com
```

`read-certificate-data` の出力は以下のようになります。

```
1) fd6aa358da7c8ed80125ceb3adace70e805ae01d "CN=xxxxxx,OU=Employees,1.2.840.113549.1.9.1=#0c1377686f406578616d706c652e636f6d,0.9.2342.19200300.100.1.25=#130a6578616d706c65,0.9.2342.19200300.100.1.25=#1305636f6d"
```

`aws_signing_helper read-certificate-data`ではSubjectを取得して表示していますので、これを`x509Subject`のセレクタとして使用できます。

```selector.json
[
 { "Key" : "x509Subject",
   "Value" : "CN=xxxxxx,OU=Employees,1.2.840.113549.1.9.1=#0c1377686f406578616d706c652e636f6d,0.9.2342.19200300.100.1.25=#130a6578616d706c65,0.9.2342.19200300.100.1.25=#1305636f6d"
  }
]
```

でも`1.2.840.113549.1.9.1` , `0.9.2342.19200300.100.1.25` って何? ってなりますね。

よくわからない数字の羅列 1.2.840.113549.1.9.1 はASN.1で記載されているOID (Object ID) です。

E([emailAddrss](https://oidref.com/1.2.840.113549.1.9.1)), 0.9.2342.19200300.100.1.25 はDC ([domainComponent](https://oidref.com/0.9.2342.19200300.100.1.25))を示しています。
また値となる文字列は #以降でおよそUTF-8の16進表現となっています。(ただし先頭数文字は文字数?が付与されているようです。)

ですのでこの謎の部分は、以下の表現となります。

```
1.2.840.113549.1.9.1=#0c1377686f406578616d706c652e636f6d : E=who@example.com
0.9.2342.19200300.100.1.25=#130a6578616d706c65 : DC=example
0.9.2342.19200300.100.1.25=#1305636f6d: DC=com
```

たしかにSubjectに記載の内容ですが、何をOIDで指定するのか? 順番はどうなるのか? など不明瞭な点が多いです。
ASN.1とかLDAPに詳しい方はわかるのかもしれませんが、これを気軽に書くのは無理かなと思いました。
特にSubjectは read-certificate-dataで取得できますが、Issuerはどうするのさという気分にはなります。

x509Serialとx509Issuerで証明書を一意にしたいと私は思うのですが、現状ではx509Serialだけで指定するのが無難かなと思います。