[Github Actions] configure-aws-credentialsを読み解く

この記事は [IDaaS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/idaas) 9日目の投稿です。

## はじめに

Github ActionsでAWSリソースを操作する際、何も考えずに定義してしまうこのアクション。
実際これって内部で何してるのか気になったので調査。

```yml
  - uses: aws-actions/configure-aws-credentials@v1
    with:
      role-to-assume: ${{ inputs.aws_role_arn }}
      aws-region: ${{ inputs.aws_region }}
```

## どれ？
これ

https://github.com/aws-actions/configure-aws-credentials

## 前提
- actionの引数には以下のみ指定
    - role-to-assume
    - aws-region


## actions.yml
とりあえず一番下をみてみる。index.jsを呼び出している模様。
```yml
#省略
runs:
  using: 'node12'
  main: 'dist/index.js'
  post: 'dist/cleanup/index.js'
```

## index.js
run()がメイン関数になるようだ。
```js
284: async function run() {
285:   try {
286:     // Get inputs
287:     const accessKeyId = core.getInput('aws-access-key-id', { required: false });
288:     const audience = core.getInput('audience', { required: false });
289:     const secretAccessKey = core.getInput('aws-secret-access-key', { required: false });
```
入力値に限定すると以下が該当、残りはデフォルトの値が入る。
```js
290: const region = core.getInput('aws-region', { required: true });
293: const roleToAssume = core.getInput('role-to-assume', {required: false});
```

<br>

@actions/coreはgithub actions用のツールキットであり、
環境変数や引数を読み込んだり、次のステップに受け渡したりできる模様。

https://www.npmjs.com/package/@actions/core


<br>

#### exportRegion
`AWS_REGION`、`DEFAULT_AWS_REGION`を環境変数として出力している
```js
308:  exportRegion(region);
```

#### useGithubOICDProvider
以下理由からTrueとなるはず
- `roleToAssume`は定義済み
- `ACTIONS_ID_TOKEN_REQUEST_TOKEN`はIDトークンを取得するためのトークンのようで、GithubActions起動時に自動で環境変数に設定される。

```js
310: // This wraps the logic for deciding if we should rely on the GH OIDC provider since we may need to reference
311: // the decision in a few differennt places. Consolidating it here makes the logic clearer elsewhere.
312:  const useGitHubOIDCProvider = () => {
313:    // The assumption here is that self-hosted runners won't be populating the `ACTIONS_ID_TOKEN_REQUEST_TOKEN`
314:    // environment variable and they won't be providing a web idenity token file or access key either.
315:    // V2 of the action might relax this a bit and create an explicit precedence for these so that customers
316:    // can provide as much info as they want and we will follow the established credential loading precedence.
317:    return roleToAssume && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN && !accessKeyId && !webIdentityTokenFile
318:   }
```

#### exportCredentials
`exportReigon`と同様、環境変数をセット。
```js
330: exportCredentials({accessKeyId, secretAccessKey, sessionToken});
```

#### configureProxy
`http_proxy`の定義をしていない為、特に動作しない
```js
334: configureProxy(proxyServer);
```

#### webIdentityToken
`useGithubOIDCProvider`はTrueなので、`webIdentityToken`と`roleDurationSeconds`を取得。
各引数にはデフォルトの値が入る。
```
audience: sts.amazonaws.com
roleDurationSeconds: 6 * 3600
```

```js
341: if(useGitHubOIDCProvider()) {
342:   webIdentityToken = await core.getIDToken(audience);
343:   roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || DEFAULT_ROLE_DURATION_FOR_OIDC_ROLES;
344:   // We don't validate the credentials here because we don't have them yet when using OIDC.
345: } 
```

core.getIDTokenは何をしているかというと、OidcClient.getIDTokenを呼び出し、

```core/src/core.ts
// https://github.com/actions/toolkit/blob/main/packages/core/src/core.ts
export async function getIDToken(aud?: string): Promise<string> {
  return await OidcClient.getIDToken(aud)
}
```
getIdTokenURLにより`ACTIONS_ID_TOKEN_REQUEST_URL`を取得、URLエンコードした`audience(=sts.amazonaws.com)`と組み合わせ`id_token_url`とした上で、getCallを呼び出し、`id_token`を取得している。
```core/src/oidc-utils.ts
// https://github.com/actions/toolkit/blob/main/packages/core/src/oidc-utils.ts
  static async getIDToken(audience?: string): Promise<string> {
    try {
      // New ID Token is requested from action service
      let id_token_url: string = OidcClient.getIDTokenUrl()
      if (audience) {
        const encodedAudience = encodeURIComponent(audience)
        id_token_url = `${id_token_url}&audience=${encodedAudience}`
      }

      debug(`ID token url is ${id_token_url}`)

      const id_token = await OidcClient.getCall(id_token_url)
      setSecret(id_token)
      return id_token
    } catch (error) {
      throw new Error(`Error message: ${error.message}`)
    }
  }
```

#### roleCredentials
AWS STSから先ほど取得した`id_token`などを利用して最終的に以下の情報を取得
エクスポテンシャルバックオフってこうやって使うのか・・
```
accessKeyId
secretAccessKey
sessionToken
```

```js
356:   // Get role credentials if configured to do so
357:   if (roleToAssume) {
358:     const roleCredentials = await retryAndBackoff(
359:       async () => { return await assumeRole({
360:         sourceAccountId,
361:         region,
362:         roleToAssume,
363:         roleExternalId,
364:         roleDurationSeconds,
365:         roleSessionName,
366:         roleSkipSessionTagging,
367:         webIdentityTokenFile,
368:         webIdentityToken
369:       }) }, true);
```

#### exportCredentials
取得したcredentialを環境変数へ
```js
370:   exportCredentials(roleCredentials);
```

#### exportAccountId
最後にアカウントIDをOutputとして設定
```js
378: await exportAccountId(maskAccountId, region);
```

## まとめ
普段何気なく使っているパッケージも中身を見てみると参考になります。
こういう機会がないと読んでる余裕ないんですけどね。

IDaaSを利用したアプリケーション開発にお悩みの方は弊社TC3までお問い合わせください。また、優秀なアーキテクトからの応募も常時受け付けています。

https://www.tc3.co.jp/