本連載では、以下のイメージの構成にあるAWSリソース基盤自動化環境の構築を実践しています。

本連載で構築していく基盤自動化環境のイメージ

前回は、ECSタスク定義を行うCloudFormationテンプレートを実装しました。今回はタスク定義したコンテナで実行されるアプリケーションが使用するAWSリソースへのアクセスポリシーを定義し、前回作成したECSタスクのIAMロールへアタッチするCloudFormationテンプレートを作成します。

なお、実際のソースコードはGitHub上にコミットしています。以降のソースコードでは本質的でない記述を一部省略しているので、実行コードを作成する場合は、必要に応じて適宜GitHub上のソースコードも参照してください。

ECSタスクロール定義スタック構築テンプレート

第34回でも述べましたが、本連載でこれまで実装してきたアプリケーションでは以下のようにAWSリソースを利用しています。

  • ALBを経由したバックエンドサービスの呼び出し(ALBのDNSをRestTemplateに指定)
  • エンドポイントを指定したRDSアクセス
  • サービスエンドポイントを指定したDynamoDBアクセス
  • エンドポイントを指定したElastiCacheアクセス
  • S3バケットへのアクセス
  • サービスエンドポイントを指定したSQSへのキュー送信、ポーリングによる取得

Backend Serviceアプリケーションと、Frontend Webアプリケーションがそれぞれ参照するAWSリソースは以下の通りです。

ALB RDS DynamoDB ElastiCache S3 SQS
Backend Service ○(キュー受信)
Frontend WebApp ○(キュー送信)

ECSタスクのIAMロール定義は前回作成していますが、Backend Service、Frontend Webアプリケーションそれぞれで使用するAWSリソースのアクセスポリシーを割り当てねばなりません。アクセスするリソースはアプリケーションごとに異なり、変更されることも多いので、ECSタスク定義とは別に各サービスごとにテンプレートを分けて実装しておくことにします。

また、ALBのようにHTTPリクエスト送信でアクセスするため、リソース定義が不要なものもありますが、一方、CloudFormationやSystem Manager Parameter Storeなど、今回テンプレートの実装のなかでアクセスするサービスも加わります。

アクセスポリシーをCloudFormationで構築する場合、リソースタイプに「AWS::IAM::Policy」を定義する必要がありますが、各AWSリソースごとに定義する内容は異なります。プロパティとして設定可能な属性は、上記リンク先の通りですが、加えて、ポリシー定義を商用環境、ステージング環境、開発環境という3つのパターンに分けて作成するようにします。

まず、RDS、DynamoDB、SQSへアクセスするBackend Serviceアプリケーションに対するアクセスポリシーを定義したECSタスクロールテンプレートのサンプルは以下の通りです。

AWSTemplateFormatVersion: '2010-09-09'

// omit

Parameters:
  // omit
  EnvType:
    Description: Which environments to deploy your service.
    Type: String
    AllowedValues: ["Dev", "Staging", "Production"]
    Default: Dev

