tk_ch’s blog

インフラエンジニアのブログ

AWS CodeCommitリポジトリへのアクセスを除きMFA強制するIAMポリシー

AWSのIAMユーザを払い出した際に、不正アクセスのリスク低減のためMFA(多要素認証)の設定をしてほしいが、設定したかの確認が面倒だし、中々やってくれないといったケースがある。
そのような場合、以下ドキュメントのようにMFAを強制するIAMポリシーを割り当てることが有効。
docs.aws.amazon.com

ただし、このIAMポリシーを使った場合、CodeCommitのリポジトリへのアクセスにもMFAが必要となる。
git-remote-codecommitやAWS CLI 認証情報ヘルパーを使えばMFAを使ってリポジトリへアクセスできるが、環境やアクセス要件によってはこれらが使用できない場合もある。
その場合は、IAMポリシーを変更することでCodeCommitリポジトリへのアクセスのみMFA強制の対象外とすることができる。
その設定をした際のメモを記載する。

実施内容

CodeCommitへのアクセスに必要なアクションを許可する

前述したドキュメントに従ってIAMポリシーを作成すると、DenyAllExceptListedIfNoMFAステートメントのNotActionに「MFA を使用していない場合に許可する操作」が記載されている。
ここに、CodeCommitリポジトリの操作に必要な以下アクションを追加する。

  • codecommit:GitPull
  • codecommit:GitPush
  • kms:Encrypt
  • kms:Decrypt
  • kms:ReEncrypt
  • kms:GenerateDataKey
  • kms:GenerateDataKeyWithoutPlaintext
  • kms:DescribeKey

※KMS関連アクションも追加しているのは、CodeCommitへのアクセス時にKMSも使用されているため。
エイリアス名「aws/codecommit」のキーに対する上記KMS関連アクションを許可する必要がある。
詳細は以下リンク参照。 docs.aws.amazon.com

編集後のDenyAllExceptListedIfNoMFAステートメントは以下。

        {
            "Sid": "DenyAllExceptListedIfNoMFA",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:EnableMFADevice",
                "iam:GetUser",
                "iam:ListMFADevices",
                "iam:ListVirtualMFADevices",
                "iam:ResyncMFADevice",
                "sts:GetSessionToken",
                "codecommit:GitPull",
                "codecommit:GitPush",
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncryptFrom",
                "kms:ReEncryptTo",
                "kms:GenerateDataKey",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        },

KMS関連アクションの許可対象リソースを制限する

編集したDenyAllExceptListedIfNoMFAステートメントはResourceが「*」なので、このままだとKMS関連アクションが全てのキーに対してMFAなしで実行できてしまう。
そのため、 以下のようにaws/codecommit キー以外に対する kms関連アクションの Deny 設定を追加する。

        {
            "Sid": "DenyKMSExceptListedIfNoMFA",
            "Effect": "Deny",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncryptFrom",
                "kms:ReEncryptTo",
                "kms:GenerateDataKey",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringNotEquals": {
                    "kms:ResourceAliases": [
                        "alias/aws/codecommit"
                    ]
                },
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }

エイリアス指定でKMSキーへのアクセスを制御するには「kms:ResourceAliases」を使う。詳細は以下参照。

docs.aws.amazon.com

※ちなみに以下の書き方でもよい。
NotResourceにKMSキーのARNを記載する必要があるのが嫌だったので、今回はエイリアス名の記載だけでよい上記の書き方を採用した。

        {
            "Sid": "DenyKMSExceptListedIfNoMFA",
            "Effect": "Deny",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncryptFrom",
                "kms:ReEncryptTo",
                "kms:GenerateDataKey",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:DescribeKey"
            ],
            "NotResource": [
                "arn:aws:kms:*:*:alias/aws/codecommit",
                "{エイリアス名が「aws/codecommit」なKMSキーのARN}"
            ],
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }

最終的なIAMポリシー

