AWS

【AWS】GuardDuty Malware Protection for Amazon S3のコストが85%削減!Terraformで使ってみる!

tiga

はじめに

最近、GuardDuty Malware Protection for Amazon S3のスキャンしたデータ量に発生するコストが最大85%削減されました。

AWS公式ドキュメント
Amazon GuardDuty Malware Protection for S3 announces price reduction
Amazon GuardDuty Malware Protection for S3 announces price reduction

今回は、そのGuardDuty Malware Protection for Amazon S3をTerraformで使ってみました!

Amazon GuardDuty Malware Protection for S3 の概要

Amazon GuardDuty Malware Protection for Amazon S3 は、S3 にアップロードされた新規オブジェクトを自動スキャンし、マルウェアを検出する AWS のセキュリティ機能です。特定のバケットやプレフィックスを対象に設定でき、検出結果は GuardDuty Findings や CloudWatch メトリクスなどで確認することが可能です!

クウォータ制限

今回機能等を調査して一番気をつけないといけない部分だと感じた部分はクウォータです。
S3にオブジェクトをアップロードする際は、ここの制限に気をつけてお使いください!

クォータ名AWS デフォルトのクォータ値調整可能説明
S3 オブジェクトの最大サイズ5 GBいいえスキャン対象の S3 オブジェクトの最大サイズ。
アーカイブバイトの抽出5 GBいいえアーカイブから抽出して分析できるデータの最大量。超過分はスキップ。
アーカイブファイルの抽出1,000いいえ抽出・分析可能なアーカイブ内のファイル数。超過分はスキップ。
アーカイブの最大深度5いいえ抽出可能なネストされたアーカイブの最大レベル。超過分はスキップ。
最大保護バケット25いいえMalware Protection for S3 を有効にできる S3 バケット数(リージョンごと)。
AWS公式ドキュメント
Malware Protection for S3 のクォータ
Malware Protection for S3 のクォータ

作成したリソース

大きく分けて二つのリソースで、オブジェクト格納用S3バケットとそのバケットに対するAmazon GuardDuty Malware Protection for S3を作成しました。それぞれに紐づく設定等は以下のようになります。

Amazon S3

S3バケットポリシーでは、以下のDenyをしています。

  • マルウェア検出(THREATS_FOUND)タグが付いたオブジェクトの取得を拒否
  • HTTPS 以外でのアクセスを拒否

Amazon GuardDuty Malware Protection for S3 を利用すると、S3 バケット内のオブジェクトを自動的にスキャンし、マルウェアが検出された場合にタグ(GuardDutyMalwareScanStatus=THREATS_FOUND)が付与されます。

このタグが付いたオブジェクトへのアクセスを防ぐために、S3 バケットポリシーを設定し、s3:GetObject アクションを拒否することで、マルウェアと判定されたファイルのダウンロードをブロックできます。これにより、感染ファイルが意図せず利用されるリスクを軽減し、セキュリティを強化できます。

他にもスキャン結果に応じてオブジェクトにタグが付与されます!

ステータス説明
NO_THREATS_FOUND脅威は検出されませんでした。
THREATS_FOUND脅威が検出されました。
UNSUPPORTEDスキャンがスキップされました(パスワード保護やクォータ制限など)。
ACCESS_DENIEDアクセス権限が不足し、スキャンできませんでした。
FAILED内部エラーによりスキャンが失敗しました。
AWS公式ドキュメント
S3 オブジェクトマルウェアスキャンの取りうる結果ステータス値
S3 オブジェクトマルウェアスキャンの取りうる結果ステータス値

Amazon GuardDuty Malware Protection for S3

紐付けている権限はコンソール上で作成する際に自動付与されるIAMポリシーを参考にしました!

file

Terraformフォルダ構成はこちら

├── guardduty_maluware_s3
│   ├── backend.tf
│   ├── data.tf
│   ├── locals.tf
│   ├── main.tf
│   ├── provider.tf
│   └── variables.tf
├── modules
│   ├── guardduty_s3_malware_protection
│   │   ├── main.tf
│   │   └── variables.tf
│   └── s3
│       ├── main.tf
│       ├── output.tf
│       └── variables.tf

上記からリソース作成しているmain.tfとポリシーを定義しているdata.tfのファイルを記載いたします。

main.tf

