今日はなにの日。

気になったこと勉強になったことのメモ。

今日は、LambdaとSecrets ManagerとVPCエンドポイントの日。

目次

とある日

RDSのMySQLユーザパスワードをLambdaやプログラムに直接書かないで、セキュリティを向上させたいため、Secrets Managerを利用した話。

LamabdaからRDSへの接続する際のパスワードをSecrets Managerで取得する際の方法でVPCエンドポイントを使った記事があまりなかったので、自分の構築を備忘録として残します。

参考にした記事など各所にあるので参考にしてください。

はじめに

この記事は、2021年6月8日を基準として構築を行っています。

想定読者

  • VPCエンドポイントを使用してLambdaからSecrets Managerへのアクセス接続を目的としている方

記事の構成

  • AWS全体構成図
  • LambdaとSecrets ManagerのVPCエンドポイント構築手順
  • LambdaとSecrets Managerでハマったポイント

解説しないこと

AWS構成図

赤点線で囲んだところの話のみします。

API GatewayとS3の話はまた別のお話で。

f:id:Updraft:20210610131608p:plain

構築手順

1. Secrets Managerを作成

https://blog.serverworks.co.jp/secrets-manager-rotation

ステップ2までは参考にしても問題ない。

2. サンプルコードを追加&修正

作成したシークレットを選択して、一番下まで行くと、

サンプルコードがあるのでコピーします。

下記のようなソースコード

Lambdaに、適当なファイルを作成してペーストします。

import boto3
import base64
from botocore.exceptions import ClientError


def get_secret():

    secret_name = "MY/SECRET/NAME"
    region_name = "us-west-2"

    # Create a Secrets Manager client
    session = boto3.session.Session()
    client = session.client(
        service_name='secretsmanager',
        region_name=region_name
    )

    # In this sample we only handle the specific exceptions for the 'GetSecretValue' API.
    # See https://docs.aws.Amazon.com/secretsmanager/latest/apireference/API_GetSecretValue.html
    # We rethrow the exception by default.

    try:
        get_secret_value_response = client.get_secret_value(
            SecretId=secret_name
        )
    except ClientError as e:
        if e.response['Error']['Code'] == 'DecryptionFailureException':
            # Secrets Manager can't decrypt the protected secret text using the provided KMS key.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        Elif e.response['Error']['Code'] == 'InternalServiceErrorException':
            # An error occurred on the server side.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        Elif e.response['Error']['Code'] == 'InvalidParameterException':
            # You provided an invalid value for a parameter.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        Elif e.response['Error']['Code'] == 'InvalidRequestException':
            # You provided a parameter value that is not valid for the current state of the resource.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
        Elif e.response['Error']['Code'] == 'ResourceNotFoundException':
            # We can't find the resource that you asked for.
            # Deal with the exception here, and/or rethrow at your discretion.
            raise e
    else:
        # Decrypts secret using the associated KMS CMK.
        # Depending on whether the secret is a string or binary, one of these fields will be populated.
        if 'SecretString' in get_secret_value_response:
            secret = get_secret_value_response['SecretString']
        else:
            decoded_binary_secret = base64.b64decode(get_secret_value_response['SecretBinary'])

    # Your code goes here.

自分は、LambdaにSecretsManager.pyで作成しました。

ハンドラーに指定してあるファイルに、SecretsManager.pyをimportしました。

importしたら、get_secret()を呼び出せれば初期配置完了です。

次は、プログラム一部を変更します。

下記の記事で上記のプログラムを拡張してデータベースに接続するプログラムまであるのですが、

自分はそこは別ファイルに切り出していたでの、必要な部分はpasswordを取り出すところだけなので、

下記のようなコードを追加。

aws.amazon.com

追加場所は、上記のコードの50番あたり。

 if 'SecretString' in get_secret_value_response:
                secret = get_secret_value_response['SecretString']
                j = json.loads(secret)
                password = j['password']

あとは、return passwordで返り値でパスワードが受け取れます。

これでLambdaのプログラム修正は完成しました。

3. VPCエンドポイント作成

Lambdaを配置してあるVPCを選択。

docs.aws.amazon.com

  1. [Security groups] で、セキュリティグループを選択または作成します。

    セキュリティグループを使用すると、エンドポイントへのアクセスをコントロールできます。これは、ファイアウォールを使用するのに似ています。

セキュリティグループは、Lambdaに付与してあるセキュリティグループを許可すればいいと思います。

4. Lambda IAMポリシーアタッチ

LambdaをVPC配置したときにセキュリティグループなどは追加済みです。

SecretsManagerへのアクセスを許可するためポリシーを付与。

最低限のポリシーsecretsmanager:GetSecretValueだけ許可すれば良いと思います。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "*"
        }
    ]
}

5. Secrets Managerリソースポリシー追加

リソースポリシーで、VPCエンドポイントのアクセスを許可する。

{
    "Id": "example-policy-1",
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "RestrictGetSecretValueoperation",
            "Effect": "Deny",
            "Principal": "*",
            "Action": ["secretsmanager:GetSecretValue"],
            "Resource": "*",
            "Condition": {
                "StringNotEquals": {
                    "aws:sourceVpce": "vpce-1234a5678b9012c"
                }
            }
        }

    ]
}

VPC エンドポイントで Secrets Manager を使用する - AWS Secrets Manager

6. Lambdaから接続テスト

Lambdaからのテストを実行する。

無事に、データベースにアクセスができたら成功となる。

Timeoutになる場合は、セキュリティ関連でアクセスができていないと思われます。

ハマったポイント

LambdaからSecrets Managerのアクセスの方法がわからないかった。

リソースポリシーとかを設定すればアクセスできると思っていたが。

VPC内に配置してあるLambdaは、VPCの外にあるSecrets Managerにアクセスできないので、VPCで通信をできるよに設定する必要があった。

その場合は、下記の記事で解説されてます。

stackoverflow.com

元記事は、英語なのでGoogle翻訳を使用しています。

であなたの関数を置き、プライベートサブネットと使用NATゲートウェイ/インスタンスをインターネットへのアクセスを提供するために、正しく設定ルートテーブルで、その結果、へSecrets Manager。

プライベート サブネットで Secrets Manager の VPC インターフェース エンドポイントを設定します。このようにして、ラムダ関数はSecrets Manager、インターネットを必要とせずに、エンドポイントを使用して に接続できます。

1つ目の方法は、下記の記事でやってる内容だと思います。

AWS Secrets Managerに保存されたRDSのログインパスワードをローテーションしてみた件 - サーバーワークスエンジニアブログ

自分は2つ目の方法で行いました。

VPC内のLambdaからSecrets Managerのアクセス経路がわからなかったため苦戦した。

AWSは、セキュリティにつまずく事が多いですね。

それも、アクセスできないからエラーが返ってくる場合と、返ってこない場合があるため前者ならいいが、後者だとトラブル箇所の見極めが難しい。