echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【AWS CDK】AWS CodePipelineからGitHubと認証する方法

本日のお題

前回、「GitHubからAWSに認証を行う(OIDCを使用)」というのをAWS CDKで実装しました。

今回はその逆で、「AWSからGitHubに認証を行う」というのをAWS CDKで実装します。(AWS CodePipelineでCI/CDを構成する際に必要になります)

なお、よく使われる&ブログ記事でも多いのは「個人アクセストークン(GitHub Personal Access Token、以下PAT)を使う方法」なのですが、この記事では「接続(CodeStar接続)」で行う方法を扱います。

上記を実施する際に必要な手順

  1. CodeStar接続の作成&GitHub Appの作成&インストール
  2. AWS CDKの実装

CodeStar接続を使うメリット

個人ユーザーに依存しない

PATは個人ユーザーに紐づくため、例えば部署移動や退職などでそのユーザーが抜けてしまった場合、使用できなくなります。
そのため、アプリでPAT使用していると、思わぬ事故(=突然接続できなくなった)の原因になりますし、またそれを管理しておく必要があります。

CodeStar接続は該当のGitHubリポジトリに紐づくので、そういったことは起こりません。(間違ってGitHubアプリを削除してしまった...などが起こる可能性はありますが...)

セキュリティ的な問題

PATは有効期限があり、期限切れ前にアプリ側のPATも更新しなければならず、管理が手間になりますし、無期限だとセキュリティ的に問題です。
またPATが漏洩したらそれだけで該当GitHubに接続できてしまうので、セキュリティ的にも問題になります。

CodeStar接続には有効期限はありませんし、仮にARNが漏洩してもそれだけで該当GitHubに接続できてしまうということはありません。(操作者に該当CodeStar接続を使用する権限がなければ接続できない)

GitHub Enterpriseでも使える

AWS CDKのソースアクション(aws-cdk-lib.aws_codepipeline_actions)には、GitHub Enterprise(以下GHE)専用のアクションがないので、GHEにPATで接続できません。
そのためGHEに接続する場合、PAT以外での接続が必須になります。

ちなみにGitHubの場合、codepipeline_actions.GitHubSourceAction から下記のようにPATを使って接続できます。(AWS CDK公式より)

const sourceAction = new codepipeline_actions.GitHubSourceAction({
  actionName: 'GitHub_Source',
  owner: '<GitHubアカウントまたはorganization名>',
  repo: '<リポジトリ名>',
  oauthToken: `<PATの値>`,
  output: sourceOutput,  // アーティファクトの出力先
  branch: 'develop', // default: 'master'
});

なおAWS CDKでは、CodeStar接続以外に下記ソースプロバイダのアクションが用意されています。

CodeStar接続の作成&GitHub Appの作成&インストール

以下の手順で、CodeStar接続を作成します。

  • CodePipeline左ツリーの [設定] - [接続] から、「接続の作成」をクリックする

  • プロバイダー(GitHub, GHEなど)、及び接続名を選択・入力する

  • AWS Connector for <プロバイダー名> by ...」という画面が表示されるので、「Authorize AWS Connector...」ボタンをクリックする

  • GitHubに接続する」という画面が表示されるので「新しいアプリをインストールする」をクリックする

    • すでに別でGitHubアプリを作成済なら既存のアプリを選択します。
  • この後GitHubの認証画面が表示されるので、ログイン情報を入力する。

    • なおOwners権限をもったアカウントでログインしないとGitHubアプリの作成ができません。
  • GitHubアプリをインストールするリポジトリを選択します。(全て/選択リポジトリのみ)

  • GitHubに接続する」画面で「接続」をクリックする

    • GitHubアプリ名に何か値が表示されているはずです
  • 「<接続名>」画面で「ステータス」が「接続可能」になっていればOKです

CodeStar接続の作成が完了したら、そのARNをコピペしておきます。(AWS CDKで使います)

ちなみに、CodeStar接続自体はAWS CDKでも作成できますが「作成できるのは接続名のみで、GitHubアプリは手作業で作成しないといけない」という理由から、今回は手作業で作成しました。

AWS CDKの実装

では、AWS CDKの実装になります。
なおボリュームの関係で、CodeStar接続に関する部分(=ソースアクション)のみ記載します。

AWS CodePipelineとCDK Pipeline、及びその違いの説明は省略します。(とりあえず「そういうものがあるんだ」くらいの認識で問題ありません。)

AWS CodePipelineの場合
// CodePipelineで使うIAM Role
const pipelineRole = new iam.Role(this, 'codePipelineRole', {
   assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
   roleName: 'codePipelineRole',
   managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')],
});
    
// サービスプリンシパルにCodeBuildを追加(接続だけなら不要かも)
pipelineRole.assumeRolePolicy.addStatements(
  new iam.PolicyStatement({
    // Restrict to listing and describing tables
    principals: [new iam.ServicePrincipal('codebuild.amazonaws.com')],
    actions: ['sts:AssumeRole'],
    effect: iam.Effect.ALLOW,
   }),
);
  
