Skip to content

Commit

Permalink
support guard duty exports
Browse files Browse the repository at this point in the history
  • Loading branch information
pjdufour-dds committed Nov 5, 2020
1 parent 778fe87 commit e698b81
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ module "aws_logs" {
| allow\_cloudwatch | Allow Cloudwatch service to export logs to bucket. | `bool` | `false` | no |
| allow\_config | Allow Config service to log to bucket. | `bool` | `false` | no |
| allow\_elb | Allow ELB service to log to bucket. | `bool` | `false` | no |
| allow\_guardduty | Allow GuardDuty service to log to bucket. | `bool` | `false` | no |
| allow\_nlb | Allow NLB service to log to bucket. | `bool` | `false` | no |
| allow\_redshift | Allow Redshift service to log to bucket. | `bool` | `false` | no |
| cloudtrail\_accounts | List of accounts for CloudTrail logs. By default limits to the current account. | `list(string)` | `[]` | no |
Expand All @@ -125,6 +126,7 @@ module "aws_logs" {
| elb\_accounts | List of accounts for ELB logs. By default limits to the current account. | `list(string)` | `[]` | no |
| elb\_logs\_prefix | S3 prefix for ELB logs. | `string` | `"elb"` | no |
| force\_destroy | A bool that indicates all objects (including any locked objects) should be deleted from the bucket so the bucket can be destroyed without error. | `bool` | `false` | no |
| guardduty\_logs\_prefixes | S3 key prefixes for GuardDuty logs. | `list(string)` | <pre>[<br> "guardduty"<br>]</pre> | no |
| nlb\_account | Account for NLB logs. By default limits to the current account. | `string` | `""` | no |
| nlb\_logs\_prefixes | S3 key prefixes for NLB logs. | `list(string)` | <pre>[<br> "nlb"<br>]</pre> | no |
| redshift\_logs\_prefix | S3 prefix for RedShift logs. | `string` | `"redshift"` | no |
Expand Down
11 changes: 11 additions & 0 deletions examples/guardduty/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module "aws_logs" {
source = "../../"

s3_bucket_name = var.test_name
guardduty_logs_prefixes = var.guardduty_logs_prefixes
region = var.region
allow_guardduty = true
default_allow = false

force_destroy = var.force_destroy
}
15 changes: 15 additions & 0 deletions examples/guardduty/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
variable "test_name" {
type = string
}

variable "region" {
type = string
}

variable "force_destroy" {
type = bool
}

variable "guardduty_logs_prefixes" {
type = list(string)
}
66 changes: 66 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ locals {

config_logs_path = var.config_logs_prefix == "" ? "AWSLogs" : "${var.config_logs_prefix}/AWSLogs"

# Config does a writability check by writing to key "[prefix]/AWSLogs/[accountId]/Config/ConfigWritabilityCheckFile".
# When there is an oversize configuration item change notification, Config will write the notification to S3 at the path.
# Therefore, you cannot limit the policy to the region.
# For example:
# config/AWSLogs/[accountId]/Config/global/[year]/[month]/[day]/
# OversizedChangeNotification/AWS::IAM::Policy/
# [accountId]_Config_global_ChangeNotification_AWS::IAM::Policy_[resourceId]_[timestamp]_[configurationStateId].json.gz
# Therefore, do not extend the resource path to include the region as shown in the AWS Console.
config_resources = sort(formatlist("${local.bucket_arn}/${local.config_logs_path}/%s/Config/*", local.config_accounts))

#
Expand Down Expand Up @@ -137,6 +145,33 @@ locals {
redshift_principal = "arn:${data.aws_partition.current.partition}:iam::${data.aws_redshift_service_account.main.id}:user/logs"

redshift_resource = "${local.bucket_arn}/${var.redshift_logs_prefix}/*"

#
# GuardDuty locals
#

# doesn't support logging to multiple accounts
# supports logging to multiple prefixes
guardduty_effect = var.default_allow || var.allow_guardduty ? "Allow" : "Deny"

# if the list of prefixes contains "", set an append_root_prefix flag
guardduty_include_root_prefix = contains(var.guardduty_logs_prefixes, "") ? true : false

# create a list of paths, but remove any prefixes containing "" using compact
guardduty_logs_path_temp = formatlist("%s/AWSLogs", compact(var.guardduty_logs_prefixes))

# now append an "AWSLogs" path to the list if guardduty_include_root_prefix is true
guardduty_logs_path = local.guardduty_include_root_prefix ? concat(local.guardduty_logs_path_temp, ["AWSLogs"]) : local.guardduty_logs_path_temp

# GuardDuty does a writability check using the [prefix]/AWSLogs path,
# rather than the full path as shown in the AWS Console.
# For example, here is the error you would receive if you used the full path in the bucket policy:
# Failed to configure export options because GuardDuty
# does not have permission to the KMS key, the S3 bucket, or the specified location in the bucke

# finally, format the full final resources ARN list
guardduty_resources = sort(formatlist("${local.bucket_arn}/%s/*", local.guardduty_logs_path))

}

#
Expand Down Expand Up @@ -325,6 +360,37 @@ data "aws_iam_policy_document" "main" {
resources = [local.bucket_arn]
}

#
# GuardDuty bucket policies
#

statement {
sid = "guardduty-logs-get-location"
effect = local.guardduty_effect
principals {
type = "Service"
identifiers = ["guardduty.amazonaws.com"]
}
actions = ["s3:GetBucketLocation"]
resources = [local.bucket_arn]
}

statement {
sid = "guardduty-logs-put-object"
effect = local.guardduty_effect
principals {
type = "Service"
identifiers = ["guardduty.amazonaws.com"]
}
actions = ["s3:PutObject"]
resources = local.guardduty_resources
condition {
test = "StringEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}
}

#
# Enforce TLS requests only
#
Expand Down
36 changes: 36 additions & 0 deletions test/terraform_aws_logs_guardduty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package test

import (
"fmt"
"strings"
"testing"

"github.com/gruntwork-io/terratest/modules/aws"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/gruntwork-io/terratest/modules/terraform"
test_structure "github.com/gruntwork-io/terratest/modules/test-structure"
)

func TestTerraformAwsLogsGuardDuty(t *testing.T) {
t.Parallel()

tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, "../", "examples/guardduty")
testName := fmt.Sprintf("terratest-aws-logs-%s", strings.ToLower(random.UniqueId()))
awsRegion := "us-west-2"

terraformOptions := &terraform.Options{
TerraformDir: tempTestFolder,
Vars: map[string]interface{}{
"region": awsRegion,
"test_name": testName,
"force_destroy": true,
"guardduty_logs_prefixes": []string{testName},
},
EnvVars: map[string]string{
"AWS_DEFAULT_REGION": awsRegion,
},
}

defer terraform.Destroy(t, terraformOptions)
terraform.InitAndApply(t, terraformOptions)
}
12 changes: 12 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ variable "allow_redshift" {
type = bool
}

variable "allow_guardduty" {
description = "Allow GuardDuty service to log to bucket."
default = false
type = bool
}

variable "create_public_access_block" {
description = "Whether to create a public_access_block restricting public access to the bucket."
default = true
Expand Down Expand Up @@ -153,3 +159,9 @@ variable tags {
default = {}
description = "A mapping of tags to assign to the logs bucket. Please note that tags with a conflicting key will not override the original tag."
}

variable "guardduty_logs_prefixes" {
description = "S3 key prefixes for GuardDuty logs."
default = ["guardduty"]
type = list(string)
}

0 comments on commit e698b81

Please sign in to comment.