Resources:
  CloudFormationAccessPolicy:                                                             #(A)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-CloudFormationAccessPolicy-backend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cloudformation:*"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-BackendEcsTaskRole-${EnvType}                  #(B)

  SQSAccessPolicy:                                                                        #(C)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-SQSAccessPolicy-backend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "sqs:*"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-BackendEcsTaskRole-${EnvType}

  DynamoDBAccessPolicy:                                                                   #(D)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-DynamoDBAccessPolicy-backend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "dynamodb:*"
              - "dax:*"
              - "application-autoscaling:DeleteScalingPolicy"
              - "application-autoscaling:DeregisterScalableTarget"
              - "application-autoscaling:DescribeScalableTargets"
              - "application-autoscaling:DescribeScalingActivities"
              - "application-autoscaling:DescribeScalingPolicies"
              - "application-autoscaling:PutScalingPolicy"
              - "application-autoscaling:RegisterScalableTarget"
              - "cloudwatch:DeleteAlarms"
              - "cloudwatch:DescribeAlarmHistory"
              - "cloudwatch:DescribeAlarms"
              - "cloudwatch:DescribeAlarmsForMetric"
              - "cloudwatch:GetMetricStatistics"
              - "cloudwatch:ListMetrics"
              - "cloudwatch:PutMetricAlarm"
              - "datapipeline:ActivatePipeline"
              - "datapipeline:CreatePipeline"
              - "datapipeline:DeletePipeline"
              - "datapipeline:DescribeObjects"
              - "datapipeline:DescribePipelines"
              - "datapipeline:GetPipelineDefinition"
              - "datapipeline:ListPipelines"
              - "datapipeline:PutPipelineDefinition"
              - "datapipeline:QueryObjects"
              - "ec2:DescribeVpcs"
              - "ec2:DescribeSubnets"
              - "ec2:DescribeSecurityGroups"
              - "iam:GetRole"
              - "iam:ListRoles"
              - "kms:DescribeKey"
              - "kms:ListAliases"
              - "sns:CreateTopic"
              - "sns:DeleteTopic"
              - "sns:ListSubscriptions"
              - "sns:ListSubscriptionsByTopic"
              - "sns:ListTopics"
              - "sns:Subscribe"
              - "sns:Unsubscribe"
              - "sns:SetTopicAttributes"
              - "lambda:CreateFunction"
              - "lambda:ListFunctions"
              - "lambda:ListEventSourceMappings"
              - "lambda:CreateEventSourceMapping"
              - "lambda:DeleteEventSourceMapping"
              - "lambda:GetFunctionConfiguration"
              - "lambda:DeleteFunction"
              - "resource-groups:ListGroups"
              - "resource-groups:ListGroupResources"
              - "resource-groups:GetGroup"
              - "resource-groups:GetGroupQuery"
              - "resource-groups:DeleteGroup"
              - "resource-groups:CreateGroup"
              - "tag:GetResources"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-BackendEcsTaskRole-${EnvType}

  RDSAccessPolicy:                                                                         #(E)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-RDSAccessPolicy-backend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "rds:*"
              - "application-autoscaling:DeleteScalingPolicy"
              - "application-autoscaling:DeregisterScalableTarget"
              - "application-autoscaling:DescribeScalableTargets"
              - "application-autoscaling:DescribeScalingActivities"
              - "application-autoscaling:DescribeScalingPolicies"
              - "application-autoscaling:PutScalingPolicy"
              - "application-autoscaling:RegisterScalableTarget"
              - "cloudwatch:DescribeAlarms"
              - "cloudwatch:GetMetricStatistics"
              - "cloudwatch:PutMetricAlarm"
              - "cloudwatch:DeleteAlarms"
              - "ec2:DescribeAccountAttributes"
              - "ec2:DescribeAvailabilityZones"
              - "ec2:DescribeInternetGateways"
              - "ec2:DescribeSecurityGroups"
              - "ec2:DescribeSubnets"
              - "ec2:DescribeVpcAttribute"
              - "ec2:DescribeVpcs"
              - "sns:ListSubscriptions"
              - "sns:ListTopics"
              - "sns:Publish"
              - "logs:DescribeLogStreams"
              - "logs:GetLogEvents"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-BackendEcsTaskRole-${EnvType}

  SSMAccessPolicy:                                                                         #(F)
        Statement:
          - Effect: Allow
            Action:
              - "cloudwatch:PutMetricData"
              - "ds:CreateComputer"
              - "ds:DescribeDirectories"
              - "ec2:DescribeInstanceStatus"
              - "logs:*"
              - "ssm:*"
              - "ec2messages:*"
            Resource: "*"
          - Effect: Allow
            Action:
              - "iam:CreateServiceLinkedRole"
            Resource: "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*"
            Condition:
              StringLike:
                iam:AWSServiceName : "ssm.amazonaws.com"
          - Effect: Allow
            Action:
              - "iam:DeleteServiceLinkedRole"
              - "iam:GetServiceLinkedRoleDeletionStatus"
            Resource: "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*"
          - Effect: Allow
            Action:
              - "ssmmessages:CreateControlChannel"
              - "ssmmessages:CreateDataChannel"
              - "ssmmessages:OpenControlChannel"
              - "ssmmessages:OpenDataChannel"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-BackendEcsTaskRole-${EnvType}

上記テンプレートの記述の基本となるポイントは、下表の通りです。

