チームで開発を行う際のAWSでのIAM権限管理

# チームで開発を行う際のAWSでのIAM権限管理

この記事は，ドコモアドベントカレンダー12日目の記事になります。  

ドコモの川崎です。  
業務では検索エンジンや交通機関の利用需要予測のプロジェクトに取り組んでいます。
12日目のこの記事では，本業務とは直接関係ないですがチームで開発を行う際のAWSでのIAM権限管理について紹介します。  

## 権限管理って必要？

研究開発の部署ですので、社員自身もコーディングをして検証用のシステムを構築します。
その際に用いる環境としてはAWSのようなクラウドサービスを積極的に活用しています。
深層学習でのモデル学習など計算コストのかかるものは、
まだまだオンプレミス環境が活躍していますが、深層学習での推論処理や検索エンジンのサーバはもっぱらクラウド上に構築しています。

チームでの開発でAWSを使っている人には当たり前の話が多いと思いますが、
商用ではなく開発環境のアカウントになると、社内で見ても権限管理は割と雑な例が多いです。
つまり、とりあえず開発者が困らないように何でもできる権限を付けてしまっています。

開発者には基本的に開発リソースには自由にアクセスさせつつ、
設定をいじってほしくない部分にはアクセスできないように権限管理をしたいのですが
その目的に見合ったIAMのポリシーがデフォルトでは用意されていません。

この記事ではとりあえずこの設定はやっておくべきというIAM設定について、
他のプロジェクトでもコピペするだけで済むように備忘録として残すことも目的であったりします。

## IAMアカウントの割り当て ＋ MFA認証の強制

アカウントは1人につき１つを払い出し、複数人で共有するアカウントの作成は許容しません。
これは監査の都合もあり、だれが何をしたかを追跡できるようにしておく必要があるためです。

IAMアカウントの作成をしたら、許容する権限をアカウントに付与することになりますが、
この状態ではパスワード変更の権限すら付いていません（でした。最近は権限が自動的に付与されます）。
また、内部的なルールでMFA認証を設定する必要があるため、MFA認証を設定するまでは
AWS上での操作を一切受け付けないようなポリシーを設定します。
開発においてはアクセスキーを払い出したりすることもあるので、そのための権限もつけます。

この辺のアカウント作成時に付与すべきIAM関連の権限は以下の通りです。

- 自身のアカウント管理
    - パスワードの変更を許可（パスワード期限切れなどに対応）
    - 自分のユーザー、パスワード、アクセスキー、署名証明書、SSH パブリックキーを IAM コンソールで管理できる
- MFA認証
    - ユーザーが各自の MFA デバイスをプロビジョニングまたは管理することを許可
    - MFA を使用してユーザーがサインインした場合に限り、ユーザーが自分の MFA デバイスの無効化を許可
        - これにより、他者がアクセスキー (MFA デバイスではなく) のみを使用して MFA デバイスを無効化したり、アカウントにアクセスしたりできなくなります。 
    - ユーザーが MFA でサインインしていない場合､"Deny" と "NotAction" の組み合わせを使用して、他のすべての AWS サービスのすべてのアクションを拒否
        - ただし、プログラム等でAccessKeyを使ったアクセスを行う場合、この設定が適用されているとMFA認証なしとみなされるので要注意。この場合、IAMロールを使うとか別のIAMアカウントを作るなどで対応してください。

### 「自身のアカウント管理」のポリシー
```xml:IAMSelfManagePasswordAccesskeySSHkey
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:*LoginProfile",
        "iam:ChangePassword",
        "iam:*AccessKey*",
        "iam:GetUser",
        "iam:*SSHPublicKey*",
        "iam:*ServiceSpecificCredential*",
        "iam:*SigningCertificate*"
      ],
      "Resource": "arn:aws:iam::*:user/${aws:username}"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:ListAccount*",
        "iam:ListAttachedUserPolicies",
        "iam:ListAttachedGroupPolicies",
        "iam:ListPolicies",
        "iam:ListUserPolicies",
        "iam:ListGroupPolicies",
        "iam:ListGroups",
        "iam:ListGroupsForUser",
        "iam:GetPolicy",
        "iam:GetAccountSummary",
        "iam:GetAccountPasswordPolicy",
        "iam:ListUsers"
      ],
      "Resource": "*"
    }
  ]
}
```

