4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS CDK + TypeScript + Lambdaを使用してKMSで暗号化した値を取得し復号する

Posted at

はじめに

AWS CDK + TypeScript + Lambdaを使用してKMSで暗号化された値を取得して復号する記事があまり見当たらなかったので、備忘録として残しておきます。

前提

こちらの記事は「AWS CLI」、「AWS CDK」のインストールを前提としており、私はそれぞれ以下のバージョンを使用しています。

  • AWS CLI・・・2.7.21
  • AWS CDK Toolkit・・・2.63.1

OSについてはM1 macOS Montereyです。

また、パラメータストアで任意のSecureStringパラメータが設定されているものとします。

AWS CDKでLambda関数を作成する

まずディレクトリを作成し、プロジェクトを作成していきます。

$ mkdir kms && cd kms
$ cdk init app --language typescript

以下のファイルが作成されているのが確認できるかと思います。

.
├── README.md
├── bin
├── cdk.json
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json

次に、lambdaディレクトリを作成し、lambda関数を作成していきます。

$ mkdir lambda && cd lambda
$ touch index.ts

サンプル関数として、kmsという文字列を出力する処理を書いておきます。

index.ts
exports.handler = async function () {
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: "kms",
  };
}

lib/kms-stack.tsに移り、既存のコードを削除したのち、今作成したindex.tsを使用するコードに変更します。

$ cd ../lib/kms-stack.ts
kms-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
- // import * as sqs from 'aws-cdk-lib/aws-sqs';
+ import * as lambda from "aws-cdk-lib/aws-lambda";

export class SampleStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
-    // The code that defines your stack goes here
-
-    // example resource
-    // const queue = new sqs.Queue(this, 'SampleQueue', {
-    //   visibilityTimeout: cdk.Duration.seconds(300)
-    // });
+    const lambdaFunction = new lambda.Function(this, "IndexHandler", {
+      runtime: lambda.Runtime.NODEJS_16_X, // ランタイム
+      code: lambda.Code.fromAsset("lambda"), // lambdaディレクトリからコードを読み込む
+      handler: "index.handler", // ハンドラ。ここではlambda/index.tsのファイル名を指す
+    });
  }
}

ここまで来たら、関数のデプロイをしていきます。
まずはcdk bootstrapコマンドでAWSデプロイ中に使用される特定のリソースをプロビジョニングします。

$ cdk bootstrap

TSファイルをコンパイルしたのち、デプロイします。

$ npm run build

$ cdk deploy

試しに関数のテストをしてみます。
スクリーンショット 0005-03-12 21.28.47.png

無事Lambda関数が実行されました🎉

KMSで暗号化された値を取得する

KMSで暗号化された値を取得するにはいくつかやり方がありますが、今回は「Lambda 拡張機能」を使用してみます。

まずlib/kms-stack.tsを編集します。

kms-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
+ import { PolicyStatement } from 'aws-cdk-lib/aws-iam';

export class KmsStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

+    const layer = lambda.LayerVersion.fromLayerVersionArn(
+      this,
+      'layer',
+      'arn:aws:lambda:ap-northeast-1:133490724326:layer:AWS-Parameters-and-Secrets-Lambda-Extension:2',
+    );

    const lambdaFunction = new lambda.Function(this, 'IndexHandler', {
      runtime: lambda.Runtime.NODEJS_16_X,
      code: lambda.Code.fromAsset('lambda'),
      handler: 'index.handler',
+     layers: [layer],
    });

+    lambdaFunction.addToRolePolicy(
+      new PolicyStatement({
+        resources: [
+          `arn:aws:kms:{リージョン}:{アカウントID}:key/{KMSのキーID}`,
+          `arn:aws:ssm:{リージョン}:{アカウントID}:parameter/{パラメータストアのパラメータの名前}`,
+        ],
+        actions: ['kms:Decrypt', 'ssm:GetParametersByPath', 'ssm:GetParameter'],
+      })
+    );
  }
}

まずlayerを定義します。
以下でAWS Parameters and Secrets Lambda ExtensionのARNを指定しています。
AWS Parameters and Secrets Lambda ExtensionのARNについてはこちらを参照。