記述 説明
A アプリケーションからCloudFormationClientを使ってスタック情報を取得するため、CloudFormationのアクセスポリシーを定義します。今回はAWSCloudFormationFullAccessポリシーを参考に全てのアクションを定義していますが、実際のアプリケーションでは、AWS公式サイトの「AWS CloudFormationのアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
B 定義したポリシーを前回作成したECSタスクのIAMロールにアタッチします。ポリシーから逆にアタッチするロールをクロススタックリファレンス参照することで拡張性を向上させます
C SQSへのアクセスポリシーを定義します。今回はAmazonSQSFullAccessポリシーを参考に全てのアクションを定義していますが、実際のアプリケーションでは、AWS公式サイトの「Amazon SQSのアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
D DynamoDBへのアクセスポリシーを定義します。今回はAmazonDynamoDBFullAccessポリシーを参考に定義していますが、実際のアプリケーションでは、AWS公式サイトの「Amazon DynamoDB のアクション、リソース、および条件キー」と「Amazon DynamoDB Accelerator(DAX)のアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
E RDSへのアクセスポリシーを定義します。今回はAmazonRDSFullAccessポリシーを参考に定義していますが、実際のアプリケーションでは、AWS公式サイトの「Amazon RDSのアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
F Systems Manager Parameters Storeへのアクセスポリシーを定義します。今回はAmazonSSMFullAccessポリシーを参考に定義していますが、実際のアプリケーションでは、AWS公式サイトの「「AWS Systems Manager のアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください

作成したテンプレートに対して、以下のようにスタック名とテンプレートパスを変更してヘルパースクリプトを実行します。

#!/usr/bin/env bash

stack_name="mynavi-sample-ecs-taskrole-backend"
template_path="sample-ecs-taskrole-backend-cfn.yml"

parameters="EnvType=Dev"

aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --parameter-overrides ${parameters} --capabilities CAPABILITY_IAM

実行が正常に終了すると、アクセスポリシーが作成され、ECSタスクのIAMロールにアタッチされます。

続いて、ElastiCache、S3、SQSへアクセスするFrontend Webアプリケーションに対するアクセスポリシーを定義したCloudFormationテンプレートは以下の通りです。

AWSTemplateFormatVersion: '2010-09-09'

// omit

Parameters:
  EnvType:
    Description: Which environments to deploy your service.
    Type: String
    AllowedValues: ["Dev", "Staging", "Production"]
    Default: Dev

Resources:
  CloudFormationAccessPolicy:                                                          #(A)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-CloudFormationAccessPolicy-frontend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cloudformation:*"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-FrontendEcsTaskRole-${EnvType}              #(B)

  S3AccessPolicy:                                                                      #(C)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-S3AccessPolicy-frontend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "s3:*"
            Resource:
              - Fn::Join:
                  - ""
                  - - Fn::ImportValue: !Sub MynaviSampleS3Bucket-Arn-${EnvType}        #(D)
                    - "/*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-FrontendEcsTaskRole-${EnvType}

  SQSAccessPolicy:                                                                     #(E)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-SQSAccessPolicy-frontend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "sqs:*"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-FrontendEcsTaskRole-${EnvType}

  ElastiCacheAccessPolicy:                                                             #(F)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-ElastiCacheAccessPolicy-frontend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "elasticache:*"
            Resource: "*"
          - Effect: Allow
            Action:
              - "iam:CreateServiceLinkedRole"
            Resource: "*arn:aws:iam::*:role/aws-service-role/elasticache.amazonaws.com/AWSServiceRoleForElastiCache"
            Condition:
              StringLike:
                iam:AWSServiceName : "elasticache.amazonaws.com"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-FrontendEcsTaskRole-${EnvType}

  SSMAccessPolicy:                                                                      #(G)
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub Mynavi-Sample-SSMAccessPolicy-frontend-${EnvType}
      PolicyDocument:
        Statement:
          - Effect: Allow
            Action:
              - "cloudwatch:PutMetricData"
              - "ds:CreateComputer"
              - "ds:DescribeDirectories"
              - "ec2:DescribeInstanceStatus"
              - "logs:*"
              - "ssm:*"
              - "ec2messages:*"
            Resource: "*"
          - Effect: Allow
            Action:
              - "iam:CreateServiceLinkedRole"
            Resource: "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*"
            Condition:
              StringLike:
                iam:AWSServiceName : "ssm.amazonaws.com"
          - Effect: Allow
            Action:
              - "iam:DeleteServiceLinkedRole"
              - "iam:GetServiceLinkedRoleDeletionStatus"
            Resource: "arn:aws:iam::*:role/aws-service-role/ssm.amazonaws.com/AWSServiceRoleForAmazonSSM*"
          - Effect: Allow
            Action:
              - "ssmmessages:CreateControlChannel"
              - "ssmmessages:CreateDataChannel"
              - "ssmmessages:OpenControlChannel"
              - "ssmmessages:OpenDataChannel"
            Resource: "*"
      Roles:
        - Fn::ImportValue: !Sub ${VPCName}-FrontendEcsTaskRole-${EnvType}