1module "s3_script_bucket" {
2  source = "../modules/s3"
3  bucket_name = local.s3_script_bucket
4}
5
6resource "aws_s3_bucket_policy" "main" {
7  bucket = module.s3_script_bucket.name
8  policy = data.aws_iam_policy_document.s3_script_bucket_policy.json
9}
10
11module "malware_protection_s3_script_bucket" {
12  depends_on = [aws_iam_role.guardduty_malware_protection_role]
13  source = "../modules/guardduty_s3_malware_protection"
14  iam_role_arn = aws_iam_role.guardduty_malware_protection_role.arn
15  plan_name = local.s3_script_bucket_malware_plan_name
16  bucket_name = module.s3_script_bucket.name
17}
18
19resource "aws_iam_role" "guardduty_malware_protection_role" {
20  name = "guardduty-malware-protection-role"
21  assume_role_policy = data.aws_iam_policy_document.guardduty_malware_protection_assume_role_policy.json
22}
23
24resource "aws_iam_policy" "guardduty_malware_protection_policy" {
25  name = "guardduty-malware-protection-policy"
26  policy = data.aws_iam_policy_document.guardduty_malware_protection_iam_policy.json
27}
28
29resource "aws_iam_role_policy_attachment" "guardduty_malware_protection_role_policy_attachment_1" {
30  depends_on = [aws_iam_role.guardduty_malware_protection_role, aws_iam_policy.guardduty_malware_protection_policy]
31  role = aws_iam_role.guardduty_malware_protection_role.name
32  policy_arn = aws_iam_policy.guardduty_malware_protection_policy.arn
33}

data.tf

1data "aws_caller_identity" "current" {}
2
3data "aws_iam_policy_document" "s3_script_bucket_policy" {
4  statement {
5    effect = "Deny"
6    actions = [
7      "s3:GetObject"
8    ]
9    resources = [
10      "${module.s3_script_bucket.arn}/*",
11      "${module.s3_script_bucket.arn}"
12    ]
13    principals {
14      type        = "AWS"
15      identifiers = ["*"]
16    }
17    condition {
18      test     = "StringEquals"
19      variable = "s3:ExistingObjectTag/GuardDutyMalwareScanStatus"
20      values   = ["THREATS_FOUND"]
21    }
22  }
23
24  statement {
25    effect = "Deny"
26    actions = [
27      "s3:*"
28    ]
29    resources = [
30      "${module.s3_script_bucket.arn}/*",
31      "${module.s3_script_bucket.arn}"
32    ]
33    principals {
34      type        = "AWS"
35      identifiers = ["*"]
36    }
37    condition {
38      test     = "Bool"
39      variable = "aws:SecureTransport"
40      values   = ["false"]
41    }
42  }
43}
44
45data "aws_iam_policy_document" "guardduty_malware_protection_assume_role_policy" {
46    statement {
47        effect = "Allow"
48        actions = ["sts:AssumeRole"]
49        principals {
50            type = "Service"
51            identifiers = ["malware-protection-plan.guardduty.amazonaws.com"]
52        }
53        condition {
54            test = "StringEquals"
55            values = [local.aws_account]
56            variable = "aws:SourceAccount"
57        }
58    }
59}
60
61data "aws_iam_policy_document" "guardduty_malware_protection_iam_policy" {
62  statement {
63    effect = "Allow"
64    actions = [
65      "events:PutRule"
66    ]
67    resources = [
68      "arn:aws:events:${local.aws_region}:${local.aws_account}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*"
69    ]
70    condition {
71      test     = "StringEquals"
72      variable = "events:ManagedBy"
73      values   = ["malware-protection-plan.guardduty.amazonaws.com"]
74    }
75    condition {
76      test = "ForAllValues:StringEquals"
77      variable = "events:source"
78      values = ["aws.s3"]
79    }
80    condition {
81      test = "ForAllValues:StringEquals"
82      variable = "events:detail-type"
83      values = ["Object Created", "AWS API Call via CloudTrail"]
84    }
85    condition {
86      test = "Null"
87      variable = "events:source"
88      values = ["false"]
89    }
90    condition {
91      test = "Null"
92      variable = "events:detail-type"
93      values = ["false"]
94    }
95  }
96
97  statement {
98    effect = "Allow"
99    actions = [
100      "events:DeleteRule",
101      "events:PutTargets",
102      "events:RemoveTargets"
103    ]
104    resources = [
105      "arn:aws:events:${local.aws_region}:${local.aws_account}:rule/DO-NOT-DELETE-AmazonGuardDutyMalwareProtectionS3*"
106    ]
107    condition {
108      test     = "StringEquals"
109      variable = "events:ManagedBy"
110      values   = ["malware-protection-plan.guardduty.amazonaws.com"]
111    }
112  }
113
114  statement {
115    effect = "Allow"
116    actions = [
117      "s3:PutBucketNotification",
118      "s3:GetBucketNotification"
119    ]
120    resources = [
121      "arn:aws:s3:::${module.s3_script_bucket.name}"
122    ]
123    condition {
124      test     = "StringEquals"
125      variable = "aws:ResourceAccount"
126      values   = [local.aws_account]
127    }
128  }
129
130  statement {
131    effect = "Allow"
132    actions = [
133      "s3:GetObjectTagging",
134      "s3:GetObjectVersionTagging",
135      "s3:PutObjectTagging",
136      "s3:PutObjectVersionTagging"
137    ]
138    resources = [
139      "arn:aws:s3:::${module.s3_script_bucket.name}/*"
140    ]
141    condition {
142      test     = "StringEquals"
143      variable = "aws:ResourceAccount"
144      values   = [local.aws_account]
145    }
146  }
147
148  statement {
149    effect = "Allow"
150    actions = [
151      "s3:PutObject"
152    ]
153    resources = [
154      "arn:aws:s3:::${module.s3_script_bucket.name}/malware-protection-resource-validation-object"
155    ]
156    condition {
157      test     = "StringEquals"
158      variable = "aws:ResourceAccount"
159      values   = [local.aws_account]
160    }
161  }
162
163  statement {
164    effect = "Allow"
165    actions = [
166      "s3:ListBucket"
167    ]
168    resources = [
169      "arn:aws:s3:::${module.s3_script_bucket.name}"
170    ]
171    condition {
172      test     = "StringEquals"
173      variable = "aws:ResourceAccount"
174      values   = [local.aws_account]
175    }
176  }
177
178  statement {
179    effect = "Allow"
180    actions = [
181      "s3:GetObject",
182      "s3:GetObjectVersion"
183    ]
184    resources = [
185      "arn:aws:s3:::{module.s3_script_bucket.name}/*"
186    ]
187    condition {
188      test     = "StringEquals"
189      variable = "aws:ResourceAccount"
190      values   = [local.aws_account]
191    }
192  }
193}

