AWS アカウント管理とセキュリティ監査自動化

by Yuma Takahashi | July 29, 2019
security | #aws

こんにちは、IT 基盤部髙橋です。
DeNA が提供するエンタメ系やヘルスケア系のサービスのインフラを見ており、そのグループのマネージャをしています。
先日 AWS Loft Tokyo で DeNA における AWS セキュリティについて発表してきたので、発表では述べられなかった具体的な設定についていくつか記載します。

AWS アカウント管理

AWS アカウント管理を効率的に行うために、スライドの中でいくつかの施策を挙げています。 その中の一部をコマンド例を交えて紹介します。

AWS Organizations を利用したマルチアカウント管理

AWS Organizations の特徴としては以下のようなものが挙げられます。

  • アカウントの一元管理
    • OU (Organization Unit) を用いた階層的なグループ化も可能
  • 一括請求 (Consolidated Billing)
    • アカウントの請求と支払いを統合
  • リザーブドインスタンスの共有
    • 他のアカウントで購入したリザーブドインスタンスを共有することができる
  • サービスコントロールポリシー (SCP) の利用
    • Organization 配下の各 AWS アカウント (メンバーアカウント) の権限を指定できる

また AWS Organizations を利用すればコマンドで AWS アカウントを作成することが可能になります。 連絡先情報やクレジットカード情報は Organization マスターアカウントの情報が引き継がれます。
--role-name には作成されるアカウントへの AdministratorAceess 権限を持つロール名を指定します。デフォルトでは以下に記載している通り OrganizationAccountAccessRole という名前になります。 --iam-user-access-to-billing には IAM ユーザーに請求情報へのアクセスを許可する場合は ALLOW を、拒否する場合は DENY を指定してください。

aws organizations create-account \
    --email ${EMAIL} \
    --account-name ${ACCOUNT_NAME} \
    --role-name OrganizationAccountAccessRole \
    --iam-user-access-to-billing ALLOW
{
    "CreateAccountStatus": {
        "RequestedTimestamp": 1564398749.614, 
        "State": "IN_PROGRESS", 
        "Id": "car-01234567890abcdefghijklmnopqrstu", 
        "AccountName": "sample-account1"
    }
}

アカウント作成の状況は以下のコマンドで確認することができます。 State の値が SUCCEEDED になるまで定期的に監視すれば、後続の作業も自動化できます。

aws organizations describe-create-account-status \
    --create-account-request-id ${ACCOUNT_ID}
{
    "CreateAccountStatus": {
        "AccountId": "123456789012", 
        "State": "SUCCEEDED", 
        "Id": "car-01234567890abcdefghijklmnopqrstu", 
        "AccountName": "sample-account1", 
        "CompletedTimestamp": 1564398812.234, 
        "RequestedTimestamp": 1564398749.696
    }
}

AWS アカウント作成時の初期設定や設定変更方法の整備

AWS アカウントを作成するときには同時に各アカウントに必要な初期設定もしています。 aws sts assume-role コマンドを利用して設定先アカウントの AdministratorAccess 権限を取得します。 ACCOUNT_ID には上記の出力結果から得られたものを入れます。 --role-session-name は権限を得たロールのセッションの識別子のようなものでここでは適当な値を入れておきます。

aws sts assume-role \
    --role-arn arn:aws:iam::${ACCOUNT_ID}:role/OrganizationAccountAccessRole \
    --role-session-name "RoleSession1"

上記コマンドで得られた情報から AccessKeyId SecretAccessKey SessionToken の値を以下のように環境変数に設定します。

export AWS_ACCESS_KEY_ID=ABCDEFGHIJKLMNOPQRST
export AWS_SECRET_ACCESS_KEY=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN
export AWS_SESSION_TOKEN=abcdefghijk...<remainder of security token>

まず以下のようにして作成しようとしているロールが存在するか判定します。 ROLE_NAME には作成したいロールの名前を入れてください。

aws iam get-role --role-name ${ROLE_NAME}

IAM ロールがまだなければ作成をします。 --assume-role-policy-document で信頼関係の内容を記載した json 形式のファイルを指定します。

aws iam create-role --role-name ${ROLE_NAME} \
    --assume-role-policy-document file://path/to/${ROLE_NAME}-Trust-Policy.json

次に IAM ポリシーの作成をします。 POLICY_NAME には作成したいポリシーの名前を入れてください。 --policy-document でポシリーの内容を記載した json 形式のファイルを指定します。 path/to/${ROLE_NAME}-Policy.json には適切なファイルパスを指定してください。

