From d47a6f6024f9b6c5d9118ec2df5dec4b3a64072b Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 11:01:48 -0400 Subject: [PATCH 1/8] initial commit --- .github/workflows/pre-commit-check.yaml | 38 +++-- .gitignore | 3 + .pre-commit-config.yaml | 53 ++++-- README.md | 43 ++--- bin/install-macos.sh | 12 +- cloudflareupdater.py | 206 ++++++++++++++++++++++++ examples/basic/README.md | 76 ++++----- examples/basic/main.tf | 6 +- main.tf | 121 +++++++++++++- outputs.tf | 5 - variables.tf | 28 ++++ versions.tf | 3 + 12 files changed, 486 insertions(+), 108 deletions(-) create mode 100644 cloudflareupdater.py create mode 100644 versions.tf diff --git a/.github/workflows/pre-commit-check.yaml b/.github/workflows/pre-commit-check.yaml index ad24bb9..0f5fdaa 100644 --- a/.github/workflows/pre-commit-check.yaml +++ b/.github/workflows/pre-commit-check.yaml @@ -4,19 +4,37 @@ on: push: branches: - master + - develop pull_request: jobs: build: runs-on: macOS-latest steps: - - uses: actions/checkout@v1 - - - name: Install prerequisites - run: | - brew install tfenv tflint terraform-docs pre-commit - pre-commit install - tfenv install - - name: pre-commit run all - run: | - pre-commit run -a + - uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + - name: Install prerequisites + run: ./bin/install-macos.sh + - name: initialize Terraform + run: terraform init --backend=false + - uses: actions/cache@v1 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: | + pre-commit + - name: pre-commit run all + run: | + pre-commit run -a + env: + AWS_DEFAULT_REGION: us-east-1 + SKIP: terraform_tflint_deep + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Apply automatic changes + commit_options: "--no-verify" + # Optional commit user and author settings + commit_user_name: Linter Bot + commit_user_email: noreply@rhythmictech.com + commit_author: Linter Bot diff --git a/.gitignore b/.gitignore index 1fef4ab..632fb62 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ # .tfvars files *.tfvars + +*.zip +tmp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 587ccce..657d914 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,43 @@ repos: -- repo: git://github.com/antonbabenko/pre-commit-terraform - rev: v1.30.0 - hooks: - - id: terraform_fmt - - id: terraform_docs -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.0.0 - hooks: - - id: end-of-file-fixer - - id: trailing-whitespace - - id: no-commit-to-branch + - repo: https://github.com/antonbabenko/pre-commit-terraform + rev: v1.31.0 + hooks: + - id: terraform_docs + args: + - --args=--sort-by-required + - id: terraform_fmt + - id: terraform_tflint + alias: terraform_tflint_deep + name: terraform_tflint_deep + args: + - --args=--deep + - id: terraform_tflint + alias: terraform_tflint_nocreds + name: terraform_tflint_nocreds + - id: terraform_tfsec + - id: terraform_validate + exclude: examples + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.0.0 + hooks: + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: check-symlinks + - id: check-yaml + args: + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + args: + - --fix=lf + - id: no-commit-to-branch + - id: pretty-format-json + args: + - --autofix + - --top-keys=name,Name + - id: trailing-whitespace + args: + - --markdown-linebreak-ext=md + exclude: README.md diff --git a/README.md b/README.md index e80e16f..5d67f2f 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,46 @@ -# terraform-anycloud-template [![](https://github.com/rhythmictech/terraform-anycloud-template/workflows/pre-commit-check/badge.svg)](https://github.com/rhythmictech/terraform-anycloud-template/actions) follow on Twitter -Template repository for terraform modules. Good for any cloud and any provider. +# terraform-aws-cloudflare-restricter [![](https://github.com/rhythmictech/terraform-aws-cloudflare-restricter/workflows/pre-commit-check/badge.svg)](https://github.com/rhythmictech/terraform-aws-cloudflare-restricter/actions) follow on Twitter + +This module will automatically manage the ingress rules for any security groups that are appropriately tagged, only permitting CloudFlare IP addresses. The module will create a Lambda that runs once per day, using the public CloudFlare API for known IP addresses to pull the latest IPs and merge them into the security group. + +By default, the Lambda will update any security group with the tag key `CLOUDFLARE_MANAGED` set to `true`, +though this can be customized. Any existing ingress rules will be removed when this tag key/value match. Since the Lambda only runs once per day, it is recommended that it be manually triggered whenever a new security group is added. ## Example -Here's what using the module will look like +Here's what using the module will look like: + ``` -module "example" { - source = "rhythmictech/terraform-mycloud-mymodule +module "cloudflare-restricter" { + source = "rhythmictech/terraform-aws-cloudflare-restricter } ``` -## About -A bit about this module - ## Requirements -No requirements. +| Name | Version | +|------|---------| +| terraform | >= 0.12.25 | ## Providers -No provider. +| Name | Version | +|------|---------| +| archive | n/a | +| aws | n/a | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| name | Moniker to apply to all resources in the module | `string` | n/a | yes | +| allowed\_ports | Ports to allow traffic from CloudFlare on (recommended to only use 443) | `list(number)` |
[
443
]
| no | +| execution\_expression | cron expression for how frequently rules should be updated | `string` | `"rate(1 day)"` | no | +| name | Moniker to apply to all resources in the module | `string` | `"cloudflare-restricter"` | no | +| tag\_key | Tag key to expect on security groups that will be managed by this module | `string` | `"CLOUDFLARE_MANAGED"` | no | +| tag\_value | Tag value to expect on security groups that will be managed by this module | `string` | `"true"` | no | | tags | User-Defined tags | `map(string)` | `{}` | no | ## Outputs -| Name | Description | -|------|-------------| -| tags\_module | Tags Module in it's entirety | +No output. - -## The Giants underneath this module -- pre-commit.com/ -- terraform.io/ -- github.com/tfutils/tfenv -- github.com/segmentio/terraform-docs diff --git a/bin/install-macos.sh b/bin/install-macos.sh index a9ff748..4bc710b 100755 --- a/bin/install-macos.sh +++ b/bin/install-macos.sh @@ -1,10 +1,18 @@ #!/bin/bash echo 'installing brew packages' -brew install tfenv tflint terraform-docs pre-commit +brew update +brew tap liamg/tfsec +brew install tfenv tflint terraform-docs pre-commit liamg/tfsec/tfsec coreutils +brew upgrade tfenv tflint terraform-docs pre-commit liamg/tfsec/tfsec coreutils echo 'installing pre-commit hooks' pre-commit install +echo 'setting pre-commit hooks to auto-install on clone in the future' +git config --global init.templateDir ~/.git-template +pre-commit init-templatedir ~/.git-template + echo 'installing terraform with tfenv' -tfenv install +tfenv install min-required +tfenv use min-required diff --git a/cloudflareupdater.py b/cloudflareupdater.py new file mode 100644 index 0000000..5aeddf2 --- /dev/null +++ b/cloudflareupdater.py @@ -0,0 +1,206 @@ +import boto3 +import json +import os +import urllib3 +import logging + +logger = logging.getLogger() +logger.setLevel(os.environ.get('LOG_LEVEL', logging.DEBUG)) + +for handler in logger.handlers: + handler.setFormatter(logging.Formatter( + '%(asctime)s [%(levelname)s](%(name)s) %(message)s')) + +for lib_logger in ['botocore', 'boto3', 'urllib3']: + logging.getLogger(lib_logger).setLevel( + os.environ.get('LIBRARY_LOG_LEVEL', logging.ERROR)) + +ec2 = boto3.client('ec2') + +def get_cloudflare_ip_list(): + """ Call the CloudFlare API and return a list of IPs """ + http = urllib3.PoolManager() + response = http.request('GET', 'https://api.cloudflare.com/client/v4/ips') + payload = json.loads(response.data.decode('utf-8')) + logger.info("Retrieved current CloudFlare IPs: %s" % (payload)) + + if 'result' in payload: + return payload['result'] + + raise Exception("Cloudflare response error") + + +def get_aws_security_groups(tag_key, tag_value): + """ Return security groups based on `tag_key=tag_value """ + + response = ec2.describe_security_groups(Filters=[ + { + 'Name': 'tag:{}'.format(tag_key), + 'Values': [ + tag_value, + ] + }]) + + return response['SecurityGroups'] + + +def check_ipv4_rule_exists(rules, address, port): + """ Check if the rule currently exists """ + logger.debug("Looking for %s and port %i" % (address, port)) + for rule in rules: + for ip_range in rule['IpRanges']: + if ip_range['CidrIp'] == address and rule['FromPort'] == port: + return True + + return False + + +def add_ipv4_rule(group, address, port): + """ Add the IP address/port to the security group """ + ec2.authorize_security_group_ingress( + GroupId=group['GroupId'], + IpPermissions=[{ + 'IpProtocol': "tcp", + 'FromPort': port, + 'ToPort': port, + 'IpRanges': [ + { + 'CidrIp': address + }, + ] + }]) + + logger.info("Added %s : %i to %s " % (address, port, group['GroupId'])) + + +def delete_ipv4_rule(group, address, port): + """ Remove the IP address/port from the security group """ + + ec2.revoke_security_group_ingress( + GroupId=group['GroupId'], + IpPermissions=[{ + 'IpProtocol': "tcp", + 'FromPort': port, + 'ToPort': port, + 'IpRanges': [ + { + 'CidrIp': address + }, + ] + }]) + + logger.info("Removed %s : %i from %s " % (address, port, group['GroupId'])) + + +def check_ipv6_rule_exists(rules, address, port): + """ Check if the rule currently exists """ + for rule in rules: + for ip_range in rule['Ipv6Ranges']: + if ip_range['CidrIpv6'] == address and rule['FromPort'] == port: + return True + return False + + +def add_ipv6_rule(group, address, port): + """ Add the IP address/port to the security group """ + ec2.authorize_security_group_ingress( + GroupId=group['GroupId'], + IpPermissions=[{ + 'IpProtocol': "tcp", + 'FromPort': port, + 'ToPort': port, + 'Ipv6Ranges': [ + { + 'CidrIpv6': address + }, + ] + }]) + + logger.info("Added %s : %i to %s " % (address, port, group['GroupId'])) + + +def delete_ipv6_rule(group, address, port): + """ Remove the IP address/port from the security group """ + + ec2.revoke_security_group_ingress( + GroupId=group['GroupId'], + IpPermissions=[{ + 'IpProtocol': "tcp", + 'FromPort': port, + 'ToPort': port, + 'Ipv6Ranges': [ + { + 'CidrIpv6': address + }, + ] + }]) + + logger.info("Removed %s : %i from %s " % (address, port, group['GroupId'])) + + +def update_security_group_policies(ip_addresses): + """ Update Information of Security Groups """ + logger.info("Checking policies of Security Groups") + + ports = list(map(int, os.environ['PORTS_LIST'].split(","))) + if not ports: + ports = [443] + + logger.debug("Will allow traffic on ports %s" % (ports)) + security_groups = get_aws_security_groups( + os.environ['TAG_KEY'], os.environ['TAG_VALUE']) + + if len(security_groups) == 0: + logger.warn("No security groups matched %s/%s" % ( + os.environ['TAG_KEY'], os.environ['TAG_VALUE'])) + return + + logger.debug("Will scan security groups: %s" % (security_groups)) + + for security_group in security_groups: + logger.info("Processing group %s" % (security_group['GroupId'])) + current_rules = security_group['IpPermissions'] + + # IPv4 + # add new addresses + logger.debug("Checking for rules to add") + for ipv4_cidr in ip_addresses['ipv4_cidrs']: + logger.debug("Looking for %s" % (ipv4_cidr)) + for port in ports: + if not check_ipv4_rule_exists(current_rules, ipv4_cidr, port): + add_ipv4_rule(security_group, ipv4_cidr, port) + + logger.debug("Checking for rules to remove") + # remove old addresses + for port in ports: + for rule in current_rules: + # is it necessary/correct to check both From and To? + if rule['FromPort'] == port and rule['ToPort'] == port: + for ip_range in rule['IpRanges']: + if ip_range['CidrIp'] not in ip_addresses['ipv4_cidrs']: + delete_ipv4_rule( + security_group, ip_range['CidrIp'], port) + + logger.debug("Checking for ipv6 rules to add") + # IPv6 -- because of boto3 syntax, this has to be separate + # add new addresses + for ipv6_cidr in ip_addresses['ipv6_cidrs']: + for port in ports: + if not check_ipv6_rule_exists(current_rules, ipv6_cidr, port): + add_ipv6_rule(security_group, ipv6_cidr, port) + + # remove old addresses + logger.debug("Checking for ipv6 rules to remove") + for port in ports: + for rule in current_rules: + for ip_range in rule['Ipv6Ranges']: + if ip_range['CidrIpv6'] not in ip_addresses['ipv6_cidrs']: + delete_ipv6_rule( + security_group, ip_range['CidrIpv6'], port) + + +def lambda_handler(event, context): + """ AWS Lambda main function """ + + ip_addresses = get_cloudflare_ip_list() + update_security_group_policies(ip_addresses) diff --git a/examples/basic/README.md b/examples/basic/README.md index ac9ffbd..8264399 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -2,58 +2,36 @@ A basic example for this repository ## Code -Look to [main.tf](./main.tf), or be helpful and copy/paste that code here. +``` -## Applying +module "this" { + source = "../.." + + name = "test" +} ``` -> alias tf="terraform" -> tf apply -Apply complete! Resources: 0 added, 0 changed, 0 destroyed. +## Applying +``` +> terraform apply -Outputs: +module.this.aws_iam_role.this: Creating... +module.this.aws_cloudwatch_event_rule.this: Creating... +module.this.aws_iam_policy.this: Creating... +module.this.aws_iam_role.this: Creation complete after 0s [id=test20200604145510752600000002] +module.this.aws_iam_role_policy_attachment.execution: Creating... +module.this.aws_lambda_function.this: Creating... +module.this.aws_cloudwatch_event_rule.this: Creation complete after 0s [id=test-daily] +module.this.aws_iam_policy.this: Creation complete after 0s [id=arn:aws:iam::951703363424:policy/test20200604145510749000000001] +module.this.aws_iam_role_policy_attachment.this: Creating... +module.this.aws_iam_role_policy_attachment.execution: Creation complete after 0s [id=test20200604145510752600000002-20200604145511227700000003] +module.this.aws_iam_role_policy_attachment.this: Creation complete after 0s [id=test20200604145510752600000002-20200604145511332200000004] +module.this.aws_lambda_function.this: Still creating... [10s elapsed] +module.this.aws_lambda_function.this: Creation complete after 14s [id=test-cloudflareupdater] +module.this.aws_lambda_permission.this: Creating... +module.this.aws_cloudwatch_event_target.this: Creating... +module.this.aws_lambda_permission.this: Creation complete after 0s [id=terraform-20200604145524937300000005] +module.this.aws_cloudwatch_event_target.this: Creation complete after 0s [id=test-daily-terraform-20200604145524937300000006] -example = { - "tags_module" = { - "name" = "TEST" - "name32" = "TEST" - "name6" = "TEST" - "namenosymbols" = "TEST" - "tags" = { - "Name" = "TEST" - "terraform_managed" = true - "terraform_module" = "terraform-terraform-tags-1.0.0" - "terraform_root_module" = "." - "terraform_workspace" = "default" - } - "tags_as_list_of_maps" = [ - { - "key" = "Name" - "value" = "TEST" - }, - { - "key" = "terraform_managed" - "value" = true - }, - { - "key" = "terraform_module" - "value" = "terraform-terraform-tags-1.0.0" - }, - { - "key" = "terraform_root_module" - "value" = "." - }, - { - "key" = "terraform_workspace" - "value" = "default" - }, - ] - "tags_no_name" = { - "terraform_managed" = true - "terraform_module" = "terraform-terraform-tags-1.0.0" - "terraform_root_module" = "." - "terraform_workspace" = "default" - } - } -} +Apply complete! Resources: 8 added, 0 changed, 0 destroyed. ``` diff --git a/examples/basic/main.tf b/examples/basic/main.tf index 13ef2c1..659912e 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -1,10 +1,6 @@ -module "example" { +module "this" { source = "../.." name = "test" } - -output "example" { - value = module.example -} diff --git a/main.tf b/main.tf index e66ee89..dad8e02 100644 --- a/main.tf +++ b/main.tf @@ -1,9 +1,118 @@ +data "archive_file" "this" { + type = "zip" + source_file = "${path.module}/cloudflareupdater.py" + output_path = "${path.module}/tmp/cloudflareupdater.zip" +} + +data "aws_iam_policy_document" "assume" { + statement { + actions = [ + "sts:AssumeRole", + ] + + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "this" { + name_prefix = var.name + assume_role_policy = data.aws_iam_policy_document.assume.json +} + +resource "aws_iam_role_policy_attachment" "execution" { + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + role = aws_iam_role.this.name +} + +data "aws_iam_policy_document" "this" { + statement { + actions = [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:RevokeSecurityGroupIngress", + "ec2:DescribeSecurityGroups", + ] + + resources = ["*"] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/${var.tag_key}" + values = [var.tag_value] + } + } + + statement { + actions = ["ec2:DescribeSecurityGroups"] + resources = ["*"] + } +} + +resource "aws_iam_policy" "this" { + name_prefix = var.name + policy = data.aws_iam_policy_document.this.json +} + +resource "aws_iam_role_policy_attachment" "this" { + policy_arn = aws_iam_policy.this.arn + role = aws_iam_role.this.name +} + + +resource "aws_lambda_function" "this" { + filename = data.archive_file.this.output_path + function_name = "${var.name}-cloudflareupdater" + handler = "cloudflareupdater.lambda_handler" + role = aws_iam_role.this.arn + runtime = "python3.7" + source_code_hash = data.archive_file.this.output_base64sha256 + tags = var.tags + timeout = 180 + + environment { + variables = { + TAG_KEY = var.tag_key + TAG_VALUE = var.tag_value + PORTS_LIST = join(",", var.allowed_ports) + + } + } + + lifecycle { + ignore_changes = [ + filename, + last_modified, + ] + } +} + +resource "aws_cloudwatch_event_rule" "this" { + name = "${var.name}-daily" + schedule_expression = var.execution_expression + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_cloudwatch_event_target" "this" { + arn = aws_lambda_function.this.arn + rule = aws_cloudwatch_event_rule.this.name + + lifecycle { + create_before_destroy = true + } +} -module "tags" { - source = "rhythmictech/tags/terraform" - version = "1.0.0" +resource "aws_lambda_permission" "this" { + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.this.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.this.arn - enforce_case = "UPPER" - names = [var.name] - tags = var.tags + lifecycle { + create_before_destroy = true + } } diff --git a/outputs.tf b/outputs.tf index 02513ff..e69de29 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,5 +0,0 @@ - -output "tags_module" { - description = "Tags Module in it's entirety" - value = module.tags -} diff --git a/variables.tf b/variables.tf index dac30d3..630fa7f 100644 --- a/variables.tf +++ b/variables.tf @@ -1,9 +1,37 @@ +######################################## +# General Vars +######################################## + +variable "allowed_ports" { + default = [443] + description = "Ports to allow traffic from CloudFlare on (recommended to only use 443)" + type = list(number) +} + +variable "execution_expression" { + default = "rate(1 day)" + description = "cron expression for how frequently rules should be updated" + type = string +} variable "name" { + default = "cloudflare-restricter" description = "Moniker to apply to all resources in the module" type = string } +variable "tag_key" { + default = "CLOUDFLARE_MANAGED" + description = "Tag key to expect on security groups that will be managed by this module" + type = string +} + +variable "tag_value" { + default = "true" + description = "Tag value to expect on security groups that will be managed by this module" + type = string +} + variable "tags" { default = {} description = "User-Defined tags" diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..161b7f9 --- /dev/null +++ b/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.12.25" +} From 5679f03ab9c3144f7256d6ed604de1207c5ee977 Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 11:48:12 -0400 Subject: [PATCH 2/8] linting per pr feedback --- main.tf | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/main.tf b/main.tf index dad8e02..c5a30ef 100644 --- a/main.tf +++ b/main.tf @@ -31,8 +31,8 @@ data "aws_iam_policy_document" "this" { statement { actions = [ "ec2:AuthorizeSecurityGroupIngress", - "ec2:RevokeSecurityGroupIngress", "ec2:DescribeSecurityGroups", + "ec2:RevokeSecurityGroupIngress" ] resources = ["*"] @@ -60,7 +60,6 @@ resource "aws_iam_role_policy_attachment" "this" { role = aws_iam_role.this.name } - resource "aws_lambda_function" "this" { filename = data.archive_file.this.output_path function_name = "${var.name}-cloudflareupdater" @@ -73,10 +72,9 @@ resource "aws_lambda_function" "this" { environment { variables = { + PORTS_LIST = join(",", var.allowed_ports) TAG_KEY = var.tag_key TAG_VALUE = var.tag_value - PORTS_LIST = join(",", var.allowed_ports) - } } From af57bf3fa1a22205b104effbce2d3af8715d4c01 Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 14:49:43 -0400 Subject: [PATCH 3/8] address pr feedback --- .pre-commit-config.yaml | 2 ++ README.md | 2 +- cloudflareupdater.py | 26 +++++++++++++------------- main.tf | 7 +++---- versions.tf | 2 +- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 657d914..f1d0e05 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,3 +41,5 @@ repos: args: - --markdown-linebreak-ext=md exclude: README.md + - id: check-ast + - id: check-builtin-literals diff --git a/README.md b/README.md index 5d67f2f..7eef96d 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ module "cloudflare-restricter" { | Name | Version | |------|---------| -| terraform | >= 0.12.25 | +| terraform | >= 0.12.19 | ## Providers diff --git a/cloudflareupdater.py b/cloudflareupdater.py index 5aeddf2..4775247 100644 --- a/cloudflareupdater.py +++ b/cloudflareupdater.py @@ -162,14 +162,6 @@ def update_security_group_policies(ip_addresses): current_rules = security_group['IpPermissions'] # IPv4 - # add new addresses - logger.debug("Checking for rules to add") - for ipv4_cidr in ip_addresses['ipv4_cidrs']: - logger.debug("Looking for %s" % (ipv4_cidr)) - for port in ports: - if not check_ipv4_rule_exists(current_rules, ipv4_cidr, port): - add_ipv4_rule(security_group, ipv4_cidr, port) - logger.debug("Checking for rules to remove") # remove old addresses for port in ports: @@ -181,14 +173,16 @@ def update_security_group_policies(ip_addresses): delete_ipv4_rule( security_group, ip_range['CidrIp'], port) - logger.debug("Checking for ipv6 rules to add") - # IPv6 -- because of boto3 syntax, this has to be separate # add new addresses - for ipv6_cidr in ip_addresses['ipv6_cidrs']: + logger.debug("Checking for rules to add") + for ipv4_cidr in ip_addresses['ipv4_cidrs']: + logger.debug("Looking for %s" % (ipv4_cidr)) for port in ports: - if not check_ipv6_rule_exists(current_rules, ipv6_cidr, port): - add_ipv6_rule(security_group, ipv6_cidr, port) + if not check_ipv4_rule_exists(current_rules, ipv4_cidr, port): + add_ipv4_rule(security_group, ipv4_cidr, port) + logger.debug("Checking for ipv6 rules to add") + # IPv6 -- because of boto3 syntax, this has to be separate # remove old addresses logger.debug("Checking for ipv6 rules to remove") for port in ports: @@ -198,6 +192,12 @@ def update_security_group_policies(ip_addresses): delete_ipv6_rule( security_group, ip_range['CidrIpv6'], port) + # add new addresses + for ipv6_cidr in ip_addresses['ipv6_cidrs']: + for port in ports: + if not check_ipv6_rule_exists(current_rules, ipv6_cidr, port): + add_ipv6_rule(security_group, ipv6_cidr, port) + def lambda_handler(event, context): """ AWS Lambda main function """ diff --git a/main.tf b/main.tf index c5a30ef..971a012 100644 --- a/main.tf +++ b/main.tf @@ -18,7 +18,7 @@ data "aws_iam_policy_document" "assume" { } resource "aws_iam_role" "this" { - name_prefix = var.name + name_prefix = "${var.name}-" assume_role_policy = data.aws_iam_policy_document.assume.json } @@ -31,7 +31,6 @@ data "aws_iam_policy_document" "this" { statement { actions = [ "ec2:AuthorizeSecurityGroupIngress", - "ec2:DescribeSecurityGroups", "ec2:RevokeSecurityGroupIngress" ] @@ -51,7 +50,7 @@ data "aws_iam_policy_document" "this" { } resource "aws_iam_policy" "this" { - name_prefix = var.name + name_prefix = "${var.name}-" policy = data.aws_iam_policy_document.this.json } @@ -87,7 +86,7 @@ resource "aws_lambda_function" "this" { } resource "aws_cloudwatch_event_rule" "this" { - name = "${var.name}-daily" + name_prefix = "${var.name}-scheduled-rule" schedule_expression = var.execution_expression lifecycle { diff --git a/versions.tf b/versions.tf index 161b7f9..749d1a2 100644 --- a/versions.tf +++ b/versions.tf @@ -1,3 +1,3 @@ terraform { - required_version = ">= 0.12.25" + required_version = ">= 0.12.19" } From f53cc3c9a28e5aeaa79496b05b5d50916ddcac36 Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 16:06:09 -0400 Subject: [PATCH 4/8] move logging line --- cloudflareupdater.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudflareupdater.py b/cloudflareupdater.py index 4775247..7e8c462 100644 --- a/cloudflareupdater.py +++ b/cloudflareupdater.py @@ -181,7 +181,6 @@ def update_security_group_policies(ip_addresses): if not check_ipv4_rule_exists(current_rules, ipv4_cidr, port): add_ipv4_rule(security_group, ipv4_cidr, port) - logger.debug("Checking for ipv6 rules to add") # IPv6 -- because of boto3 syntax, this has to be separate # remove old addresses logger.debug("Checking for ipv6 rules to remove") @@ -193,6 +192,7 @@ def update_security_group_policies(ip_addresses): security_group, ip_range['CidrIpv6'], port) # add new addresses + logger.debug("Checking for ipv6 rules to add") for ipv6_cidr in ip_addresses['ipv6_cidrs']: for port in ports: if not check_ipv6_rule_exists(current_rules, ipv6_cidr, port): From 7ca09f56d20f119cc10e8a0c1ae15e415a767f56 Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 16:19:38 -0400 Subject: [PATCH 5/8] Update main.tf Co-authored-by: Scott Miller --- main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.tf b/main.tf index 971a012..abc7806 100644 --- a/main.tf +++ b/main.tf @@ -86,7 +86,7 @@ resource "aws_lambda_function" "this" { } resource "aws_cloudwatch_event_rule" "this" { - name_prefix = "${var.name}-scheduled-rule" + name_prefix = "${var.name}-scheduled-rule-" schedule_expression = var.execution_expression lifecycle { From 67634fcc21b7cfd7d2ee95530b390eecf0bf4ffd Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 16:19:44 -0400 Subject: [PATCH 6/8] Update variables.tf Co-authored-by: Scott Miller --- variables.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variables.tf b/variables.tf index 630fa7f..6395394 100644 --- a/variables.tf +++ b/variables.tf @@ -15,7 +15,7 @@ variable "execution_expression" { } variable "name" { - default = "cloudflare-restricter" + default = "cloudflare-restrictor" description = "Moniker to apply to all resources in the module" type = string } From 534748278cbfbfa91abb0b8c2b99f6c0a67f1685 Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 16:21:06 -0400 Subject: [PATCH 7/8] spelling is hard --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7eef96d..4ffaf36 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# terraform-aws-cloudflare-restricter [![](https://github.com/rhythmictech/terraform-aws-cloudflare-restricter/workflows/pre-commit-check/badge.svg)](https://github.com/rhythmictech/terraform-aws-cloudflare-restricter/actions) follow on Twitter +# terraform-aws-cloudflare-restrictor [![](https://github.com/rhythmictech/terraform-aws-cloudflare-restrictor/workflows/pre-commit-check/badge.svg)](https://github.com/rhythmictech/terraform-aws-cloudflare-restrictor/actions) follow on Twitter This module will automatically manage the ingress rules for any security groups that are appropriately tagged, only permitting CloudFlare IP addresses. The module will create a Lambda that runs once per day, using the public CloudFlare API for known IP addresses to pull the latest IPs and merge them into the security group. @@ -9,8 +9,8 @@ though this can be customized. Any existing ingress rules will be removed when t Here's what using the module will look like: ``` -module "cloudflare-restricter" { - source = "rhythmictech/terraform-aws-cloudflare-restricter +module "cloudflare-restrictor" { + source = "rhythmictech/terraform-aws-cloudflare-restrictor" } ``` From a2f0406cfdae5c6617fe309fda6cbbbabd76ad55 Mon Sep 17 00:00:00 2001 From: Cris Daniluk Date: Thu, 4 Jun 2020 16:24:24 -0400 Subject: [PATCH 8/8] update checks --- .github/workflows/pre-commit-check.yaml | 1 + .pre-commit-config.yaml | 22 ++++++++++++++++++++++ README.md | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit-check.yaml b/.github/workflows/pre-commit-check.yaml index 0f5fdaa..de16f60 100644 --- a/.github/workflows/pre-commit-check.yaml +++ b/.github/workflows/pre-commit-check.yaml @@ -31,6 +31,7 @@ jobs: AWS_DEFAULT_REGION: us-east-1 SKIP: terraform_tflint_deep - uses: stefanzweifel/git-auto-commit-action@v4 + if: ${{ failure() }} with: commit_message: Apply automatic changes commit_options: "--no-verify" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f1d0e05..8b81b85 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,6 +3,7 @@ repos: rev: v1.31.0 hooks: - id: terraform_docs + always_run: true args: - --args=--sort-by-required - id: terraform_fmt @@ -15,7 +16,28 @@ repos: alias: terraform_tflint_nocreds name: terraform_tflint_nocreds - id: terraform_tfsec + - repo: local + hooks: - id: terraform_validate + name: terraform_validate + entry: | + bash -c ' + AWS_DEFAULT_REGION=us-east-1 + declare -a DIRS + for FILE in "$@" + do + DIRS+=($(dirname "$FILE")) + done + for DIR in $(printf "%s\n" "${DIRS[@]}" | sort -u) + do + cd $(dirname "$FILE") + terraform init --backend=false + terraform validate . + done + ' + language: system + verbose: true + files: \.tf(vars)?$ exclude: examples - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.0.0 diff --git a/README.md b/README.md index 4ffaf36..8a250d1 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ module "cloudflare-restrictor" { |------|-------------|------|---------|:--------:| | allowed\_ports | Ports to allow traffic from CloudFlare on (recommended to only use 443) | `list(number)` |
[
443
]
| no | | execution\_expression | cron expression for how frequently rules should be updated | `string` | `"rate(1 day)"` | no | -| name | Moniker to apply to all resources in the module | `string` | `"cloudflare-restricter"` | no | +| name | Moniker to apply to all resources in the module | `string` | `"cloudflare-restrictor"` | no | | tag\_key | Tag key to expect on security groups that will be managed by this module | `string` | `"CLOUDFLARE_MANAGED"` | no | | tag\_value | Tag value to expect on security groups that will be managed by this module | `string` | `"true"` | no | | tags | User-Defined tags | `map(string)` | `{}` | no |