Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HOLD: Support for guardduty exports #63

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}