diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..0bca29a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +addons: + apt: + packages: + - git + - make + - curl + +install: + - make init + +script: + - make terraform:install + - make terraform:get-plugins + - make terraform:get-modules + - make terraform:lint + - make terraform:validate diff --git a/LICENSE b/LICENSE index 8dada3e..63ac84e 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2017 Cloud Posse, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b0f7470 --- /dev/null +++ b/Makefile @@ -0,0 +1,6 @@ +SHELL := /bin/bash + +-include $(shell curl -sSL -o .build-harness "https://git.io/build-harness"; echo .build-harness) + +lint: + $(SELF) terraform:install terraform:get-modules terraform:get-plugins terraform:lint terraform:validate diff --git a/README.md b/README.md new file mode 100644 index 0000000..770e4b9 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ +# terraform-aws-cloudwatch-flow-logs [![Build Status](https://travis-ci.org/cloudposse/terraform-aws-cloudwatch-flow-logs.svg)](https://travis-ci.org/cloudposse/terraform-aws-cloudwatch-flow-logs) + +Terraform module for enabling [`flow logs`](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/flow-logs.html) for `vpc` and `subnets`. + +## Usage + +```terraform +module "flow_logs" { + source = "git::https://github.com/cloudposse/terraform-aws-cloudwatch-flow-logs.git?ref=master" + vpc_id = "${var.vpc_id}" + namespace = "${var.namespace}" + stage = "${var.stage}" +} +``` +## Inputs + +| Name | Default | Description | Required | +|:----------------------|:--------------------------------------:|:--------------------------------------------------------------------------------------------------------|:--------:| +| `namespace` | `` | Namespace (e.g. `cp` or `cloudposse`) | Yes | +| `stage` | `` | Stage (e.g. `prod`, `dev`, `staging`) | Yes | +| `name` | `` | Name (e.g. `bastion` or `db`) | No | +| `delimiter` | `-` | Delimiter to be used between `name`, `namespace`, `stage`, etc. | No | +| `attributes` | `[]` | Additional attributes (e.g. `policy` or `role`) | No | +| `tags` | `{}` | Additional tags (e.g. `map("BusinessUnit","XYZ")` | No | +| `vpc_id` | `` | ID of VPC | Yes | +| `subnet_ids` | `[]` | IDs of subnets | No | +| `eni_ids` | `[]` | IDs of ENIs | No | +| `region` | `` | AWS region e.g. `us-central-1` | No | +| `retention_in_days` | `30` | Number of days you want to retain log events in the log group | No | +| `traffic_type` | `ALL` | Type of traffic to capture. Valid values: ACCEPT,REJECT, ALL | No | +| `shard_count` | `1` | Number of shards that the stream will use | No | +| `retention_period` | `48` | Length of time data records are accessible after they are added to the stream | No | +| `shard_level_metrics` | `[ "IncomingBytes", "OutgoingBytes",]` | List of shard-level CloudWatch metrics which can be enabled for the stream | No | +| `encryption_type` | `NONE` | GUID for the customer-managed KMS key to use for encryption. The only acceptable values are NONE or KMS | No | +| `kms_key_id` | `` | ID of KMS key | No | +| `filter_pattern` | `"[]"` | Valid CloudWatch Logs filter pattern for subscribing to a filtered stream of log events | No | +| `enabled` | `true` | Set to false to prevent the module from creating anything | No | + +## Outputs + +| Name | Description | +|:----------------------|:---------------------------| +| `log_group_arn` | ARN of the log group | +| `eni_flow_ids` | Flow Log IDs of ENIs | +| `subnet_flow_ids` | Flow Log IDs of subnets | +| `vpc_flow_id` | Flow Log IDs of VPC | +| `kinesis_arn` | ARN of Stream | +| `kinesis_id` | Stream ID | +| `kinesis_name` | Stream name | +| `kinesis_shard_count` | Count of Shards for Stream | + +## License + +Apache 2 License. See [`LICENSE`](LICENSE) for full details. diff --git a/iam.tf b/iam.tf new file mode 100644 index 0000000..eabd1af --- /dev/null +++ b/iam.tf @@ -0,0 +1,79 @@ +data "aws_iam_policy_document" "log_assume" { + count = "${var.enabled == "true" ? 1 : 0}" + + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["vpc-flow-logs.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "log" { + statement { + actions = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + ] + + resources = [ + "*", + ] + } +} + +resource "aws_iam_role_policy" "log" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.vpc_label.id}" + role = "${aws_iam_role.log.id}" + policy = "${data.aws_iam_policy_document.log.json}" +} + +resource "aws_iam_role" "log" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.vpc_label.id}" + assume_role_policy = "${data.aws_iam_policy_document.log_assume.json}" +} + +data "aws_iam_policy_document" "kinesis_assume" { + statement { + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["logs.${length(var.region) > 0 ? var.region: data.aws_region.default.name}.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "kinesis" { + statement { + actions = [ + "kinesis:PutRecord*", + "kinesis:DescribeStream", + "kinesis:ListStreams", + ] + + resources = [ + "${aws_kinesis_stream.default.arn}", + ] + } +} + +resource "aws_iam_role" "kinesis" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.kinesis_label.id}" + assume_role_policy = "${data.aws_iam_policy_document.kinesis_assume.json}" +} + +resource "aws_iam_role_policy" "kinesis" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.vpc_label.id}" + role = "${aws_iam_role.kinesis.id}" + policy = "${data.aws_iam_policy_document.kinesis.json}" +} diff --git a/kinesis.tf b/kinesis.tf new file mode 100644 index 0000000..b219084 --- /dev/null +++ b/kinesis.tf @@ -0,0 +1,41 @@ +module "kinesis_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.1" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + attributes = "${compact(concat(var.attributes, list("kinesis")))}" + tags = "${var.tags}" + enabled = "${var.enabled}" +} + +module "subscription_filter_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.1" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + attributes = "${compact(concat(var.attributes, list("filter")))}" + tags = "${var.tags}" + enabled = "${var.enabled}" +} + +resource "aws_kinesis_stream" "default" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.kinesis_label.id}" + shard_count = "${var.shard_count}" + retention_period = "${var.retention_period}" + shard_level_metrics = "${var.shard_level_metrics}" + encryption_type = "${var.encryption_type}" + kms_key_id = "${var.kms_key_id}" + tags = "${module.kinesis_label.tags}" +} + +resource "aws_cloudwatch_log_subscription_filter" "default" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.subscription_filter_label.id}" + log_group_name = "${aws_cloudwatch_log_group.default.name}" + filter_pattern = "${var.filter_pattern}" + destination_arn = "${aws_kinesis_stream.default.arn}" + role_arn = "${aws_iam_role.kinesis.arn}" +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..10fdf93 --- /dev/null +++ b/main.tf @@ -0,0 +1,67 @@ +data "aws_region" "default" { + current = "true" +} + +module "log_group_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.1" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + attributes = "${compact(concat(var.attributes, list("log")))}" + tags = "${var.tags}" + enabled = "${var.enabled}" +} + +module "vpc_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.1" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + attributes = "${compact(concat(var.attributes, list("vpc")))}" + tags = "${var.tags}" + enabled = "${var.enabled}" +} + +module "subnet_label" { + source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=tags/0.3.1" + namespace = "${var.namespace}" + name = "${var.name}" + stage = "${var.stage}" + delimiter = "${var.delimiter}" + attributes = "${compact(concat(var.attributes, list("subnets")))}" + tags = "${var.tags}" + enabled = "${var.enabled}" +} + +resource "aws_cloudwatch_log_group" "default" { + count = "${var.enabled == "true" ? 1 : 0}" + name = "${module.log_group_label.id}" + retention_in_days = "${var.retention_in_days}" + tags = "${module.log_group_label.tags}" +} + +resource "aws_flow_log" "vpc" { + count = "${var.enabled == "true" ? 1 : 0}" + log_group_name = "${aws_cloudwatch_log_group.default.name}" + iam_role_arn = "${aws_iam_role.log.arn}" + vpc_id = "${var.vpc_id}" + traffic_type = "${var.traffic_type}" +} + +resource "aws_flow_log" "subnets" { + count = "${var.enabled == "true" ? length(compact(var.subnet_ids)) : 0}" + log_group_name = "${aws_cloudwatch_log_group.default.name}" + iam_role_arn = "${aws_iam_role.log.arn}" + subnet_id = "${element(compact(var.subnet_ids), count.index)}" + traffic_type = "${var.traffic_type}" +} + +resource "aws_flow_log" "eni" { + count = "${var.enabled == "true" ? length(compact(var.eni_ids)) : 0}" + log_group_name = "${aws_cloudwatch_log_group.default.name}" + iam_role_arn = "${aws_iam_role.log.arn}" + subnet_id = "${element(compact(var.eni_ids), count.index)}" + traffic_type = "${var.traffic_type}" +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..dee0d37 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,39 @@ +output "log_group_arn" { + value = "${aws_cloudwatch_log_group.default.arn}" + description = "ARN of the log group" +} + +output "vpc_flow_id" { + value = "${aws_flow_log.vpc.id}" + description = "Flow Log IDs of VPCs" +} + +output "subnet_flow_ids" { + value = "${aws_flow_log.subnets.*.id}" + description = "Flow Log IDs of subnets" +} + +output "eni_flow_ids" { + value = "${aws_flow_log.eni.*.id}" + description = "Flow Log IDs of ENIs" +} + +output "kinesis_id" { + value = "${aws_kinesis_stream.default.id}" + description = "Stream ID" +} + +output "kinesis_name" { + value = "${aws_kinesis_stream.default.name}" + description = "Stream name" +} + +output "kinesis_shard_count" { + value = "${aws_kinesis_stream.default.shard_count}" + description = "Count of Shards for Stream" +} + +output "kinesis_arn" { + value = "${aws_kinesis_stream.default.arn}" + description = "ARN of Stream" +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..454729b --- /dev/null +++ b/variables.tf @@ -0,0 +1,102 @@ +variable "name" { + default = "" + description = "Name (e.g. `bastion` or `db`)" +} + +variable "namespace" { + description = "Namespace (e.g. `cp` or `cloudposse`)" + type = "string" +} + +variable "stage" { + description = "Stage (e.g. `prod`, `dev`, `staging`)" + type = "string" +} + +variable "delimiter" { + type = "string" + default = "-" + description = "Delimiter to be used between `name`, `namespace`, `stage`, etc." +} + +variable "attributes" { + type = "list" + default = [] + description = "Additional attributes (e.g. `policy` or `role`)" +} + +variable "tags" { + type = "map" + default = {} + description = "Additional tags (e.g. map(`BusinessUnit`,`XYZ`)" +} + +variable "region" { + description = "AWS region" + default = "" +} + +variable "retention_in_days" { + description = "Number of days you want to retain log events in the log group" + default = "30" +} + +variable "traffic_type" { + description = "Type of traffic to capture. Valid values: ACCEPT,REJECT, ALL" + default = "ALL" +} + +variable "vpc_id" { + description = "ID of VPC" +} + +variable "subnet_ids" { + description = "IDs of subnets" + type = "list" + default = [] +} + +variable "eni_ids" { + description = "IDs of ENIs" + type = "list" + default = [] +} + +variable "shard_count" { + description = "Number of shards that the stream will use" + default = "1" +} + +variable "retention_period" { + description = "Length of time data records are accessible after they are added to the stream" + default = "48" +} + +variable "shard_level_metrics" { + description = "List of shard-level CloudWatch metrics which can be enabled for the stream" + + default = [ + "IncomingBytes", + "OutgoingBytes", + ] +} + +variable "encryption_type" { + description = "GUID for the customer-managed KMS key to use for encryption. The only acceptable values are NONE or KMS" + default = "NONE" +} + +variable "filter_pattern" { + description = "Valid CloudWatch Logs filter pattern for subscribing to a filtered stream of log events" + default = "[version, account, eni, source, destination, srcport, destport, protocol, packets, bytes, windowstart, windowend, action, flowlogstatus]" +} + +variable "kms_key_id" { + description = "ID of KMS key" + default = "" +} + +variable "enabled" { + default = "true" + description = "Set to false to prevent the module from creating anything" +}