試してみる

それでは、実際に安全なデータとEICARテストファイルをS3に格納して、Amazon GuardDuty Malware Protection for S3の動作を見ていきます。

※EICAR (European Institute for Computer Antivirus Research) テストファイル は、ウイルス対策ソフトの動作確認用に作られたテストファイルです。このファイルは 実際のマルウェアではなく、ウイルス対策ソフトが正常に動作しているかを確認するためのもので、多くのウイルス対策ソフトがこれを「テストウイルス」として検出するように設計されています。

事前準備

CloudShellにて、テストファイル(.zip)の作成とEICARテストZipファイル(.zip)のダウンロードを実行します。

# テストファイル(.zip)の作成

$ mkdir test
$ cd test
$ touch test.json
$ cd ..
$ zip -r test.zip test

# EICARテストZipファイル(.zip)のダウンロード

$ wget https://secure.eicar.org/eicar_com.zip

S3にアップロード&S3からダウンロード

ここからはS3にアップロードした後に、S3からダウンロードをしてみます!
S3バケットポリシーにて、Amazon GuardDuty Malware Protection for S3でマルウェア検出されたオブジェクトはGetObjectできないようにしているため、EICARテストZipファイル(.zip)はS3からダウンロードできなくなっています。

# ダウンロード先のフォルダを作成

mkdir download

# ファイル確認

$ ls -l
total 16
drwxr-xr-x. 2 cloudshell-user cloudshell-user 4096 Feb 18 21:04 download
-rw-r--r--. 1 cloudshell-user cloudshell-user  184 Jan  3 15:29 eicar_com.zip
drwxr-xr-x. 2 cloudshell-user cloudshell-user 4096 Feb 18 20:49 test
-rw-r--r--. 1 cloudshell-user cloudshell-user  316 Feb 18 20:52 test.zip

# テストファイル(.zip)のアップロード&ダウンロード

$ aws s3 cp test.zip s3://<S3バケット名>/test.zip
upload: ./test.zip to s3://<S3バケット名>/test.zip

$ aws s3 cp s3://<S3バケット名>/test.zip ./download
download: s3://<S3バケット名>/test.zip to download/test.zip

# EICARテストZipファイル(.zip)のアップロード&ダウンロード

$ aws s3 cp eicar_com.zip s3://<S3バケット名>/eicar_com.zip
upload: ./eicar_com.zip to s3://<S3バケット名>/eicar_com.zip

$ aws s3 cp s3://<S3バケット名>/eicar_com.zip ./download
fatal error: An error occurred (403) when calling the HeadObject operation: Forbidden

# ダウンロード先のフォルダを確認

$ cd download
$ ls -l
total 4
-rw-r--r--. 1 cloudshell-user cloudshell-user 316 Feb 18 21:19 test.zip

結果確認

先ほどEICARテストZipファイル(.zip)はS3からダウンロードできないことを確認しました。
それぞれのリソースがどのようになっているか確認してみましょう!

オブジェクトのタグ

# テストファイル(.zip)のtag確認

$ aws s3api get-object-tagging --bucket <S3バケット名> --key test.zip
{
    "TagSet": [
        {
            "Key": "GuardDutyMalwareScanStatus",
            "Value": "NO_THREATS_FOUND"
        }
    ]
}

# EICARテストZipファイル(.zip)のtag確認

$ aws s3api get-object-tagging --bucket <S3バケット名> --key eicar_com.zip
{
    "TagSet": [
        {
            "Key": "GuardDutyMalwareScanStatus",
            "Value": "THREATS_FOUND"
        }
    ]
}

GuardDutyの結果確認

file
file

まとめ

今回GuardDuty Malware Protection for Amazon S3を使ってみました。
検出されたオブジェクトをS3バケットポリシーでGetObjectを拒否しましたが、検出スキップされたオブジェクトはどうしようか考えています。CloudWatchメトリクスを使用してアラームを作成して気づけるようにしておくなど、使い方を模索しながら今後使っていこうと思いました!

AUTHOR
tiga
tiga
記事URLをコピーしました