【GuardDuty】S3バケット上のIPリストにアクセスするための権限設定




# はじめに
Amazon GuardDutyにIPリストを設定する際、
S3バケット上のIPリストにアクセスするための権限ってどうすればいいのでしょうか？

# まとめ
S3バケット上のIPリストにアクセスする際に使用する権限は、
GuardDutyのサービスロール（`AWSServiceRoleForAmazonGuardDuty`）です。

ただし、このサービスロールに権限を追加するために、設定が必要です。
具体的には、IPリストを設定（`guardduty:CreateThreatIntelSet`）するユーザの権限に、`iam:PutRolePolicy`が必要です。
したがって、IPリストを新規設定するユーザの、最小権限は下記になります。

``` json:policy-guardduty-ip-list-v2.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "guardduty:*ThreatIntelSet*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:PutRolePolicy"
            ],
            "Resource": "arn:aws:iam::*:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"
        }
    ]
}
```

※上記の権限は、IPリストを新規設定する際の、超最小権限です。実際は、参照・更新・削除の権限も入れておきましょう。詳しくは、「さいごに」を参照してください。

# 前提
GuardDuty・S3について復習しましょう。

## 前提1：GuardDutyって？IPリストって？
Amazon GuardDutyは、CloudTrail、VPCフローログ、DNSログ、S3データアクセスイベントから、不審な操作を検出するサービスです。
GuardDutyの設定として、ユーザ指定のIPリストに基づいて、アラートを発生させたり、抑制させたりできます。

大まかな動作は次のエントリで理解できます。