aws iam create-policy --policy-name ${POLICY_NAME} \
    --policy-document file://path/to/${ROLE_NAME}-Policy.json

作成した IAM ロールに先ほど作成した IAM ポリシーをアタッチします。

aws iam attach-role-policy --role-name ${ROLE_NAME} \
    --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/${POLICY_NAME}

このようにして初期設定に必要なロールやポシリーの作成を繰り返し行います。 その他初期設定として CloudTrail の設定やそのログを保管する S3 Bucket の作成なども DeNA では行っています。

全 AWS アカウントの設定変更に関しては、上記で述べた各アカウントに存在する OrganizationAccountAccessRole を使い、同じように aws sts assume-role をして設定変更を行います。 AWS Organizations を使えば全 AWS アカウントのリストを取得することも可能なので、これで取得できた全アカウントに対して操作するようなものを書けばよいでしょう。

aws organizations list-accounts
{
    "Accounts": [
        {
            "Name": "sample-account1", 
            "Email": "sample-email@dena.jp", 
            "Id": "123456789012", 
            "JoinedMethod": "CREATED", 
            "JoinedTimestamp": 1536059567.45, 
            "Arn": "arn:aws:organizations::123456789013:account/o-abcdefghij/123456789012", 
            "Status": "ACTIVE"
        }, 
        {
            "Name": "sample-account2", 
            "Email": "sample-email2@dena.jp", 
            "Id": "123456789013", 
            "JoinedMethod": "CREATED", 
            "JoinedTimestamp": 1536059568.45, 
            "Arn": "arn:aws:organizations::123456789000:account/o-abcdefghij/123456789013", 
            "Status": "ACTIVE"
        }, 
        ...
    ]
}

IAM ユーザー管理

発表の中では社内ディレクトリで管理されるフェデレーティッドユーザーを利用した AWS マネジメントコンソールログインの方法について説明しました。

ID フェデレーションによる AWS マネジメントコンソールログイン

下記は踏み台アカウントから各サービスのアカウントに Switch Role を利用してロールの切り替えを行う図ですが、この時の各アカウントに存在するロールのポリシーと信頼関係について解説します。

踏み台アカウントにある step-service-account1-admin ロールのポリシーは以下のようにしています。

{
    "Version": "2012-10-17",
    "Statement": {
        "Effect": "Allow",
        "Action": "sts:AssumeRole",
        "Resource": "arn:aws:iam::*:role/service-account1-admin"
    }
}

踏み台アカウントにある step-service-account1-admin ロールの信頼関係は以下のようにしています。
STEP_ACCOUNT_ID には踏み台アカウントのアカウント ID を入れてください。 SAML_PROVIDER_NAME には作成した SAML プロバイダの名前を入れてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${STEP_ACCOUNT_ID}:saml-provider/${SAML_PROVIDER_NAME}"
      },
      "Action": "sts:AssumeRoleWithSAML",
      "Condition": {
        "StringEquals": {
          "SAML:aud": "https://signin.aws.amazon.com/saml"
        }
      }
    }
  ]
}

各サービスアカウントにある service-account1-admin ロールのポリシーは以下のようにしています。(ここでは AdministratorAccess 権限となっています。)

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

各サービスアカウントにある service-account1-admin ロールの信頼関係は以下のようにしています。 STEP_ACCOUNT_ID には踏み台アカウントのアカウント ID を入れてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::${STEP_ACCOUNT_ID}:role/step-service-account1-admin"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

上記は AdministratorAccess 権限についての設定例ですが、ReadOnlyAccess やカスタマイズした権限ももたせることが可能になっています。

AWS セキュリティ監査自動化

発表では AWS セキュリティ監査自動化システムの実装例としていくつか述べました。 ここではさらにどのようなコマンドを使って判定するかを具体的に書いていきたいと思います。

ルートユーザーに対して MFA を有効化すること

概要: 認証情報レポートでルートユーザーに対して mfa_active の値が true であるか確認します

まずはレポートの生成をします。

aws iam generate-credential-report
{
    "State": "STARTED", 
    "Description": "No report exists. Starting a new report generation task"
}

次に生成したレポートを取得します。

aws iam get-credential-report

取得された内容は Base64 エンコードされているのでデコードする必要があります。 <root_account> の行で mfa_active の値が true であれば MFA が有効であることになります。