// 上記Roleに追加するIAMポリシー
const pipelineInlinePolicy = new iam.Policy(this, 'pipelineInlinePolicy', {
  // CodePipelineにcodestar-connections:UseConnectionを許可するポリシー。
  // これがないとCodeStar接続ができないので注意!!!
  policyName: 'pipelineInlinePolicy',
  statements: [
    new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['codestar-connections:UseConnection'],
    resources: [<作成したCodeStar接続のArn>],
  }),
  // あと、CodeBuildで必要なアクションの許可だったり、
  // Artifactバケットへのアクセス許可だったりを定義
  new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['codebuild:StartBuild', 'codebuild:BatchGetBuilds'],
    resources: [`arn:aws:codebuild:${this.region}:${this.account}:project/*`],
  }),
  new iam.PolicyStatement({
    effect: iam.Effect.ALLOW,
    actions: ['s3:*'],
    resources: [`<ArtifactバケットのARN>`, `<ArtifactバケットのARN>/*`],
  }),
});
  
pipelineRole.attachInlinePolicy(pipelineInlinePolicy);  
  
// ソースアクションの設定
const sourceArtifact = new codepipeline.Artifact();
const sourceAction = new actions.CodeStarConnectionsSourceAction({
  actionName: 'CodeStarConnectionsSourceAction',
  // ここで作成したCodeStar接続のARNを設定するのが重要
  connectionArn: `<作成したCodeStar接続のARN>`,
  output: sourceArtifact,
  owner: '<GitHubのアカウントororganization名(=repoの「/」の左側)>',
  repo: '<対象のリポジトリ名>',
  // デフォルトはmaster(mainじゃない)
  branch: '<対象のブランチ名>', 
  // 先に作成しておいたIAM Roleを設定する。
  // これがないと接続に失敗するので注意
  role: pipelineRole,
  // この2つはデフォルト値(同じなら設定不要)
  // runOrder: 1,
  // triggerOnPush: true,
});
CDK Pipelineの場合
// IAM Roleや割り当てポリシーはAWS CodePipelineと同じなので省略
// AWS CodePipeline同様、IAM Roleでcodestar-connections:UseConnectionを
// CodePipeline(CodeBuildも?)に許可しないと接続が失敗するので注意
const steps = new CodeBuildStep('CodeBuildStep', {
   commands: ['npm run build', 'npx cdk synth'],
   actionRole: pipelineRole,
   // inputにCodePipelineSource.connectionメソッドで接続情報を指定する。
  // ここが重要!!!
   input: CodePipelineSource.connection('<対象のリポジトリ名>', '<対象のブランチ名>', {
     connectionArn: `<作成したCodeStar接続のARN>`,
     triggerOnPush: true,
   }),
   installCommands: ['npm install'],
   role: pipelineRole,
});
  

new CodePipeline(this, 'Pipeline', {
  pipelineName: ’CDKPipelineSample’,
  synth: steps,
  role: pipelineRole,
});

接続できない場合

GitHubと接続ができない場合、主に下記の原因が考えられるので、確認してみてください。(もちろん他にもあります)

CDKの定義ミス

そもそもCDKの定義が間違っていないかを確認します。(意外とARNが間違っていたり、typoが原因だったりします)

GHE側の接続制限(GHE)

GHEの場合、セキュリティ上の理由で、GitHub側で「ホワイトリストに登録したIPアドレスしか接続を許可していない」というケースがあります。

この場合、下記の対応が必要になります。

  • AWS CodePipelineをVPCに入れる
  • そのVPCから外部にアクセスするリソース(NATなど)にパブリックIP(Elastic IPなど)を割り当てる
  • 上記IPアドレスホワイトリストに追加する

細かい説明は省略しますが、この場合は [設定] - [接続] の「ホスト」タブでVPCを使用するホストを作成することで接続可能になります。

まとめ

以上、AWS CodePipelineでGitHubと認証する方法でした。

PATは簡単な判明、今回記載したような事項もあったり、GitHub Enterpriseだといろいろ制限があったりするので、その場合は今回のCodeStar接続を使うことになります。
ちょっとややこしいですが、いろいろ便利な面もあるので、ぜひ有効に活用してください。

それでは、今回はこの辺で。

【AWS CDK】GitHub ActionsでOIDCを使用してAWSと認証する方法

本日のお題

  • CD/CDなどでGitHub Actionからcdk deployコマンドを実施する際に、OIDCを使用して(IAM Userのアクセスキー&シークレットアクセスキーではなく)IAM RoleでAWSと認証を行う方法
  • 上記をAWS CDKで実装する方法

上記を実施する際に必要な手順

  1. IAM OIDC IDプロバイダーを作成する
  2. 認証用のIAM Roleを作成する
  3. 上記IAM Roleに必要なポリシーをアタッチする
  4. GitHub Actionsにてconfigure-aws-credentialsを使用してAWSと認証する

AWS CDKのコード

今回は、いきなりAWS CDKのコードを掲載します。(多分その方がわかりやすいので)