[脅威リストを使ったGuardDutyの検出テスト | DevelopersIO](https://dev.classmethod.jp/articles/guardduty-generate-findings-test/ "脅威リストを使ったGuardDutyの検出テスト | DevelopersIO")

このIPリストですが、S3上に置く必要があります。

## 前提2：S3の権限って？
Amazon S3はオブジェクトストレージサービスです。

S3にアクセスするためには、適切なアクセス権限の付与が必要です。
権限付与の方法はいくつかあり、次のエントリがわかりやすいです。

[S3のアクセスコントロールが多すぎて訳が解らないので整理してみる | DevelopersIO](https://dev.classmethod.jp/articles/s3-acl-wakewakame/ "S3のアクセスコントロールが多すぎて訳が解らないので整理してみる | DevelopersIO")

今回は、GuardDutyがS3に置いたIPリストにアクセスするために、どのような権限が必要か検証します。

# 検証
次の流れで検証します。途中、別シェルで環境を分けます。

1. 準備
    1. S3バケットを作成して、IPリストをアップロード
    2. GuardDutyの有効化
    3. IAMロールを最小権限で作成
2. 検証
    1. IAMロールでIPリスト設定（別シェルで実行）
    2. 失敗したら、CloudTrailで失敗したAPIを確認
    3. IAMロールに失敗したAPIの許可を追加
    4. 再度、IAMロールでIPリスト設定（別シェルで実行）
    5. 新たに許可されたAPIの実行内容を確認

なお、今回使用するIPリストは、脅威とみなすIPアドレスのリストとなります。
信頼するIPアドレスについても、恐らく同様の結果になるはずです。（試してない）

## 検証環境
1. 作業するIAMユーザには`AdministratorAccess`ポリシーをアタッチしてください。
2. 次のすべてのツールを使います。
    - AWS-CLI
    - jq
3. AWS-CLIの操作については、POSIXシェル上で実施してください。


## 1. 準備

### 1-1. S3バケットを作成して、IPリストをアップロード
脅威とみなすIPアドレスのリストとして、`ThreatIntelSet.txt`を作成します。

``` ThreatIntelSet.txt
XXX.XXX.XXX.XXX
```

バケットを作成して、IPリストをアップロードします。

``` bash
$ BUCKETNAME=guard-duty-iplist-test-$(date '+%Y%m%d-%H%M%S')
$ aws s3 mb s3://$BUCKETNAME
make_bucket: guard-duty-iplist-test-20211120-141946
$ aws s3 cp ThreatIntelSet.txt s3://$BUCKETNAME/
upload: ./ThreatIntelSet.txt to s3://guard-duty-iplist-test-20211120-141946/ThreatIntelSet.txt
```

別シェルでの作業で、バケット名を使うため、バケット名をファイルに出力します。

``` bash
$ echo $BUCKETNAME > BucketName.txt
```

確認はしませんが、この時点でS3へのアクセス権限は何も設定されていません。
作業中のIAMユーザがこのS3バケットにアクセスできるのは、`AdministratorAccess`をアタッチしているからです。

### 1-2. GuardDutyの有効化
GuardDutyを有効にします。
ちなみに、GuardDutyはアカウント・リージョンで高々一つしか有効にできません。すでに有効になっている場合には失敗します。もし失敗したら、`delete-detector`で削除できます。

``` bash
$ aws guardduty create-detector --enable \
        --finding-publishing-frequency FIFTEEN_MINUTES \
        --query 'DetectorId' --output text > DetectorId.txt
```

別シェルでの作業で、GuardDutyのIDを使うため、ファイルに出力します。

### 1-3. IAMロールを最小権限で作成
GuardDutyにIPリストを設定するためのIAMロールを、最小権限で作成します。

ここでは2つのファイルを使用します。
- `assume-role-policy.json` : IAMロールの信頼ポリシー ※要編集
- `policy-guardduty-ip-list.json` : IAMロールのインラインポリシーに定義するIAMポリシー

`assume-role-policy.json`を編集して、`your-account-id`を、ご自身のアカウントIDに置き換え、保存してください。

``` json:assume-role-policy.json
{
    "Version": "2012-10-17",
    "Statement": {
      "Effect": "Allow",
      "Principal": {
          "AWS": "your-account-id"
      },
      "Action": "sts:AssumeRole"   
    }
}
```

`policy-guardduty-ip-list.json`で許可されているAPIはGuardDutyに関するものだけですね。

``` json:policy-guardduty-ip-list.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "guardduty:*ThreatIntelSet*"
            ],
            "Resource": "*"
        }
    ]
}
```

IAMロールを作成して、上記のポリシーを付与します。

``` bash
$ aws iam create-role --role-name role-guard-duty \
    --assume-role-policy-document file://assume-role-policy.json > /dev/null
$ aws iam put-role-policy --role-name role-guard-duty \
    --policy-name policy-guardduty-ip-list \
    --policy-document file://policy-guardduty-ip-list.json
```

さらに、一時的な認証情報を取得し、別シェルでの作業するためにファイルに出力します。
この認証情報の有効期限は1時間なので、もし有効期限が切れたら、もう一度`assume-role`して認証情報を取得しなおしてください。

``` bash
$ aws sts assume-role \
    --role-session-name role-session-guardduty-test \
    --role-arn $( \
        aws iam get-role --role-name role-guard-duty \
            --query 'Role.Arn' --output text \
    ) > assume.txt
```

## 2. 検証
### 2-1. IAMロールでIPリスト設定（別シェルで実行）
今まで、`AdministratorAccess`がアタッチされたユーザで作業してきましたが、
暫くの間、最小権限のIAMロールで作業するために、別のシェルで作業していきます。
今までの`AdministratorAccess`のシェルはまた後で使うので閉じずに置いておいてください。

別のシェルで、一時的な認証情報を環境変数に設定します。
これで最小権限のIAMロールで作業可能になります。（`aws sts get-caller-identity`で確認してみてください）

``` bash
$ assume=$(cat assume.txt)
$ export \
    AWS_ACCESS_KEY_ID=$(echo $assume | jq -r '.Credentials.AccessKeyId') \
    AWS_SECRET_ACCESS_KEY=$(echo $assume | jq -r '.Credentials.SecretAccessKey') \
    AWS_SESSION_TOKEN=$(echo $assume | jq -r '.Credentials.SessionToken')
```

さて！それでは、最小権限のIAMロールでIPリストの設定を試みましょう。
ここで使うのは、`guardduty:CreateThreatIntelSet` APIです。

``` bash
$ BUCKETNAME=$(cat BucketName.txt)
$ DetectorId=$(cat DetectorId.txt)
$ aws guardduty create-threat-intel-set --detector-id $DetectorId \
    --name ThreatIntelSet --activate --format TXT \
    --location s3://$BUCKETNAME/ThreatIntelSet.txt

An error occurred (BadRequestException) when calling the CreateThreatIntelSet operation: The request was rejected because you do not have the required iam:PutRolePolicy permission.
```

*失敗！*親切なことに、エラーメッセージに足りない権限が書いてありますね。
`iam:PutRolePolicy` APIの権限が不足しているみたいです。

### 2-2. CloudTrailで失敗したAPIを確認
一度、`AdministratorAccess`のシェルに戻って、失敗の原因を詳しく調査します。
一時的な認証情報のシェルは、あとでまた使うので閉じずに置いておいてください。

`AdministratorAccess`のシェルで、CloudTrailのイベントを出力します。

``` bash
$ aws cloudtrail lookup-events --lookup-attributes \ 
    AttributeKey=Username,AttributeValue=role-session-guardduty-test \
    > cloudtrail-events.json
```

cloudtrail-events.txtを開いて、実行時間付近のイベントを探しましょう……。
次のような、`iam:PutRolePolicy` APIのイベントがありました。

``` cloudtrail-events(抜粋).json
        {
            "EventId": "81f77b0d-0e9f-4ad7-ad4a-9b8d5ced458a",
            "EventName": "PutRolePolicy",
            "ReadOnly": "false",
            "AccessKeyId": "XXXXXXXXXXXXXXXX",
            "EventTime": "2021-11-20T22:34:33+09:00",
            "EventSource": "iam.amazonaws.com",
            "Username": "role-session-guardduty-test",
            "Resources": [],
            "CloudTrailEvent": "＜省略：イベント内容＞"
        }
``` 

`CloudTrailEvent`アイテムに詳しいイベントの内容が書いてあります。

``` json:CloudTrailEventの内容
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "XXXXXXXXXXXX:role-session-guardduty-test",
        "arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/role-guard-duty/role-session-guardduty-test",
        "accountId": "XXXXXXXXXXXX",
        "accessKeyId": "XXXXXXXXXXXX",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "XXXXXXXXXXXX",
                "arn": "arn:aws:iam::XXXXXXXXXXXX:role/role-guard-duty",
                "accountId": "XXXXXXXXXXXX",
                "userName": "role-guard-duty"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-11-20T13:27:19Z",
                "mfaAuthenticated": "false"
            }
        },
        "invokedBy": "guardduty.amazonaws.com"
    },
    "eventTime": "2021-11-20T13:34:33Z",
    "eventSource": "iam.amazonaws.com",
    "eventName": "PutRolePolicy",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "guardduty.amazonaws.com",
    "userAgent": "guardduty.amazonaws.com",
    "errorCode": "AccessDenied",
    "errorMessage": "User: arn:aws:sts::XXXXXXXXXXXX:assumed-role/role-guard-duty/role-session-guardduty-test is not authorized to perform: iam:PutRolePolicy on resource: role AWSServiceRoleForAmazonGuardDuty",
    "requestParameters": null,
    "responseElements": null,
    "requestID": "XXXXXXXXXXXX",
    "eventID": "XXXXXXXXXXXX",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "XXXXXXXXXXXX",
    "eventCategory": "Management"
}
```

ここから、検証のポイントになります。

APIを発行した者は、`userIdentity.invokedBy`/`sourceIPAddress`/`userAgent`から、GuardDutyと推測できます。
GuardDutyが引き受けている権限は`arn:aws:sts::XXXXXXXXXXXX:assumed-role/role-guard-duty/role-session-guardduty-test`ですが、これは先ほど作成したIAMロールの一時的な認証情報ですね。

一つ目のポイントは、このように、GuardDutyは`guardduty:CreateThreatIntelSet` APIを発行したユーザの権限を引き受けて、別のAPI（`iam:PutRolePolicy`）を発行するということです。
なので、`guardduty:CreateThreatIntelSet` APIを発行するユーザには、GuardDutyが発行したいAPIの権限も付与しなければいけないのです。

さらに、`errorMessage`がAWS-CLIの出力したものと異なり、`AWSServiceRoleForAmazonGuardDuty`と書いてあります。

2つ目のポイントは、GuardDutyが、`iam:PutRolePolicy` APIで、何かしらの権限を付与したかった対象は、`AWSServiceRoleForAmazonGuardDuty`というロールということです。

さて、`AWSServiceRoleForAmazonGuardDuty`とは、何なのか？

答えは、GuardDutyのサービスロールですね。
サービスロールとは、AWSサービスが引き受けるIAMロールのことです。
GuardDutyは固定で`AWSServiceRoleForAmazonGuardDuty`を使います。

`AdministratorAccess`のシェルで、GuardDutyの詳細を取得すると、
サービスロールが`AWSServiceRoleForAmazonGuardDuty`となっていることを確認できます。

``` bash
$ DetectorId=$(cat DetectorId.txt)
$ aws guardduty get-detector --detector-id $DetectorId
{
    "CreatedAt": "2021-11-20T14:45:09.083Z",
    "FindingPublishingFrequency": "FIFTEEN_MINUTES",
    "ServiceRole": "arn:aws:iam::XXXXXXXXXXXX:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty",
    "Status": "ENABLED",
    "UpdatedAt": "2021-11-20T14:45:09.083Z",
    "DataSources": {
        "CloudTrail": {
            "Status": "ENABLED"
        },
        "DNSLogs": {
            "Status": "ENABLED"
        },
        "FlowLogs": {
            "Status": "ENABLED"
        },
        "S3Logs": {
            "Status": "ENABLED"
        }
    },
    "Tags": {}
}
```

### 2-3. IAMロールに失敗したAPIの許可を追加
IAMロールに`iam:PutRolePolicy` APIの許可を追加します。

`AdministratorAccess`のシェルで、`policy-guardduty-ip-list.json`を編集して、`policy-guardduty-ip-list-v2.json`を作成します。

``` diff:policy-guardduty-ip-list-v2.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "guardduty:*ThreatIntelSet*"
            ],
            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "iam:PutRolePolicy"
+            ],
+            "Resource": "arn:aws:iam::*:role/aws-service-role/guardduty.amazonaws.com/AWSServiceRoleForAmazonGuardDuty"
        }
    ]
}
```

IAMロールに編集したポリシーを付与します。

``` bash
$ aws iam put-role-policy --role-name role-guard-duty \
    --policy-name policy-guardduty-ip-list \
    --policy-document file://policy-guardduty-ip-list-v2.json
```

### 2-4. 再度、IAMロールでIPリスト設定（別シェルで実行）
一時的な認証情報のシェルに移って、もう一度、IAMロールでIPリストの設定を試みます。
`AdministratorAccess`のシェルはまた後で使うので閉じずに置いておいてください。

レッツ・リトライ！

```
$ aws guardduty create-threat-intel-set --detector-id $DetectorId \
    --name ThreatIntelSet --activate --format TXT \
    --location s3://$BUCKETNAME/ThreatIntelSet.txt
{
    "ThreatIntelSetId": "XXXXXXXXXXXXXXXXXXXX"
}
```

正常にIPリストを設定できました！

### 2-5. 新たに許可されたAPIの実行内容を確認
`AdministratorAccess`のシェルに戻って、新たに許可した`iam:PutRolePolicy` APIで、どんな内容のIAMポリシーを付与しているのか確認します。

`AdministratorAccess`のシェルで、CloudTrailのイベントを出力します。

``` bash
$ aws cloudtrail lookup-events --lookup-attributes \ 
    AttributeKey=Username,AttributeValue=role-session-guardduty-test \
    | jq '.Events[] | select(.EventName == "PutRolePolicy")' \
    > cloudtrail-events-v2.json
```

新しいイベントが増えています。

``` cloudtrail-events-v2(抜粋).json
        {
            "EventId": "c62d89db-46f3-4538-b345-a13c71a44893",
            "EventName": "PutRolePolicy",
            "ReadOnly": "false",
            "AccessKeyId": "XXXXXXXXXXXXXXXX",
            "EventTime": "2021-11-20T22:37:15+09:00",
            "EventSource": "iam.amazonaws.com",
            "Username": "role-session-guardduty-test",
            "Resources": [
                {
                    "ResourceType": "AWS::IAM::Policy",
                    "ResourceName": "d4be9ec85d6cd89f8b08bb31a23570c8"
                },
                {
                    "ResourceType": "AWS::IAM::Role",
                    "ResourceName": "AWSServiceRoleForAmazonGuardDuty"
                }
            ],
            "CloudTrailEvent": "＜省略：イベント内容＞"
        }
```


``` json:CloudTrailEventの内容
{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "AROAYTUJN4U3EHCPQK73U:role-session-guardduty-test",
        "arn": "arn:aws:sts::XXXXXXXXXXXXXXXX:assumed-role/role-guard-duty/role-session-guardduty-test",
        "accountId": "XXXXXXXXXXXXXXXX",
        "accessKeyId": "XXXXXXXXXXXXXXXX",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "AROAYTUJN4U3EHCPQK73U",
                "arn": "arn:aws:iam::XXXXXXXXXXXXXXXX:role/role-guard-duty",
                "accountId": "XXXXXXXXXXXXXXXX",
                "userName": "role-guard-duty"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2021-11-20T10:39:14Z",
                "mfaAuthenticated": "false"
            }
        },
        "invokedBy": "guardduty.amazonaws.com"
    },
    "eventTime": "2021-11-20T11:07:33Z",
    "eventSource": "iam.amazonaws.com",
    "eventName": "PutRolePolicy",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "guardduty.amazonaws.com",
    "userAgent": "guardduty.amazonaws.com",
    "requestParameters": {
        "roleName": "AWSServiceRoleForAmazonGuardDuty",
        "policyName": "XXXXXXXXXXXXXXXXXXXX",
        "policyDocument": "{"Version":"2012-10-17","Statement":[{"Sid":"1","Effect":"Allow","Action":["s3:GetObject"],"Resource":["arn:aws:s3: : :guard-duty-iplist-test-20211120-141946/ThreatIntelSet.txt"]}]}"
    },
    "responseElements": null,
    "requestID": "36291f18-4a00-4a1b-9b15-06cc736fac3f",
    "eventID": "cc0b380b-2bdf-48b4-9974-020774ea92e9",
    "readOnly": false,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "XXXXXXXXXXXXXXXX",
    "eventCategory": "Management"
}
```

requestParametersにAPIのパラメータが記録されています。
さらにその中の、policyDocumentの内容を展開します。（json入れ子ひどい……）

``` json:policyDocumentの内容
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3: : :guard-duty-iplist-test-20211120-141946/ThreatIntelSet.txt"
            ]
        }
    ]
}
```

`s3:GetObject` APIを許可しています。しかも、Getする対象は`ThreatIntelSet.txt`ですね。
このポリシーをGuardDutyのサービスロール（`AWSServiceRoleForAmazonGuardDuty`）に付与することで、S3上のIPリストへのアクセスを実現しているんですね。

以上で、IPリスト設定のために、IPリストを保持するS3バケットへのアクセス権限の設定方法がわかりましたね。

## 余談
ちなみに、サービスロール（`AWSServiceRoleForAmazonGuardDuty`）に`s3:GetObject` APIの許可があれば、別にIAMロールに`iam:PutRolePolicy` APIは必要ないんじゃ……と思い試しましたが、ダメでした。

サービスロール（`AWSServiceRoleForAmazonGuardDuty`）は直接編集不可能なので、
上記手順をすべて実施した後に、もう一度、手順1-3と手順2-1だけを実施して、試せます。
結果は、同じで`iam:PutRolePolicy` APIが許可されていないと怒られますね。

`iam:PutRolePolicy`はもう必要ない手順なんですけどね。

これだと、S3のバケットポリシーに、サービスロール（`AWSServiceRoleForAmazonGuardDuty`）を指定してもダメでしょうね。（試していない）

# さいごに
実のところ、公式ドキュメントに必要な権限が書かれています。

- [信頼されている IP リストと脅威リストの操作 - Amazon GuardDuty](https://docs.aws.amazon.com/ja_jp/guardduty/latest/ug/guardduty_upload-lists.html#upload-permissions "信頼されている IP リストと脅威リストの操作 - Amazon GuardDuty")
- [GuardDuty の信頼できる IP アドレスリストをセットアップする方法を教えてください。](https://aws.amazon.com/jp/premiumsupport/knowledge-center/guardduty-trusted-ip-list/ "GuardDuty の信頼できる IP アドレスリストをセットアップする方法を教えてください。")

`iam:DeleteRolePolicy` APIも許可すると、しっかり追加したポリシーを掃除してくれるみたいです。