上記テンプレート記述の基本となるポイントは、下表の通りです。

記述 説明
A アプリケーションからCloudFormationClientを使ってスタック情報を取得するため、CloudFormationのアクセスポリシーを定義します。今回はAWSCloudFormationFullAccessポリシーを参考に全てのアクションを定義していますが、実際のアプリケーションでは、AWS公式サイトの「AWS CloudFormation のアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
B 定義したポリシーを前回作成したECSタスクのIAMロールにアタッチします。ポリシーから逆にアタッチするロールをクロススタックリファンレンス参照することで拡張性を向上させます
C S3へのアクセスポリシーを定義します。今回はAmazonS3FullAccessポリシーを参考に全てのアクションを定義していますが、実際のアプリケーションでは、AWS公式サイトの「Amazon S3 のアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
D S3へのアクセスを「ref:section-cloudformation-s3-sample-label」で構築したバケットのARN(AmazonResourceName) に対し、クロススタックリファレンスで取得した値とワイルドカードを文字列結合しています
E SQSへのアクセスポリシーを定義します。今回はAmazonSQSFullAccessポリシーを参考に全てのアクションを定義していますが、実際のアプリケーションでは、AWS公式サイトの「Amazon SQS のアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
F ElastiCacheへのアクセスポリシーを定義します。今回はAmazonElastiCacheFullAccessポリシーを参考に定義していますが、実際のアプリケーションでは、AWS公式サイトの「Amazon ElastiCacheのアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください
G Systems Manager Parameters Storeへのアクセスポリシーを定義します。今回はAmazonSSMFullAccessポリシーを参考に定義していますが、 実際のアプリケーションでは、AWS公式サイトの「AWS Systems Manager のアクション、リソース、および条件キー」を参考に必要最小限のアクションを定義するようにしてください

作成したテンプレートに対して、以下のように、スタック名とテンプレートパスを変更してヘルパースクリプトを実行します。

#!/usr/bin/env bash

stack_name="mynavi-sample-ecs-taskrole-frontend"
template_path="sample-ecs-taskrole-frontend-cfn.yml"

parameters="EnvType=Dev"

aws cloudformation deploy --stack-name ${stack_name} --template-file ${template_path} --parameter-overrides ${parameters} --capabilities CAPABILITY_IAM

実行が正常に終了すると、アクセスポリシーが作成され、ECSタスクのIAMロールにアタッチされます。

ガートナー ジャパン リサーチ部門 ITインフラストラクチャ&セキュリティのリサーチ ディレクター、池田武史氏

以上、今回はECSタスクのIAMロールにアタッチするポリシーを構築するCloudFormationテンプレートを実装しました。次回は、ECSサービスを構築するCloudFormationテンプレートを作成する手順を紹介します。

著者紹介


川畑 光平(KAWABATA Kohei) - NTTデータ

金融機関システム業務アプリケーション開発・システム基盤担当、ソフトウェア開発自動化関連の研究開発を経て、デジタル技術関連の研究開発・推進に従事。

Red Hat Certified Engineer、Pivotal Certified Spring Professional、AWS Certified Solutions Architect Professional等の資格を持ち、アプリケーション基盤・クラウドなど様々な開発プロジェクト支援にも携わる。AWS Top Engineers & Ambassadors選出。

本連載の内容に対するご意見・ご質問は Facebook まで。