最終的なIAMポリシーは以下のようになる。
このIAMポリシーをIAMユーザ or IAMユーザが所属するIAMグループに割り当てることで、「MFA設定するまで、MFAの設定とCodeCommitの操作以外できない」という挙動にすることができる。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowViewAccountInfo",
            "Effect": "Allow",
            "Action": [
                "iam:GetAccountPasswordPolicy",
                "iam:ListVirtualMFADevices"
            ],
            "Resource": "*"
        },
        {
            "Sid": "AllowManageOwnPasswords",
            "Effect": "Allow",
            "Action": [
                "iam:ChangePassword",
                "iam:GetUser"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnAccessKeys",
            "Effect": "Allow",
            "Action": [
                "iam:CreateAccessKey",
                "iam:DeleteAccessKey",
                "iam:ListAccessKeys",
                "iam:UpdateAccessKey"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnSigningCertificates",
            "Effect": "Allow",
            "Action": [
                "iam:DeleteSigningCertificate",
                "iam:ListSigningCertificates",
                "iam:UpdateSigningCertificate",
                "iam:UploadSigningCertificate"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnSSHPublicKeys",
            "Effect": "Allow",
            "Action": [
                "iam:DeleteSSHPublicKey",
                "iam:GetSSHPublicKey",
                "iam:ListSSHPublicKeys",
                "iam:UpdateSSHPublicKey",
                "iam:UploadSSHPublicKey"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnGitCredentials",
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceSpecificCredential",
                "iam:DeleteServiceSpecificCredential",
                "iam:ListServiceSpecificCredentials",
                "iam:ResetServiceSpecificCredential",
                "iam:UpdateServiceSpecificCredential"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnVirtualMFADevice",
            "Effect": "Allow",
            "Action": [
                "iam:CreateVirtualMFADevice",
                "iam:DeleteVirtualMFADevice"
            ],
            "Resource": "arn:aws:iam::*:mfa/${aws:username}"
        },
        {
            "Sid": "AllowManageOwnUserMFA",
            "Effect": "Allow",
            "Action": [
                "iam:DeactivateMFADevice",
                "iam:EnableMFADevice",
                "iam:ListMFADevices",
                "iam:ResyncMFADevice"
            ],
            "Resource": "arn:aws:iam::*:user/${aws:username}"
        },
        {
            "Sid": "DenyAllExceptListedIfNoMFA",
            "Effect": "Deny",
            "NotAction": [
                "iam:CreateVirtualMFADevice",
                "iam:EnableMFADevice",
                "iam:GetUser",
                "iam:ListMFADevices",
                "iam:ListVirtualMFADevices",
                "iam:ResyncMFADevice",
                "sts:GetSessionToken",
                "codecommit:GitPull",
                "codecommit:GitPush",
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncryptFrom",
                "kms:ReEncryptTo",
                "kms:GenerateDataKey",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        },
        {
            "Sid": "DenyKMSExceptListedIfNoMFA",
            "Effect": "Deny",
            "Action": [
                "kms:Encrypt",
                "kms:Decrypt",
                "kms:ReEncryptFrom",
                "kms:ReEncryptTo",
                "kms:GenerateDataKey",
                "kms:GenerateDataKeyWithoutPlaintext",
                "kms:DescribeKey"
            ],
            "Resource": "*",
            "Condition": {
                "ForAnyValue:StringNotEquals": {
                    "kms:ResourceAliases": [
                        "alias/aws/codecommit"
                    ]
                },
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "false"
                }
            }
        }
    ]
}

補足

CodeCommitへMFAを使ってアクセスする方法

CodeCommitへMFAを使ってアクセスするには、git-remote-codecommitかAWS CLI 認証情報ヘルパーを使う必要がある。 ※このページに「CodeCommit への一時アクセスまたはフェデレーティッドアクセスに対して推奨されるアプローチは git-remote-codecommit を設定すること」とあるので、git-remote-codecommitの方が推奨されている模様。

それぞれの具体的な設定手順は以下を参照。
- git-remote-codecommit docs.aws.amazon.com - AWS CLI 認証情報ヘルパー docs.aws.amazon.com

どちらの接続方式も、get-session-token コマンドを使用してMFA認証して、コマンド結果に含まれる一時的な認証情報を用いて、CodeCommit へアクセスする。
アクセス方法は以下を参照。
aws.amazon.com

IAMユーザー払い出し時の注意点

ユーザ作成時は「パスワードのリセットが必要」にチェックを入れない

IAMユーザ作成時によくやるのが、「パスワードのリセットが必要」にチェックを入れて、初回ログイン時にユーザにパスワードリセットを強制する設定。
しかし、本ページで紹介したIAMポリシーを割り当てたユーザーは、MFA認証でログインしない限りパスワード変更できないため、MFA設定前の初回ログイン時にはパスワードリセットする権限がない。
そのため、初回ログイン時に「パスワードリセットしないと先に進めないのに、リセット権限がない」という状況に陥り、何もできなくなってしまう。
なので、IAMユーザ作成時には「パスワードのリセットが必要」にチェックを入れず、2回目以降のログイン時にパスワードを変更するようユーザに伝える必要がある。
※IAMポリシーを緩めれば初回ログインでパスワードリセットさせることは可能だが、このドキュメントに「IAM ではこのようなアクセス許可をお勧めしません。ユーザーが MFA なしで自分のパスワードを変更できるようにすると、セキュリティ上のリスクが生じる可能性があります。」と記載してあるので、やらない方が良いと思う。

MFA設定画面までの行き方に注意

AWSのMFA有効化手順にはIAMの画面からナビゲーションペインの「ユーザ」をクリックすると記載されている。
しかし、本ページのポリシーを適用しているとMFA認証でログインするまではユーザの一覧画面を開く権限がなく、エラーとなってしまう。
そのため、自分のユーザの画面を開くためには、右上のユーザー名から「セキュリティ認証情報」をクリックする必要がある。
新規ユーザを作成した場合はこの手順についてもユーザに伝える。

参考文献

【IAM】MFA 強制ポリシーの注意点【多要素認証】