### 「MFA認証」のポリシー
```xml:IAMSelfManageMFA
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iam:CreateVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:ResyncMFADevice",
        "iam:DeleteVirtualMFADevice"
      ],
      "Resource": [
        "arn:aws:iam::*:mfa/${aws:username}",
        "arn:aws:iam::*:user/${aws:username}"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:DeactivateMFADevice"
      ],
      "Resource": [
        "arn:aws:iam::*:mfa/${aws:username}",
        "arn:aws:iam::*:user/${aws:username}"
      ],
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    },
    {
      "Effect": "Allow",
      "Action": [
        "iam:ListMFADevices",
        "iam:ListVirtualMFADevices",
        "iam:ListUsers"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Deny",
      "NotAction": [
        "iam:*",
        "sts:GetSessionToken"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}
```

## 利用リージョンの制限

EC２インスタンスを構築するリージョンについて日本国内に限定するために、
ポリシーでap-northeast-1以外のリージョンでのEC2インスタンス操作を拒否させます。

### 「利用リージョン制限」のポリシー

```xml:DenyEC2OperationOutsideTokyoRegion
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Action": [
        "ec2:AttachVolume",
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:DeleteTags",
        "ec2:DetachClassicLinkVpc",
        "ec2:AcceptVpcPeeringConnection",
        "ec2:DeleteVpcPeeringConnection",
        "ec2:ReplaceIamInstanceProfileAssociation",
        "ec2:DeleteRouteTable",
        "ec2:RejectVpcPeeringConnection",
        "ec2:UpdateSecurityGroupRuleDescriptionsIngress",
        "ec2:DeleteVolume",
        "ec2:StartInstances",
        "ec2:CreateNetworkInterfacePermission",
        "ec2:RevokeSecurityGroupEgress",
        "ec2:DeleteInternetGateway",
        "ec2:DeleteLaunchTemplateVersions",
        "ec2:CreateVpcPeeringConnection",
        "ec2:EnableVpcClassicLink",
        "ec2:DeleteNetworkAcl",
        "ec2:DetachVolume",
        "ec2:RebootInstances",
        "ec2:AuthorizeSecurityGroupEgress",
        "ec2:AttachClassicLinkVpc",
        "ec2:DeleteLaunchTemplate",
        "ec2:UpdateSecurityGroupRuleDescriptionsEgress",
        "ec2:TerminateInstances",
        "ec2:CreateTags",
        "ec2:DeleteRoute",
        "ec2:RunInstances",
        "ec2:StopInstances",
        "ec2:CreateLaunchTemplateVersion",
        "ec2:CreateVolume",
        "ec2:RevokeSecurityGroupIngress",
        "ec2:DeleteCustomerGateway",
        "ec2:DisassociateIamInstanceProfile",
        "ec2:DeleteSecurityGroup",
        "ec2:DeleteDhcpOptions",
        "ec2:DisableVpcClassicLink",
        "ec2:ModifyLaunchTemplate",
        "ec2:AssociateIamInstanceProfile",
        "ec2:DeleteNetworkAclEntry"
      ],
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "ec2:Region": "ap-northeast-1"
        }
      }
    }
  ]
}
```

## そのほかに付与する権限

開発環境での利用となる場合は、特に利用するリソースに大きな制限を付けたくないので、
PowerUserAccessポリシーを付与することが多いです。
また、S3、EC2とRDSしか利用しないことが明らかなプロジェクトでは、AmazonS3FullAccess、
AmazonEC2FullAccess、AmazonRDSFullAccessポリシーのみを付与します。

開発者にもどれだけのリソースを利用していて、請求額がどれぐらいかを実感してもらうために、
請求情報への参照権限を付与しています。
AWSAccountUsageReportAccess、AWSAccountActivityAccessの２つのポリシーを付与します。

### 参考

今回ご紹介したポリシーはAmazonの公式チュートリアルにも一部載っていますので、
最新の情報はそちらを参照ください。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/tutorial_users-self-manage-mfa-and-creds.html