kms-stack.ts
const layer = lambda.LayerVersion.fromLayerVersionArn(
  this,
  'layer',
  'arn:aws:lambda:ap-northeast-1:133490724326:layer:AWS-Parameters-and-Secrets-Lambda-Extension:2',
);

次にLambda関数にレイヤーを追加します。

kms-stack.ts
const lambdaFunction = new lambda.Function(this, 'IndexHandler', {
  runtime: lambda.Runtime.NODEJS_16_X,
  code: lambda.Code.fromAsset('lambda'),
  handler: 'index.handler',
  layers: [layer],
});

また、暗号化された値を取得し復号するためのポリシーをIAMロールにアタッチします。

kms-stack.ts
lambdaFunction.addToRolePolicy(
  new PolicyStatement({
    resources: [
      `arn:aws:kms:{リージョン}:{アカウントID}:key/{KMSのキーID}`,
      `arn:aws:ssm:{リージョン}:{アカウントID}:parameter/{パラメータストアのパラメータの名前}`,
    ],
    actions: ['kms:Decrypt', 'ssm:GetParametersByPath', 'ssm:GetParameter'],
  })
);

簡単のためリソース等をハードコーディングしていますが本来は環境変数等で扱うべきです。

lambda/index.tsに移ります。
暗号化された値を取得するのにlocalhostにリクエスト必要があるため、lambdaディレクトリでaxiosをインストールしておきます。

lambda/
$ npm init -y
$ npm i axios

lambda/index.tsで実際に暗号化された値を取得する処理を追加します。

index.ts
+ import axios from 'axios';

exports.handler = async function () {
-  return {
-    statusCode: 200,
-    headers: { "Content-Type": "text/plain" },
-    body: "kms",
-  };
+  const options = {
+    headers: {
+      'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN,
+    },
+    method: 'GET',
+  }

+  const endpoint = encodeURI('http://localhost:2773/systemsmanager/parameters/get/?name={パラメータストアのパラメータの名前}&withDecryption=true');
+
+  try {
+    const res = await axios.get(endpoint, options);
+    
+     return {
+       statusCode: 200,
+       headers: { "Content-Type": "text/plain" },
+       body: res.data.Parameter.Value,
+     };
+  } catch (error) {
+    console.log(error);
+  }
}

基本的にドキュメントに記載されていますが、いくつかポイントを抑えておきます。

  • ヘッダーにX-Aws-Parameters-Secrets-Tokenを設定

拡張キャッシュからパラメータを取得するには、GET リクエストのヘッダーに X-Aws-Parameters-Secrets-Token 参照を含める必要があります。トークンを AWS_SESSION_TOKEN に設定します。
https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/ps-integration-lambda-extensions.html

kms-stack.ts
+  const options = {
+    headers: {
+      'X-Aws-Parameters-Secrets-Token': process.env.AWS_SESSION_TOKEN,
+    },
+    method: 'GET',
+  }
  • localhostの2773ポートにGETリクエスト

GET リクエストに localhost を使用します。拡張機能は、localhost ポート 2773 に送信されます。

  • パラメータストアのパラメータ名はurlエンコード

GET 呼び出しを使用する場合、特殊文字を保存するために、パラメータ値を HTTP 用にエンコードする必要があります。

  • withDecryptionをtrueに設定

withDecryptionをtrueに設定することに関しては明示的に設定する必要がある旨の記載がありませんが、こちらのパラメータがないと値を復号できません。

kms-stack.ts
+  const endpoint = encodeURI('http://localhost:2773/systemsmanager/parameters/get/?name={パラメータストアのパラメータの名前}&withDecryption=true');

それではデプロイしてみます。

$ npm run build

$ cdk deploy

今回SecureStringの値を「暗号化された値」という文字列にしています。

関数のテストを実行

スクリーンショット 0005-03-12 21.39.35.png

無事暗号化された値が取得できました🎉

個人的にlocalhostにリクエストを送る際にwithDecryptionをtrueにしないといけないという点がハマりポイントでした(ドキュメントは隅々まで見ないといけないですね)

最後に

GoQSystemでは一緒に働いてくれる仲間を募集中です!
ご興味がある方は以下リンクよりご確認ください。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?