🔐

AWS SDK v3 における認証

2022/01/20に公開

対象読者

  • AWS SDK v3 の適切な credential provider の選び方を知りたい方、公式ドキュメントだけではわからなかった方
  • credential providerの使用方法・仕組みを知りたい方
  • AWS の認証について、使用目的をベースに学びたい方

AWS SDK v3 credential provider のインストール

npm install @aws-sdk/credential-providers
  • すべての定義が @aws-sdk/credential-providers に含まれています。個別のパッケージが見つかりますが、個別にインストールすることは非推奨となっています。
  • TS の型定義は含まれています。

各種サービスでServiceRoleの認証情報を取得する方法

EC2

fromInstanceMetadata() view code

  • 引数なしで動きます。
  • http://169.254.169.254(Instance Metadata Endpoint) からトークンを取得します。
  • 詳しい処理については#reading fromInstanceMetadata()

AWS Lambda

fromEnv() view code

  • 引数なしで動きます。
  • 注意: Dockerイメージを指定する場合も、fromContainerMetadata()は使用しません。
  • LambdaにAWSがセットする環境変数を使用します。
  • 詳細: 実際に使われる環境変数は以下です。
    • AWS_ACCESS_KEY_ID (必須)
    • AWS_SECRET_ACCESS_KEY (必須)
    • Temporary security credentials in IAM
      • AWS_SESSION_TOKEN
      • AWS_CREDENTIAL_EXPIRATION
    • 注意: 自分でセットして使うというようなことは、基本的にないはずで、避けるべきです。

ECS

fromContainerMetadata() view code

引数なしで動きます。

詳しい処理については#reading fromContainerMetadata()

INIファイル

fromIni()

INIファイルはプロフィールやShared Configとも呼ばれます。aws configure経由で生成される、~/.aws/credentialsにあるやつです。

例えば、ローカル環境であることを分岐して、 fromIni({ profile: process.env.AWS_PROFILE }) のようにします。

注意: プロダクション環境では使用を避けてください。

また、#Cloud9(EC2)や、#EC2へのSSH経由によってのみ使えるようにするのがセキュリティ的にベターかと思います。

Cloud9(EC2)

AWS managed temporary credentialsを使う場合はfromIni()を使用します。一部の操作に制限があるので注意してください。基本的にはこちらが推奨されます。~/.aws/credentialsが見当たらない場合は、Preferences > AWS Settings > Credentials > AWS managed temporary credentials がセットされているか確認してください。
注意: fromTemporaryCredentials()ではありません。STSとの処理はAWS側が自動でやってくれるのがAWS managed temporary credentialsです。その結果はINIファイルに入っています。

もしくは、EC2 Instance Profile を設定して#EC2と同様にfromInstanceMetadata()を使用します。

Cloud9(SSH)

SSHを使用している場合は#INIファイルによる方法をとるしかないようです。(source)

fromIni()を使います。EC2タイプのCloud9環境を使用することが推奨されます。

Web Identity Federation (Web、モバイル、他のクラウド環境、GitHub Actions、その他OIDC)

Amazon Cognitoidentity pools(federated identities)の場合は、fromCognitoIdentity()fromCognitoIdentityPool()を使用します。

BrowserとNode.js環境両方で使えます。GitHub ActionsはOpenID Providerを提供してくれるようになったので、この方法が使えるはずです。引数は公式ドキュメントを参照してください。

sts:AssumeRoleWithWebIdentityを使う方法の場合は、fromWebToken()を使用します。Google、Amazon、Facebookにしか対応されておらず、特にモバイルなどではCognitoを使用する方法が推奨されています。引数は公式ドキュメントを参照してください。

オンプレミス

#Cloud9(SSH)と同様の事情かと思います。他のクラウドというだけであったり、OIDC Providerを独自で提供できるのであればWeb Identity Federationが使えると思います。

EKS

#オンプレミスの事情と同じだと思っています。使ったことないので、cluster authenticationに使われたAWSやOIDCの情報が使えるのかもしれませんが、パッと見たところ、k8sへのログイン認証して終わりなのかな。(あってたらあってるよ!ってコメントもらえると助かります)。

sts:AssumeRole

fromTemporaryCredentials({ /* sts:AssumeRole params, etc... */ }) view code

  • 内部では@aws-sdk/client-stsの、STSClientを経由してsts:AssumeRoleを呼び出しています。つまり、先にSTSClientのための認証をfromEnv()などにより適切に渡すことになります。
  • 使い機会は少ないかと思います。

SSO

fromSSO()

使ったことありません。引数を与えなければINIファイルを読みに行きます。

外部プロセス

fromProcess()

おそらくマニアックな方式だと思われます。認証情報を外部プロセスからもらう方式です。(AWSがそういったツールを用意しているというわけではなさそうです)

重要なインターフェース

以下の2つのインターフェースが、AWS SDK v3 の認証において重要です。

  • Credentials: 静的な認証情報を表す。
  • CredentialsProvider: Credentialsを非同期(Promise)で返す関数

Credentialsの実装は以下です。

