AWS Secrets Managerによる機密情報の管理

アイデンティティ/アクセス管理に使用されるパスワードや秘密鍵などの機密情報=シークレットの適切な管理方法は、情報セキュリティマネジメントを考える上で重要なテーマです。 シークレットの機密性を維持することは必要不可欠である一方、適切に保護するためにはある程度の労力を必要とします。
Amazon Web Services (AWS)はシークレット管理サービスとして「AWS Secrets Manager」を提供しており、シークレット管理に要する手間を大きく軽減します。
このコラムでは、AWS Secrets Managerの概要について解説すると共に、AWS Secrets Managerの基本的な使い方を解説します。

AWS Secrets Managerについて

AWS Secrets Managerとは何か

AWS Secrets ManagerはAWSのシークレット管理サービスであり、AWS Secrets Managerを利用することによってシークレットの保管・更新・監視などの手間を軽減することができます。

データベース管理システムにアクセスするソフトウェアなどは、アクセスに必要なユーザー名やパスワードなどを外部の設定ファイルから読み込んだり、環境変数やコマンドライン引数からパラメーターとして受け取ったりすることによってシークレットを取得します。

また、あまり良い方法ではありませんが、ソフトウェアのソースコードなどにシークレットが直接記述されることもあります。

多くの場合、これらの方法ではシークレットはプレーンテキストとして保存されるため、設定ファイルのパーミッションが適切に設定されていないなどの原因により、正当な権限を持たない人によるシークレットの表示や変更ができてしまう恐れがあります。

一方、AWS Secrets Managerを利用する場合、AWS Secrets Managerがシークレットを保管し、シークレットを必要とするソフトウェアなどはAPIを通じてAWS Secrets Managerからシークレットを取得するため、シークレットをプレーンテキストで保存する必要はありません。

なお、AWS Secrets ManagerのAPIへのアクセスはAWSのアイデンティティ/アクセス管理サービスである「AWS Identity Access Management」(IAM)によって管理されます。

仮想マシンサービスの「Amazon EC2」やコード実行環境サービスの「AWS Lambda」からシークレットを利用する場合、IAMの「ロール」と呼ばれるしくみを用いることによってAWSのアクセスキーIDとシークレットアクセスキーの保存も不要となり、さらに安全にシークレットを保護することができます。

Amazon Key Management Serviceとの違い

AWSは暗号化キー管理サービスとして「AWS Key Management Service」(KMS)を提供しており、AWS KMSを利用することによって暗号化キーの保管・使用・更新などの手間を軽減することができます。

AWS Secrets ManagerとAWS KMSはいずれもシークレットや暗号化キーなどの機密情報を保管する点では共通していますが、それぞれ用途が次のように異なります。

まず、AWS Secrets Managerは前述のようにデータベース管理システムに接続するための認証情報などのシークレットを保管し、シークレットそのものを取得するためのAPIを提供します。

一方、AWS KMSは任意のデータの暗号化や復号化に使用するための暗号化キーを保管し、データの暗号化や復号化を行うためのAPIを提供します。

AWS KMSが保管する暗号化キー自体を第三者が取得できないことはもちろん、暗号化キーを作成したAWS KMSの利用者やAWSの従業員でさえも取得することはできず、AWS KMSの利用者は暗号化と復号化のAPIを用いて暗号化キーを使用することのみを許可されます。

したがって、シークレットを保管してソフトウェアなどから取得したい場合はAWS Secrets Managerを利用し、暗号化と復号化をソフトウェアなどから行いたい場合はAWS KMSを利用するのが一般的なユースケースとなります。

Amazon Secrets Managerの使い方

前段では、AWS Secrets Managerの概要について解説しました。

AWS Secrets Managerを利用することによって、設定ファイルや環境変数などの代わりにAPIを使ってプログラムからシークレットを取得することができます。

以下、AWS Secrets Managerの基本的な使い方として、AWS Secrets Managerからシークレットを作成する方法とAWS Lambdaからシークレットを取得する方法の2点を紹介します。

シークレットの作成

シークレットを作成するには、AWSのWebコンソールにアクセスし、ページ内の検索窓に「Secrets Manager」と入力するなどしてAWS Secrets Managerのページへ移動します。

AWS Secrets Managerのページが表示された後、ページの右側にある「新しいシークレットを保存する」ボタンをクリックします。

シークレットの作成ページが表示された後、シークレットの種類として「その他のシークレット」を選択し、シークレット/値のペアとしてそれぞれ「secret」と「ここにシークレットが入ります」を入力してからページの下側にある「次」ボタンをクリックします。

シークレットの名前と説明の入力ページが表示された後、シークレットの名前として「MySecret」を入力してからページの下側にある「次」ボタンをクリックします。

ローテーションの設定ページが表示された後、ページの下側にある「次」ボタンをクリックします。

レビューページが表示された後、ページの下側にある「保存」ボタンをクリックします。

シークレットの一覧ページが表示された後、作成されたシークレットの名前をクリックします。

シークレットの詳細ページが表示された後、シークレットの詳細のセクションに含まれる「シークレットのARN」を控えます。

AWS Lambdaからのシークレットの取得

AWSのデプロイサービスである「AWS CloudFormation」を使用して、シークレットを取得する権限を持つAWS Lambda関数と権限を持たないAWS Lambda関数の2つを作成します。

AWS CloudFormationテンプレートとして下記の内容のYAMLファイルを作成して「template.yaml」などの名前で保存します。