user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated
<root_account>,arn:aws:iam::123456789012:root,2018-06-20T13:38:27+00:00,not_supported,2019-07-02T09:23:21+00:00,not_supported,not_supported,true,false,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A
...

IAM で利用が完了し使われてないアクセスキーは無効化・削除すること

概要: list-users でユーザ一覧を出し、 list-access-keysStatusActive なものの中で、 get-access-key-last-used から得られる LastUsedDate が特定の期間内であるか確認します

まずは list-users で IAM ユーザ一覧の情報を取得します。

aws iam list-users
{
    "Users": [
        {
            "Arn": "arn:aws:iam::123456789012:user/sample-user1",
            "UserId": "ABCDEFGHIJKLMNOPQRSTU",
            "CreateDate": "2018-07-23T09:37:10Z",
            "UserName": "sample-user1",
            "Path": "/"
        },
        {
            "Arn": "arn:aws:iam::123456789013:user/sample-user2",
            "UserId": "ABCDEFGHIJKLMNOPQRSTV",
            "CreateDate": "2018-08-01T11:51:47Z",
            "UserName": "sample-user2",
            "Path": "/"
        },
        
    ]
}

次に list-access-keysStatus の情報を取得します。 --user-name には上記で取得できたユーザー名を指定します。

aws iam list-access-keys --user-name sample-user1
{
    "AccessKeyMetadata": [
        {
            "AccessKeyId": "ABCDEFGHIJKLMNOPQRST", 
            "Status": "Active", 
            "CreateDate": "2019-01-09T08:15:14Z", 
            "UserName": "sample-user1"
        }, 
        {
            "AccessKeyId": "ABCDEFGHIJKLMNOPQRSU", 
            "Status": "Active", 
            "CreateDate": "2019-07-29T16:35:35Z", 
            "UserName": "sample-user1"
        }
    ]
}

最後に --access-key-id でアクセスキー ID を指定してそのキーの LastUsedDate を求めます。得られた LastUsedDate が特定の期間内であるか確認します。

aws iam get-access-key-last-used --access-key-id ABCDEFGHIJKLMNOPQRST
{
    "AccessKeyLastUsed": {
        "Region": "us-east-1", 
        "LastUsedDate": "2019-01-09T10:52:00Z", 
        "ServiceName": "iam"
    }, 
    "UserName": "sample-user1"
}

ELB のアクセスログを有効化すること

概要: describe-load-balancer-attributesaccess_logs.s3.enabled の値が true であるか確認します

aws elbv2 コマンドを使います。 --load-balancer-arn には情報を取得する LB の ARN を指定します。

aws elbv2 describe-load-balancer-attributes --load-balancer-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/sample-alb1/abcdefghijklmnop

以下のように色々な値が取れます。今回は access_logs.s3.enabled の値を確認します。

{
    "Attributes": [
        {
            "Value": "false", 
            "Key": "access_logs.s3.enabled"
        }, 
        {
            "Value": "", 
            "Key": "access_logs.s3.bucket"
        }, 
        {
            "Value": "", 
            "Key": "access_logs.s3.prefix"
        }, 
        {
            "Value": "60", 
            "Key": "idle_timeout.timeout_seconds"
        }, 
        {
            "Value": "false", 
            "Key": "deletion_protection.enabled"
        }, 
        {
            "Value": "true", 
            "Key": "routing.http2.enabled"
        }
    ]
}

RDS でインターネットからのアクセスが不要な場合はパブリックアクセシビリティを無効にすること

概要: describe-db-instancesPubliclyAccessiblefalse であるか確認します

aws rds コマンドを使います。 --db-instance-identifier には DB の識別子を指定します。

aws rds describe-db-instances --db-instance-identifier sample-db1

PubliclyAccessible の値が false であれば問題ないです。(かなり多くの情報が取れるので省略します。)

{
    "DBInstances": [
        {
            "DBInstanceIdentifier": "sample-db1", 
            "AvailabilityZone": "ap-northeast-1a", 
            "DBInstanceClass": "db.m5.large", 
            "BackupRetentionPeriod": 7, 
            "Engine": "mysql", 
            "PubliclyAccessible": true, 
            ...
        }
    ]
}

おわりに

以上、DeNA での AWS アカウント管理とセキュリティ監査自動化について説明してきました。 DeNA ではこれまで以上に大規模に AWS を利用していくために、セキュリティレベルの高い状態をより効率的に構築管理できる方法を日々考えています。