export interface Credentials {
    /**
     * AWS access key ID
     */
    readonly accessKeyId: string;
    /**
     * AWS secret access key
     */
    readonly secretAccessKey: string;
    /**
     * A security or session token to use with these credentials. Usually
     * present for temporary credentials.
     */
    readonly sessionToken?: string;
    /**
     * A {Date} when these credentials will no longer be accepted.
     */
    readonly expiration?: Date;
}

https://github.com/aws/aws-sdk-js-v3/blob/5187be2ee1a9731053b1e94bff25395130c0bcef/packages/types/src/credentials.ts#L6-L27

各種のfrom...()CredentialsCredentialsProviderを返します。

Credentialsの使い方

Credentialsを各種AWS SDK v3クライアントのコンストラクタに渡します。例えば、fromEnv() とS3クライアントを組み合わせる場合は以下のようになります。

import { S3 } from '@aws-sdk/client-s3';
import { fromInstanceMetadata } from '@aws-sdk/credential-providers';

const credentials = fromInstanceMetadata();

const s3 = new S3({ credentials /* ... */ });
s3.listBuckets({}).then((result) => console.log(result));

これは例えば、適切なRoleを設定したEC2などで使えます。学習目的の場合は、Nodeをセットアップして実際に試してみると良いかもしれません。

すべてのクライアントは、S3に対して、S3ClientのようにうしろにClientをつけたものが存在します。使用方法は以下のようになります。

import { S3Client, ListBucketsCommand } from '@aws-sdk/client-s3';
import { fromInstanceMetadata } from '@aws-sdk/credential-providers';

const credentials = fromInstanceMetadata();

const s3Client = new S3Client({
  credentials,
})

s3Client.send(new ListBucketsCommand({})).then((result) => console.log(result))

やっていることは同一です。

例: どのCredentialを使うかを環境変数によって決める

たとえば、EC2で開発して、本番環境であるLambdaにデプロイするような例を考えます。

import { fromInstanceMetadata, fromEnv } from '@aws-sdk/credential-providers';

export const createAwsCredentials = () => {
  if (process.env.NODE_ENV === 'production') {
    return fromEnv();
  } else {
    return fromInstanceMetadata();
  }
};

環境変数 NODE_ENVproduction かどうかで分岐して異なる AWS Credentials を作成する関数を用意しておきます。

他にもINIファイルを指定できたり、直接トークンを指定できるようにしたいというような要望に合わせて変えていくといいと思います。


以下はコードリーディングしたものです。バージョンを固定したリンクを置いています。そのバージョンの時の情報です。内容としては少し細かいかもしれません。また、ドキュメントにもこの辺は書いてあります。

詳細: reading fromInstanceMetadata()

view code

  1. インスタンスメタデータのエンドポイントを決定する。
  • a. 環境変数 AWS_EC2_METADATA_SERVICE_ENDPOINT の値
  • b. INIファイル内の ec2_metadata_service_endpoint の値
  • c. これで決まらなければ、まず IPv4 と IPv6 どちらを使うか決める。
    • i. 環境変数 AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE の値 (IPv4/IPv6)
    • ii. INIファイル内の ec2_metadata_service_endpoint_mode の値
    • iii. これで決まらなければ IPv4
  • d. 続いて、IPv4ならhttp://169.254.169.254IPv6ならhttp://[fd00:ec2::254]を使用
  1. PROFILE="$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/)" にてロール名取得(同等のcurl+bash文を書いているだけです、エンドポイントhttp://169.254.169.254は上記で決定した値)
  2. MTOKEN="$(curl http://169.254.169.254/latest/api/token -H x-aws-ec2-metadata-token-ttl-seconds:21600 -X PUT)" でmetadata API用のトークンを取得
  3. CRED="$(curl http://169.254.169.254/latest/meta-data/iam/security-credentials/${PROFILE} -H x-aws-ec2-metadata-token:${MTOKEN})" でTempoary Credential取得

後半は以下を実装した形になっています。

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html

ただし、リトライ処理などが途中にあります。

詳細: reading fromContainerMetadata()

view code

以下を実装した形になっています。

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html

curl 169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI だけでほぼ終わります。一部、AWS_CONTAINER_CREDENTIALS_FULL_URIAWS_CONTAINER_AUTHORIZATION_TOKENによって挙動を変える部分がありますが、詳細な利用目的はわかりません。

ECSでしか使わず、ECSで使う場合は迷わずにfromContainerMetadata()とすればよいので、あまり気になる部分もなさそうです。

詳細: reading fromEnv()

view code

環境変数から読んでセットするだけなのでかんたんです。fromEnv()を読んだタイミングではなく、そのCredentialsを使うタイミングでのprocess.envが使用されるので、動的にprocess.envを更新するようなことも、できなくはないです。

詳細: reading fromTemporaryCredentials()

view code

@aws-sdk/client-stsSTSClientを利用してsts:AssumeRoleしているだけのようです。MFAにも対応しています。


追加でまた別の認証方式についても読んだり、使ったことないものを試したりするかもしれません。その時はまた追記します。

GitHubで編集を提案

Discussion