AWSTemplateFormatVersion: 2010-09-09
Resources:
  AuthorizedLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: AllowGetSecretValue
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: secretsmanager:GetSecretValue
                Resource: (ここにシークレットのARNが入ります)
  AuthorizedFunction: 
    Type: AWS::Lambda::Function
    Properties: 
      Handler: index.handler
      Runtime: nodejs12.x
      Code:
        ZipFile: |
          const AWS = require('aws-sdk')

          exports.handler = async function (event, context) {
            try {
              const secretsManager = new AWS.SecretsManager({
                region: 'ap-northeast-1',
              })

              const response = await secretsManager.getSecretValue({
                SecretId: 'MySecret',
              }).promise()

              return JSON.stringify({response}, null, 2)
            } catch (err) {
              return JSON.stringify({err}, null, 2)
            }
          }
      MemorySize: 128
      Timeout: 10
      Role: !GetAtt AuthorizedLambdaExecutionRole.Arn
  UnauthorizedLambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
  UnauthorizedFunction: 
    Type: AWS::Lambda::Function
    Properties: 
      Handler: index.handler
      Runtime: nodejs12.x
      Code:
        ZipFile: |
          const AWS = require('aws-sdk')

          exports.handler = async function (event, context) {
            try {
              const secretsManager = new AWS.SecretsManager({
                region: 'ap-northeast-1',
              })

              const response = await secretsManager.getSecretValue({
                SecretId: 'MySecret',
              }).promise()

              return JSON.stringify({response}, null, 2)
            } catch (err) {
              return JSON.stringify({err}, null, 2)
            }
          }
      MemorySize: 128
      Timeout: 10
      Role: !GetAtt UnauthorizedLambdaExecutionRole.Arn

AWS CloudFormationテンプレートの作成後、ターミナルを起動して下記のコマンドを実行します。

aws cloudformation deploy --stack SecretsManager --template template.yaml --capabilities CAPABILITY_IAM

デプロイが完了した後、下記のコマンドを実行してAWS Lambda関数の名前を取得します。

aws cloudformation describe-stack-resource --stack SecretsManager --logical-resource-id AuthorizedFunction --query StackResourceDetail.PhysicalResourceId
aws cloudformation describe-stack-resource --stack SecretsManager --logical-resource-id UnauthorizedFunction --query StackResourceDetail.PhysicalResourceId

AWS Lambda関数の名前を取得した後、下記のコマンドを実行してAWS Lambda関数を実行します。

aws lambda invoke --function-name (ここにAuthorizedFunctionの名前が入ります) outfile-authorized
aws lambda invoke --function-name (ここにUnauthorizedFunctionの名前が入ります) outfile-unauthorized

outfile-authorizedをエディターなどで開き、シークレットを取得できていることを確認します。
なお、下記では見やすいようにJSONデータを整形しています。

{
  "response": {
    "ARN": "arn:aws:secretsmanager:ap-northeast-1:(ここにAccountIDが入ります):secret:MySecret-CR3uz6",
    "Name": "MySecret",
    "VersionId": "838f159f-7881-4079-80d1-78a1144197ac",
    "SecretString": "{\"secret\":\"ここにシークレットが入ります\"}",
    "VersionStages": ["AWSCURRENT"],
    "CreatedDate": "2020-03-08T04:19:40.429Z"
  }
}

同様に、outfile-auauthorizedをエディターなどで開き、シークレットを取得できていないことを確認します。
なお、下記では見やすいようにJSONデータを整形しています。

{
  "err": {
    "message": "User: arn:aws:sts::(ここにAccountIDが入ります):assumed-role/SecretsManager-UnauthorizedLambdaExecutionRole-H5EQ4OUKBP5J/SecretsManager-UnauthorizedFunction-1FIUC6JDENF75 is not authorized to perform: secretsmanager:GetSecretValue on resource: arn:aws:secretsmanager:ap-northeast-1:(ここにAccountIDが入ります):secret:MySecret-CR3uz6",
    "code": "AccessDeniedException",
    "time": "2020-03-08T05:24:00.714Z",
    "requestId": "46ea7ea6-c742-4f3c-8c6d-b85df5771094",
    "statusCode": 400,
    "retryable": false,
    "retryDelay": 80.71079263580422
  }
}

クリーンアップ

AWS Lambda関数を削除するには、下記のコマンドを実行します。

aws cloudformation delete-stack --stack SecretsManager

シークレットを削除するには、シークレットの詳細ページの右上にある「アクション」ボタンをクリックして表示されるメニューの中から「シークレットを削除する」を選んでクリックします。

シークレットの削除ダイアログが表示された後、待ち時間として「7」日を入力してからダイアログの右下にある「スケジュール削除」ボタンをクリックします。

シークレットの詳細ページに「このシークレットは【ここに日付が入ります】に削除されました」と表示されることを確認します。

おわりに

システムを構築・運用する上でシークレットの管理は重要なのですが、適切に管理することは容易なことではありません。

AWS Secrets Managerはシークレットを管理する上で有用なサービスですが、たとえばAPI経由で取得したシークレットをファイルに保存したり、環境変数やコマンドライン引数などのパラメーターとして使用したりするなど、不適切な取り扱いをした場合はメリットが半減してしまいます。

このような状態に陥らないためにも、サービスを利用して何ができるのかという点に加え、そのサービスをなぜ利用する必要があるのかという点についても十分に考えることが求められます。

24時間365日対応可能なクラウド監視・運用代行で、あなたをシステム運用から解放します!

移行準備段階で知っておくべきAmazon Web Servicesの
サービスを学び、具体的にクラウド検討を考える!

ネットワークからクラウドまでトータルサポート!!
NTT東日本のクラウド導入・運用サービスを確認してください!!

Amazon Web Services(AWS)、Microsoft Azureの
導入支援サービスのご相談、お問い合わせをお待ちしております。

ページ上部へ戻る