import * as cdk from 'aws-cdk-lib';
import { aws_iam } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class OIDCSampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string) {
    super(scope, id);
  
    // cdk deployAWSを実施するAWSアカウントとリージョン  
    const accountId = this.account;
    const region = this.region;
  
    const user = '<対象のGitHubユーザー名>';
    const repo = '<対象のリポジトリ名>';
    const branch = '<対象のブランチ名>';
  
    // 1. IAM OIDC IDプロバイダーを作成する  
    const oidcProvider = new aws_iam.OpenIdConnectProvider(
      this,
      'GitHubOIDCProvider',
      {
        url: 'https://token.actions.githubusercontent.com',
        clientIds: ['sts.amazonaws.com'],
      }
    );
  
    // 2. 認証用のIAM Roleを作成する
    const oidcDeployRole = new aws_iam.Role(this, 'GitHubOidcRole', {
      roleName: 'github-oidc-role',
      assumedBy: new aws_iam.FederatedPrincipal(
        oidcProvider.openIdConnectProviderArn,
        {
          StringLike: {
            'token.actions.githubusercontent.com:sub': `repo:${user}/${repo}:ref:refs/heads/${branch}`,
          },
        },
        'sts:AssumeRoleWithWebIdentity' //これを忘れるとStatementのActionが'sts:AssumeRole'となりOIDCでのAssumeRoleで使えなくなる。
      ),
    });
  
    // 3. 上記IAM Roleに必要なポリシーをアタッチする 
    const deployPolicy = new aws_iam.Policy(this, 'deployPolicy', {
      policyName: 'deployPolicy',
      statements: [
        new aws_iam.PolicyStatement({
          effect: aws_iam.Effect.ALLOW,
          actions: ['sts:AssumeRole'],
          resources: [
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-deploy-role-${accountId}-${region}`,
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-file-publishing-role-${accountId}-${region}`,
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-image-publishing-role-${accountId}-${region}`,
            `arn:aws:iam::${accountId}:role/cdk-hnb659fds-lookup-role-${accountId}-${region}`,
          ],
        }),
      ],
    });

    oidcDeployRole.attachInlinePolicy(deployPolicy);
  }
}

1. IAM OIDC IDプロバイダーを作成する

IAM OIDC IDプロバイダーを作成し、そのURLに https://token.actions.githubusercontent.com (このホスト名は固定)、クライアントIDにAWS Security Token Service (AWS STS)を指定します。

2. 認証用のIAM Roleを作成する

IAM OIDC IDプロバイダーに対し、GitHub Actionsからのフェデレーション認証の場合にAssume Roleをを許可するIAM Roleを作成します。
そしてそのURLに https://token.actions.githubusercontent.com、クライアントIDにAWS Security Token Service (AWS STS)を指定します。

またその際にGitHubのユーザー名、リポジトリ名、ブランチ名を指定し、対象のブランチでのみ認証を行うようにします。

なおこの時assumeRoleAction引数に「sts:AssumeRoleWithWebIdentity」を指定しないとOIDCでのAssumeRoleが出来ないので注意です。(「sts:AssumeRole」ではダメ)

3. 上記IAM Roleに必要なポリシーをアタッチする

上記IAM ポリシーを作成し、上記IAM Roleにアタッチします。

今回はcdk deployを実施する用のポリシーをインラインポリシーとしてアタッチしています。

ちなみに上記ポリシーの内容&sts:AssumeRole元のリソースの内容については、下記をご参照ください。(事前に cdk bootstrap が実施済であることが前提です)

speakerdeck.com

GitHub Enterprise(GHE) の場合

もちろん今回の方法はGHEでも使用できますが、GHEの場合、GitHubと以下の点が異なります。

  • ユーザー名を指定していた箇所は、GHEではorganization名を指定する
  • ホスト名は token.actions.githubusercontent.com ではなく、 「<GHEのドメイン名>/_services/token」になる

例えばorganizationが「my_org」、GHEのドメイン名が「github.hogehoge.com」の場合、CDKのソースはこうなります。(差分だけ記載)

    const oidcProvider = new aws_iam.OpenIdConnectProvider(
      this,
      'GitHubOIDCProvider',
      {
        url: 'https://github.hogehoge.com/_services/token',
        clientIds: ['sts.amazonaws.com'],
      }
    );
  
    // 2. 認証用のIAM Roleを作成する
    const oidcDeployRole = new aws_iam.Role(this, 'GitHubOidcRole', {
      roleName: 'github-oidc-role',
      assumedBy: new aws_iam.FederatedPrincipal(
        oidcProvider.openIdConnectProviderArn,
        {
          StringLike: {
            'github.hogehoge.com/_services/token:sub': `repo:my_org/${repo}:ref:refs/heads/${branch}`,
          },
        },
        'sts:AssumeRoleWithWebIdentity' 
      ),
    });
  }
}

OIDC IDプロバイダーのサムプリントについて

今までIAM OIDC IDプロバイダーを使用する場合、OIDC IDプロバイダーの検証用にサムプリント(thumbprint)を設定する必要がありましたが、今年の7/6よりサムプリントが検証に必要なくなりました。

GitHub OIDC is now using library of trusted CAs · Issue #763 · aws-actions/configure-aws-credentials · GitHub

なおAWS CDKではサムプリントは任意項目で、CloudFormationでは必須項目ですが、適当な値を入れておけば良いみたいです。

GitHub Actions

# トリガ設定は適宜変更
# 最低限しか書いてないです(cache処理などは省略)
on: 
  push:
    branches:
      - 'develop'
      - 'main'

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      # 今回は直接記載しているが、必要に応じてsecretsに登録する
      AWS_ACCOUNT_ID: "<AWSアカウント番号>"
      AWS_REGION: "ap-northeast-1"
  
    # ここでpermissionを設定するのが大事
    permissions:
      id-token: write
      contents: read
  
    steps:  
      - name: Checkout
        uses: actions/checkout@v3
  
      - name: Install CDK Dependency
        run: npm ci
  
      # 4. GitHubにてconfigure-aws-credentialsを使用してAWSと認証する  
      - name: Assume Role
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: "arn:aws:iam::${{ env.AWS_ACCOUNT_ID }}:role/github-oidc-role"
          aws-region: ${{ env.AWS_REGION }}
  
      # npm scriptに登録しておいたcdk deployコマンドを実施する
      - name: Deploy
        run: npm run deploy 

ポイントは下記2点です

permissionsにid-token: write、contents: readを設定する

これを設定しないとOIDCを使用してcdk deployが実施できない

aws-actions/configure-aws-credentials@v2を使用して、「2. 認証用のIAM Roleを作成する」で作成したIAM Roleで認証を行う

role-to-assumeに該当IAM RoleのArnを指定する

ちなみにロール名だと「credential情報がない」みたいなエラーになった。(secretsにアクセスキー&シークレットアクセスキーを登録すればOKかも)

ただ、それをしたくないからOIDCを使用しているのに、それやったら意味がない...

まとめ

以上、AWS CDKでGitHubでOIDCを使用してAWSと認証する方法でした。

アクセスキー&シークレットアクセスキーはセキュリティ的な観点からも、発行しないで済むならそれに越したことはないので、今回のような手法を積極的に取り入れて、よりセキュアになればと思います。

なお、GitHub側で「特定のIPアドレスからしか接続を許可していない」ような設定をしている場合、この方法が使えないので、その場合は残念ながらこの方法は使えません。(GHEでありがち)

では、今回はこの辺で。

【AWS】AWS Dev Day 2023&Reject Day 2023に参加しました

はじめに

少し前の話になりますが、2023/6/22(木) ~ 2022/6/23(金)にAWS Dev Day 2023が、そしてその翌日の6/24(土)にReject Day 2023が開催されました。

いろいろ仕事やら登壇やらでバタバタしてしまいましたが、参加した感想などを簡潔に書こうと思います。

なお、サクッとした感想なので、ご了承ください。

AWS Dav Day 2023

技術者向けのAWS公式カンファレンスの一つ。
AWS Summitが幅広い層をターゲットにしているのに対し、こちらは完全に開発者をメインターゲットにしたカンファレンス。

セッション内容もGitHubでの公募から、一般投票&AWS運営側の審査で採用されたもの。

なお私も応募しましたが、無事落選しました。(だからReject Day 2023で登壇しているわけですが)

aws.amazon.com

Reject Day 2023

JAWS-UGとAWS Startup Comunnityの融資メンバーの方が運営。 AWS Dev Day 2023ので採用されなかったCFPのうち、運営の方が選んだCFPを発表するという、いわゆる「救済イベント」的なイベント。

connpass.com

なおこちらのイベントにて「Mobageの監視環境をAWSで構築する話」という内容で登壇させて頂きました。

speakerdeck.com

全体的なセッションの感想

AWS CDKのセッションがかなり良かった

AWS Dev Day1日目の最初から「AWS CDKで学ぶGoFデザインパターン」及び「BLEA開発チームが学んだAWS CDKの開発プラクティス」の2つAWS CDK関連セッションがあり、その他「失敗から学んだCDK Construct Libraryを利用した開発の効率化」など、まさに業務でガッツリAWS CDKをやっている自分としては、ドンピシャな内容でした。

特にBLEA(BaseLine Environmrnt on AWS)の存在を知ることができたのが大きかったです。(今まさに業務で使用中です)

デザインパターンの話がかなり多かった

先ほどの「AWS CDKで学ぶGoFデザインパターン」を始め「既存の Web 向け課金システムをそのままに、Saga パターンでのアプリ内課金システム構築」、及び「気がついたらSagaパターンになっていた!少人数で運用するサーバレスバックエンド」など、デザインパターンのセッションが結構あり、この辺りにあまり詳しくない自分には大変勉強になる内容でした

アンチパターンを含めた事例の話がありがたかった

「失敗知識から学ぶ!クラウドアプリ設計で避けるべき事例とその対策」「Amazon S3Amazon Cognito・AWS Lambdaのアンチパターンで学ぶセキュリティ・バイ・デザイン」など、具体的な失敗&アンチパターン事例を挙げて説明をしてくれたので、非常に分かりやすかったです。
特にセキュリティ関連はインシデントに直結するだけに、注意しなければならないと強く感じました。

その他

「貴方のツールボックスにねじこみたい!ドキュメントデータベースのススメ」「AthenaとCloudWatchで始める低コストなSLOエラーバジェット監視」など、自分の業務に直結するセッションも多く、また「第一回 似てるサービス使い分け大会」など、実際のユースケースを説明してくれたりなど、非常にためになるセッションが多かったです。

参加した感想

やっぱりオフラインイベント最高!

やっぱりオフラインイベントは良いですね。
オンラインとは違い、独特の雰囲気や人との交流もありますし、なによりモチベーションの上がり方が違います。

特に遠方だとお金の問題もありますが、こういうイベントには積極的に参加すべきですし、会社側でも積極的に参加させるべきだと思います。
AWS Dev Day 2日目のゼネラルセッションでKris Howard氏が言っていた通り「早起きして、外に出て、テックイベントに参加する。それがモチベーションを上げる秘訣」でしょうね。

自分ももっと積極的にオフラインイベントに足を運ぶべきだと思いました。

次は登壇したい

やっぱりこれですね。
AWS Dev Dayに限らず、いつかAWS公式カンファレンスにて登壇したいです。
そのためには、もっとアウトプットして、知見を増やさねば...

というわけで、ちょっと急ぎ足で文書が煩雑になってしまいましたが、今回はこの辺で。

【Serverless Framework】APIキーの作成方法と注意点(後編:注意点と対策)

本日のお題

Serverless Framework(以下「SFW」)において、API GatewayAPIキーを作成する方法、及びその際の注意点について。

なお前回記載した通り、今回のお題は前編と後編の2回に分けており、この記事はその後編になります。

  • 前編:作成方法(これは前回
  • 後編:注意点と対策(今回はココ)

参考

TL; DR

  • provider.apiGateway.apiKeys 配列の末尾以外のAPIKeyを削除すると、予期しない挙動をするケースがある
  • 対策としては、enabled:false で無効化するか、resources.Resources に生CFnテンプレートを書く
    • もちろん、削除しないのが一番いい
  • APIキーの値を設定する可能性がある場合、APIキー名は設定しない方がよい。(必ずではない)

Serverless FrameworkでAPIキーを作成する際の注意点

先に結論から言うと、「apiKeys 配列で上にある(=末尾ではない)APIキーを削除する場合は注意が必要」となります。

そしてその際の挙動は「他のAPIキーに名前(=name)を設定しているものがあるかどうか」によって変わります。

ケース1:名前(=name)を設定していない場合

まず、現在のAPIキーに名前(=name)を設定しているものがない状態だったとします。

provider:
  apiGateway:
    apiKeys:
      - description: 削除するAPIキー
      - description: 削除しないAPIキー1
      - description: 削除しないAPIキー2

この状態で、一番上の「description: 削除するAPIキー」を削除してデプロイすると、最新のAPIキーは下記の状態となります。(1個ずつ上にスライドする感じ)

  • エラーは発生しない(=デプロイ自体は成功する)
  • 「description: 削除しないAPIキー1」のキーの値(=value)が、デプロイ前の「description: 削除するAPIキー」の値になっている
  • 同様に「description: 削除しないAPIキー2」のキーの値(=value)が、デプロイ前の「description: 削除しないAPIキー1」の値になっている
ケース2:名前(=name)を設定している場合

次に、現在のAPIキーに名前(=name)を設定しているものがある状態だったとします。

provider:
  apiGateway:
    apiKeys:
      - description: 削除するAPIキー
      - name: noDeletedKey1
        description: 削除しないAPIキー1
      - description: 削除しないAPIキー2

この状態で、一番上の「description: 削除するAPIキー」を削除してデプロイすると、下記の状態となります。

  • エラーが発生する(=デプロイ自体が失敗する)
  • 「ApiKey:noDeletedKey1 already exists」というエラーが表示される。(=名前の重複エラー)

対策

上記の現象は、簡潔に言うと「CloudFormation(以下CFn)の論理IDのズレ」が原因で起こります。
これの回避策としては、下記2点となります。

  1. enabled:false を設定して対応する
  2. resources.Resource セクションに生CFnテンプレートでAPIキーを定義する

なおこの「CFnの論理IDのズレ」については、この下の「参考:論理IDのズレが起こる理由」で説明しているので、興味があれば一度読んでみてください。(単にAPIキーを作るだけなら、特に知らなくても問題ありません。)

対策1:enabled:false を設定して対応する

これが一番手っ取り早いです。
enabled:false を設定すれば、そのAPI Keyは無効になりますし、API Key自体が削除されるわけではないので、先述の「論理IDのズレ」も発生しません。

ただし「使わなくなったAPIキーがずっと残り続ける」という問題があります。(許容できるなら問題なし)

対策2:resources.Resources セクションに生CFnテンプレートでAPIキーを定義する

生CFnテンプレートなら、論理IDを自分で設定でき、使わなくなったAPIキーを削除しても論理IDが変更されることはないので、これも「論理IDのズレ」は起こりません。

例えばこんな感じです。

resources:
  Resources:
    # APIキーの設定
    CfnApiGatewayApiKey1:
      Type: 'AWS::ApiGateway::ApiKey'
      Properties:
        Description: APIキー1
        StageKeys:
          # 「ApiGatewayRestApi」は、Serverless FrameworkがAPI Gatewayに対して付与する固定の論理ID
          - RestApiId: !Ref ApiGatewayRestApi
          - StageName: dev
    CfnApiGatewayApiKey2:
      Type: 'AWS::ApiGateway::ApiKey'
      Properties:
        Description: APIキー2
        StageKeys:
          - RestApiId: !Ref ApiGatewayRestApi
          - StageName: dev
  
    # 使用料プランを作成した場合、使用料プランとAPIキーの紐づけ設定
    CfnApiGatewayUsagePlanKey1:
      Type: AWS::ApiGateway::UsagePlanKey'
      Properties:
        KeyId: !Ref CfnApiGatewayApiKey1
        KeyType: 'API_KEY'
        # 「ApiGatewayUsagePlan」は、Serverless Frameworkが使用料プランに対して付与する固定の論理ID
        UsagePlanId: !Ref ApiGatewayUsagePlan
    CfnApiGatewayUsagePlanKey2:
      Type: 'AWS::ApiGateway::ApiKey'
      Properties:
        KeyId: !Ref CfnApiGatewayApiKey2
        KeyType: 'API_KEY'
        UsagePlanId: !Ref ApiGatewayUsagePlan

ただし、この方法を実施する場合、以下の問題があります。

  • APIキー(必要に応じて使用料プランとの紐づけ設定も)を自分で1から生CFnテンプレートを書く必要がある
    • 使用料プラン自体はServerless Frameworkの usagePlan で定義すればOK
  • 初回デプロイ時のみ、必ず「ApiKeyのデプロイステージが不正(=存在しない)」というエラーが出る
    • ステージ情報(AWS::ApiGateway::Deployment で設定)より先にApiKeyを作成してしまうため
    • Api Gatewayや使用量プランと異なり、ステージ情報の論理IDは毎回ランダムな文字列が設定されるため、DependsOn 設定ができない
    • 対策としては下記
      • 初回デプロイ時は、resources.Resources セクションのAPIキー、及び使用料プランとの紐づけ設定をコメントアウトしてデプロイする。(=ステージだけ先にデプロイする)
      • 2回目以降は、上記のコメント状態を解除してデプロイする(=ステージはすでに存在するので、デプロイ可能)

参考:論理IDのズレが起こる理由

Serverless Frameworkでは、serverless.ymlをCloudFormation(以下CFn)テンプレートに変換する際、apiKeys の配列に対して、上から順に「ApiGatewayApiKey1」「ApiGatewayApiKey2」「ApiGatewayApiKey3」...のように、「ApiGatewayApiKey+連番」という論理IDを付けます。

なので最初の状態は、CFnテンプレートは以下のようになります。

ApiGatewayApiKey1:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除するAPIキー
    StageKeys:
      # 「ApiGatewayRestApi」は、Serverless FrameworkがApi Gatewayに対して付与する固定の論理ID
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
ApiGatewayApiKey2:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー1
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
ApiGatewayApiKey3:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー2
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev

この状態で一番上の「description: 削除するAPIキー」を削除すると、最新のCFnテンプレートは以下のようになります。

ApiGatewayApiKey1:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー1
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
ApiGatewayApiKey2:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー2
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev

つまりCFnテンプレート的には「ApiGatewayApiKey1&2のDescriptionだけが変更された」という扱いになってしまうので、「ケース1:名前(=name)を設定していない場合 」で説明した現象が発生します。(内部的には「削除するAPIキー」自体、削除されていない)

また「ケース2:名前(=name)を設定している場合」に関してですが、一番上の「description: 削除するAPIキー」の削除前後の最新のCFnテンプレートは以下のようになります。

# 削除前
ApiGatewayApiKey1:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除するAPIキー
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
ApiGatewayApiKey2:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー1
    Name:  noDeletedKey1
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
ApiGatewayApiKey3:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー2
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
  
  
#削除後
ApiGatewayApiKey1:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー1
    Name:  noDeletedKey1
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev
ApiGatewayApiKey2:
  Type: 'AWS::ApiGateway::ApiKey'
  Properties:
    Description: 削除しないAPIキー2
    StageKeys:
      - RestApiId: !Ref ApiGatewayRestApi
      - StageName: dev

この状態でデプロイしようとすると、CFnは下記の挙動を取ります。

  • 先頭の定義から順に処理を実施する
  • 「ApiGatewayApiKey1」にNameが新規に設定されたから、既存のAPIキー(=「description: 削除するAPIキー」のAPIキー) の削除→再生成(=置き換え)を実行しようとする
  • でも前回の「ApiGatewayApiKey2」で作成した「Name: noDeletedKey1」のキーがまだ存在している
  • Nameが重複しているのでエラー

結果、名前の重複エラーとなります。

ちなみに、名前を指定した際にAPIキーの削除→再生成が実施されるのは、CFnの仕様のようです。

  • 名前を指定すると、置き換えを必要とする更新を実施できない
  • 置き換えを実施する場合、名前を変更する必要がある
    • 今回は新たに名前を指定したので「置き換えを実施する必要がある」と判断された

If you specify a name, you cannot perform updates that require replacement of this resource. You can perform updates that require no or some interruption. If you must replace the resource, specify a new name.

https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html#aws-resource-apigateway-apikey-properties

前回の記事の中で「valueを設定する場合、name は設定しないことが推奨されている」と記載しましたが、この制約があるからだと思います。

告知

今週の2023/6/24(土) (AWS Dev Day 2023の翌日)に開催される「Reject Day 2023」(主催:JAWS-UG&AWS Startup Community)において「Mobageの監視環境をAWSで構築する話」という内容でお話しさせて頂くことになりました。

connpass.com

内容ですが、YAPC::Kyoto 2023でお話しした内容をベースに、もう少し開発者よりな話をしたいと思いますので、皆様よろしくお願いします。

それでは、今回はこの辺で。

【Serverless Framework】APIキーの作成方法と注意点(前編:作成方法)

本日のお題

Serverless Framework(以下「SFW」)において、API GatewayAPIキーを作成する方法、及びその際の注意点について。

なお今回は長くなりそうなので、下記のように前編と後編の2回に分けています。

  • 前編:作成方法(今回はココ)
  • 後編:注意点と対策(これは次回)

参考

TL; DR

  • provider.apiGateway.apiKeysに定義する
  • 使用料プランごとに定義できる。(使用料プランが複数ある場合)
  • APIキーの値を設定する場合、APIキー名は設定しないことが推奨されている

APIキーを作成する方法

provider.apiGateway.apiKeys セクションに配列で定義を行うことで、作成できる。

https://www.serverless.com/framework/docs/providers/aws/events/apigateway#setting-api-keys-for-your-rest-api

provider:
  apiGateway:
    apiKeys:
      # プロパティ名省略時は、name(=APIキー名)が設定される
      - hogehogeKey
      - fugafugaKey-${opt:stage, 'dev'}
      # プロパティ名を指定することで、複数のプロパティを一括で指定できる
      - name: piyopiyoKey
        value: piyopiyoXYZ
        description: piyopiyoの用途で使用するAPIキー

使用料プラン(usagePlan)を複数作成した場合、使用料プラン単位で紐づけるAPIキーを作成できる。(使用料プランが1つしかない場合は、自動でその使用料プランにAPIキーが紐づけられる)

provider:
  apiGateway:
    apiKeys:
      - normal:
          - normalKey
      - primary:
          - primaryKey
    usagePlan:
      - normal:
          quota:
            limit: 1000
            offset: 2
            period: MONTH
          throttle:
            burstLimit: 20
            rateLimit: 10
      - primary:
          quota:
            limit: 10000
            offset: 1
            period: MONTH
          throttle:
            burstLimit: 200
            rateLimit: 100

なお、apiKeysで設定できるプロパティ名は以下の通り。(すべて任意項目ですが、どれか一つは指定する必要があります)

プロパティ名 説明 未指定時の値 備考
name APIキーの名前 自動生成 実際のAPIキーの値(「x-api-key」ヘッダで設定する値)ではない。
value 実際のAPIキーの値(「x-api-key」ヘッダで設定する値) 自動生成 任意の値にしたい場合に設定する。
description APIキーの説明 なし
enabled APIキーの有効/無効の設定 true(=有効) enabled:falseのキーでの認証は不可
customerId AWS Marketplaceの顧客ID なし AWS Marketplaceと統合する場合に設定する

なお、注意点は下記になります

  • valueを設定する場合、name は設定しないことが推奨されている
  • valueはセンシティブな値なので、取り扱いには注意。*1

これを設定することで、CloudFormationの AWS::ApiGateway::ApiKey、及び(使用料プランを作成していれば) AWS::ApiGateway::UsagePlanKey が作成されます。

告知

来週の2023/6/24(土) (AWS Dev Day 2023の翌日)に開催される「Reject Day 2023」(主催:JAWS-UG&AWS Startup Community)において「Mobageの監視環境をAWSで構築する話」という内容でお話しさせて頂くことになりました。

connpass.com

内容ですが、YAPC::Kyoto 2023でお話しした内容をベースに、もう少し開発者よりな話をしたいと思いますので、皆様よろしくお願いします。

今回はちょっと短めですが、後編の「注意点と対策」が長くなりそうなので、今回はこの辺で。

*1:この辺は人やシステムによって考え方があると思うので、ここではそこまで言及しません

【Serverless Framework】API Gatewayのログを定義する

本日のお題

Serverless Framework(以下「SFW」)において、API Gateway関連ログの出力方法について。 *1

参考

やり方

provider.logsセクション内の各通信方式(httpApi, restApi, websocket)にAPI Gateway関連ログのプロパティを定義すればOK。(他にもframeworkLambdaという、「AWS Lambda-backed カスタムリソースのログ」の設定項目があるが、今回は割愛)

https://www.serverless.com/framework/docs/providers/aws/guide/serverless.yml#logs

各通信方式毎に設定可能なプロパティが決まっているので、使用する通信方式に沿ったプロパティを定義する。(各プロパティは下表を参照)

なおこの項目は、API Gateway マネジメントコンソールの「ステージ」 - 「ログ/トレース」タブの設定項目に対応しています。*2

プロパティ名 説明 「ログ/トレース」タブの該当項目 httpApi restApi websocket 備考
accessLogging アクセスログを出力する アクセスログの有効化」 × 「/aws/api-gateway/<API名>-」ロググループにログが出力される
format HTTPログやアクセスログのフォーマットをcontext形式で設定する 「ログの形式」 ・RestApi及びwebsocketの場合、accessLoggingがtrueの場合のみ反映される
・context形式については下記URLを参照(※1)
・未指定時は自動設定される
executionLogging 簡単な実行ログを出力する - × API-Gateway-Execution-Logs_/」ロググループにログが出力される
level ログを出力するイベントをINFO(全部), ERROR(エラーのみ), OFF(出力しない)から設定する CloudWatchログ × INFOを選択した場合、エラーイベントもロギングされる
fullExecutionData 実行ログに詳細情報が追加される - × ・リクエスト情報やLambdaへのリクエスト、Lambdaからのレスポンス情報も記載される。
・リクエストbodyもロギングされるので、扱う情報によっては注意が必要
role API GatewayのログをCloudWatchに出力する際に使用するIAM RoleのARN - × ×
roleManagedExternally API GatewayのログをCloudWatchに出力する際に使用するIAM RoleをSFW外部で管理するかどうか - × × デフォルトはfalse

※1:https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference

# 設定例
provider
  logs
    restApi
      accessLogging: true
      executionLogging: true
      level: INFO
      fullExecutionData: false      

少々短めですが、今回はこの辺で。

*1:Lambda関数のログは、SFWでデプロイすれば、JavaScriptのconsole.logなど各種ログ出力関数で出力できます

*2:X-Rayトレース」はprovider.tracingセクションで設定します

【AWS】AWS請求ポリシーの更新に対応する

今回のお話

AWSの請求関連のポリシーについて、今年(2023年)7月6日より適用される新しいポリシーに更新する方法

詳細について

AWSで請求関連の情報*1を扱うIAMポリシーについて、今年の1月10日に新しいものがローンチされ、いくつかのポリシーが追加/廃止されます。

参考(AWS公式ブログ):https://aws.amazon.com/jp/blogs/news/changes-to-aws-billing-cost-management-and-account-consoles-permissions/

これらの新しいポリシーは今年の7月6日より適用されるので、それまでに新しいIAMポリシーを適用する必要があります。

実はAWS公式からもこの案内がメールが来ているんですが、リンク先の公式ブログが全て英語なので「さっぱりわからん」「日本語でおk」と思われた人もいるのでは?と思います。(ちなみに「参考」のリンクは日本語訳されたものになっています)

そこで、今回はこの新しいポリシーの適用方法を説明したいと思います。

前提

請求情報の閲覧において、下記を適用している場合は、特に対処は不要です。(下記の場合には、ポリシー更新が自動で反映されます)

  • ルートユーザーでのみ閲覧している(=ほかのユーザに閲覧権限を付与していない)
  • 閲覧ポリシーとして「Billing」(=AWSマネージドポリシー)をアタッチしている

もしカスタムポリシーを適用している場合のみ、処理が必要です。

また、以降の内容にある

  • 新ポリシーは7月6日より適用
  • 旧ポリシーも7月5日までは有効

の記載は「2023年3月7日 午前4時(=日本時間)より前に作成されたアカウント」に対するものです。(ほとんどどはこれに該当すると思います)

2023年3月7日 午前4時(=日本時間)以降に作成されたアカウントについては、新ポリシーを即座に反映する必要があります*2

IAMポリシー/SCPの更新

ポリシーの更新ですが、とりあえず新ポリシーに対応するだけなら、公式ブログの下記ポリシーを適用すればよいです。(設定をカスタマイズしたい場合はその都度設定してください)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ThesePermissionsWillHaveNoEffectTillEndOfMigration",
            "Effect": "Allow",
            "Action": [
                "ce:Get*",
                "ce:Describe*",
                "ce:List*",
                "account:GetAccountInformation",
                "billing:Get*",
                "payments:List*",
                "payments:Get*",
                "tax:List*",
                "tax:Get*",
                "consolidatedbilling:Get*",
                "consolidatedbilling:List*",
                "invoicing:List*",
                "invoicing:Get*",
                "cur:Get*",
                "cur:Validate*",
                "freetier:Get*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ThisPermissionWillContinueProvidingAccessAsNormal",
            "Effect": "Allow",
            "Action": "aws-portal:ViewBilling",
            "Resource": "*"
        }
    ]
}

大きな変更点としては下表の通りなので、これに対応できればOKです。(各サービスプレフィックスの用途については、公式ブログを参照)

変更内容 サービスプレフィックス アクション 備考
追加 consolidatedbilling 全アクション
追加 freetier 全アクション
追加 invoicing 全アクション
追加 payments 全アクション
削除 aws-portal 全アクション
削除 purchase-orders ViewPurchaseOrdersおよびModifyPurchaseOrders これ以外のアクションは引き続き継続

なお注意点として、

  • 廃止アクションも廃止までは今まで通りのアクセス許可を提供する
  • 新アクションは廃止ポリシーを削除しないと有効にならない

というのがあります。

なので新ポリシーをテストしつつ請求情報へのアクセス権限が失われないように、期限が来るまでは新旧両方のポリシーを設定しておくのがいいのではないかと思います。

なお上記はIAMポリシー前提で記載しましたが、AWS Orgを使用してサービスコトロールポリシー(SCP)を使用している場合、SCPでも同様の対応も必要です。

影響を受けるポリシーを確認する

なお「更新が必要かどうか」を確認するためのツールとして、「Affected Policies」というツールが新たに用意されています。

このツールはマネジメントコンソールから「請求」のホーム画面に移動し、左のリストの最下段にある「アクセス許可 - Affected Policies」から開けます。

詳細な操作手順は公式ブログで丁寧に説明されているので、ここでは省略しますが、下表の操作が可能なようです

項目 説明 備考
新しい IAM アクションを管理 新ポリシーを即座に反映したり、旧ポリシーに戻したりできる AWS Orgを使用している場合、対象アカウントを選択可能
更新するポリシー 更新が必要なポリシーがある場合、該当ポリシーが表示される 更新自体はリンク先のIAMマネジメントコンソールから行う

まとめ

以上、AWSの請求ポリシー更新についてでした。

AWS公式ブログが和訳されたのもあって、だいぶ原文よりは理解しやすくなりましたが、それでもちょっとややこしい部分ではあるので、理解の助けになればと思います。

あと、ポリシー更新は忘れずに行ってください。(忘れると7/6以降、請求情報が閲覧不可になってしまうので...)

告知

少し先ですが、2023/6/24(土) 開催の「Reject Day 2023」(主催:JAWS-UG&AWS Startup Community)において、「Mobageの監視環境をAWSで構築する話」という内容でお話しさせて頂くことになりました。

connpass.com

内容ですが、YAPC::Kyoto 2023でお話しした内容をベースに、もう少し開発者よりな話をしたいと思いますので、よろしくお願いします。

それでは、今回はこの辺で。

*1:請求書/支払いの閲覧など、基本ルートユーザーやAWS Organizations(以下「AWS Org」)のマスターアカウントでしか扱えない情報

*2:旧ポリシーの延長申請は出せるようです