Browse Source

Migrates TF11 AWS CIS Hardening Across

Migrates the TF11 CIS hardening into TF12.
Creates a submodule for creating EBS KMS keys.

Notably, does not migrate the logging, as that needs to be redone for
the distributed environment.

Alerts generated here need to be consumed by Splunk or users need to
sign up for the SNS topic. It's possible they could be consolidated.
Fred Damstra 5 years ago
parent
commit
eaf3fd67b0
38 changed files with 2377 additions and 4 deletions
  1. 23 0
      base/account_standards/ebs-kms-key.tf
  2. 75 0
      base/account_standards/files/cloudtrail_status_check.py
  3. 67 0
      base/account_standards/files/password_policy_check.py
  4. 35 0
      base/account_standards/files/root_account_check.py
  5. 75 0
      base/account_standards/files/support_group_check.py
  6. 70 0
      base/account_standards/files/user_policies_check.py
  7. 16 0
      base/account_standards/lambda_policy.tf
  8. 4 0
      base/account_standards/outputs.tf
  9. 74 0
      base/account_standards/section-1_12.tf
  10. 73 0
      base/account_standards/section-1_16.tf
  11. 17 0
      base/account_standards/section-1_17.tfskip
  12. 65 0
      base/account_standards/section-1_22.tf
  13. 105 0
      base/account_standards/section-1_5.tf
  14. 135 0
      base/account_standards/section-2_1.tf.TODO
  15. 410 0
      base/account_standards/section-3.tf
  16. 31 0
      base/account_standards/templates/billing_s3_bucket_policy.json.tpl
  17. 197 0
      base/account_standards/templates/cis_hardening_iam_role_policy_prod.json.tpl
  18. 197 0
      base/account_standards/templates/cis_hardening_iam_role_policy_test.json.tpl
  19. 25 0
      base/account_standards/templates/cloudtrail_cloudwatch_logs_inline_policy_prod.json.tpl
  20. 25 0
      base/account_standards/templates/cloudtrail_cloudwatch_logs_inline_policy_test.json.tpl
  21. 13 0
      base/account_standards/templates/cloudtrail_cloudwatch_logs_role_policy_prod.json.tpl
  22. 13 0
      base/account_standards/templates/cloudtrail_cloudwatch_logs_role_policy_test.json.tpl
  23. 95 0
      base/account_standards/templates/cloudtrail_kms_policy.json.tpl
  24. 30 0
      base/account_standards/templates/cloudtrail_s3_policy.json.tpl
  25. 30 0
      base/account_standards/templates/cloudtrail_s3_policy_prod.json.tpl
  26. 30 0
      base/account_standards/templates/cloudtrail_s3_policy_test.json.tpl
  27. 13 0
      base/account_standards/templates/iam_lambda_assume_role_policy.json.tpl
  28. 21 0
      base/account_standards/templates/lambda_cloudtrail_status_check_policy.json.tpl
  29. 21 0
      base/account_standards/templates/lambda_password_policy_check_policy.json.tpl
  30. 21 0
      base/account_standards/templates/lambda_root_account_check_policy.json.tpl
  31. 23 0
      base/account_standards/templates/lambda_support_group_check_policy.json.tpl
  32. 21 0
      base/account_standards/templates/lambda_user_policies_check_policy.json.tpl
  33. 204 4
      base/account_standards/vars.tf
  34. 2 0
      submodules/kms/ebs-key/README.md
  35. 104 0
      submodules/kms/ebs-key/main.tf
  36. 3 0
      submodules/kms/ebs-key/outputs.tf
  37. 11 0
      submodules/kms/ebs-key/vars.tf
  38. 3 0
      submodules/kms/ebs-key/version.tf

+ 23 - 0
base/account_standards/ebs-kms-key.tf

@@ -0,0 +1,23 @@
+module "ebs_root_encrypt_decrypt" {
+  source = "../../submodules/kms/ebs-key"
+
+  name = "ebs_root_encrypt_decrypt"
+  alias = "alias/ebs_root_encrypt_decrypt"
+  description = "Default key for encrypting and decryption EBS volumes."
+  tags = merge(var.standard_tags, var.tags)
+  key_admin_arns = [ ]
+  key_user_arns = [ ]
+  key_attacher_arns =  [ ]
+  standard_tags = var.standard_tags
+  aws_account_id = var.aws_account_id
+  aws_partition = var.aws_partition
+}
+
+# Note: The following wasn't configured in tf11
+resource "aws_ebs_default_kms_key" "ebs_root_encrypt_decrypt" {
+  key_arn = module.ebs_root_encrypt_decrypt.key_arn
+}
+
+resource "aws_ebs_encryption_by_default" "encryptbydefault" {
+  enabled = true
+}

+ 75 - 0
base/account_standards/files/cloudtrail_status_check.py

@@ -0,0 +1,75 @@
+import os
+import boto3
+
+
+def answer_no(x): return True if str(x).lower() in [
+    '0', 'no', 'false'] else False
+
+
+def answer_yes(x): return True if str(x).lower() in [
+    '1', 'yes', 'true'] else False
+
+
+def send_notifications(message):
+    # TODO
+    return True
+
+
+def is_bucket_not_public(bucket_name):
+    s3 = boto3.client('s3')
+    bucket_acl = s3.get_bucket_acl(Bucket=bucket_name)
+
+    # If there is a permission attached with any value for AllUsers,
+    # it means the bucket is public
+    # We don't need to check if the permission any of
+    # READ|WRITE|READ_ACP|WRITE_ACP|FULL_CONTROL
+    for grantee in bucket_acl['Grants']:
+        if grantee['Grantee']['Type'] == 'Group' \
+                and grantee['Grantee']['URI'] == 'http://acs.amazonaws.com/groups/global/AllUsers':
+            return False
+    return True
+
+
+def lambda_handler(event, context):
+    rc = 1
+    message_body = 'Chekcing trails'
+    print message_body
+
+    cloudtrail = boto3.client('cloudtrail')
+    trails = cloudtrail.describe_trails()
+
+    for trail in trails['trailList']:
+        notification = 'Checking ' + trail['Name']
+        print notification
+        message_body += notification + "\n"
+
+        if trail['IsMultiRegionTrail'] \
+                and ('KmsKeyId' in trail and trail['KmsKeyId'] != '') \
+                and trail['IncludeGlobalServiceEvents'] \
+                and trail['LogFileValidationEnabled']:
+
+            notification = trail['Name'] + ' is OK'
+            print notification
+            message_body += notification + "\n"
+            rc = 0
+        else:
+            notification = trail['Name'] + \
+                ' does not match with the requirements'
+            print notification
+            message_body += notification + "\n"
+
+        if not is_bucket_not_public(trail['S3BucketName']):
+            rc = 1
+            notification = trail['Name'] + \
+                "\'s bucket has public access."
+            print notification
+            message_body += notification + "\n"
+
+    if rc == 1 and ('DRY_RUN' in os.environ and answer_no(os.environ['DRY_RUN'])):
+        send_notifications(message_body)
+        exit(rc)
+
+# if __name__ == "__main__":
+#    event = 1
+#    context = 1
+#    lambda_handler(event, context)

+ 67 - 0
base/account_standards/files/password_policy_check.py

@@ -0,0 +1,67 @@
+import os
+import boto3
+
+
+def send_notifications(message):
+    # TODO
+    return True
+
+
+def lambda_handler(event, context):
+    iam = boto3.client('iam')
+    message_body = ""
+
+    try:
+        policy = iam.get_account_password_policy()
+    except:
+        message_body = 'Account has no password policy'
+        print message_body
+
+    require_uppercase_characters = bool(
+        os.environ['REQUIRE_UPPERCASE_CHARACTERS']) if 'REQUIRE_UPPERCASE_CHARACTERS' in os.environ else True
+    require_lowercase_characters = bool(
+        os.environ['REQUIRE_LOWERCASE_CHARACTERS']) if 'REQUIRE_LOWERCASE_CHARACTERS' in os.environ else True
+    require_symbols = bool(
+        os.environ['REQUIRE_SYMBOLS']) if 'REQUIRE_SYMBOLS' in os.environ else True
+    require_numbers = bool(
+        os.environ['REQUIRE_NUMBERS']) if 'REQUIRE_NUMBERS' in os.environ else True
+    minimum_password_length = int(
+        os.environ['MINIMUM_PASSWORD_LENGTH']) if 'MINIMUM_PASSWORD_LENGTH' in os.environ else 14
+    password_reuse_prevention = int(
+        os.environ['PASSWORD_REUSE_PREVENTION']) if 'PASSWORD_REUSE_PREVENTION' in os.environ else 24
+    max_password_age = int(
+        os.environ['MAX_PASSWORD_AGE']) if 'MAX_PASSWORD_AGE' in os.environ else 90
+    allow_users_to_change_password = bool(
+        os.environ['ALLOW_USERS_TO_CHANGE_PASSWORD']) if 'ALLOW_USERS_TO_CHANGE_PASSWORD' in os.environ else True
+    hard_expiry = bool(os.environ['HARD_EXPIRY']
+                       ) if 'HARD_EXPIRY' in os.environ else True
+
+    if not message_body:
+        if policy['PasswordPolicy']['RequireUppercaseCharacters'] != require_uppercase_characters:
+            message_body += "Require an uppercase letter has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['RequireLowercaseCharacters'] != require_lowercase_characters:
+            message_body += "Require an lowercase letter has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['RequireSymbols'] != require_symbols:
+            message_body += "Require a symbol has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['RequireNumbers'] != require_numbers:
+            message_body += "Require a number has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['MinimumPasswordLength'] != minimum_password_length:
+            message_body += "Minimum password length has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['MaxPasswordAge'] != max_password_age:
+            message_body += "Maximum password age has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['AllowUsersToChangePassword'] != allow_users_to_change_password:
+            message_body += "Allow users to change password has been set incorrectly\n"
+
+        if policy['PasswordPolicy']['HardExpiry'] != hard_expiry:
+            message_body += "Hard password expiry has been set incorrectly\n"
+
+    if message_body:
+        send_notifications(message_body)
+    else:
+        print 'Everything seems fine'

+ 35 - 0
base/account_standards/files/root_account_check.py

@@ -0,0 +1,35 @@
+import boto3
+
+
+def send_notifications(message):
+    # TODO
+    return True
+
+
+def lambda_handler(event, context):
+    iam = boto3.client('iam')
+    message_body = ""
+
+    account_summary = iam.get_account_summary()
+
+    if account_summary['SummaryMap']['AccountAccessKeysPresent'] != 0:
+        notification = "Root account has an access key. It should be removed\n"
+        print notification
+        message_body += notification
+
+    if account_summary['SummaryMap']['AccountMFAEnabled'] != 1:
+        notification = "Root account does not have MFA set up\n"
+
+    # TODO
+    # There will be check if the root account's MFA device is a hardware oneself.
+    # First, I need to have one that I can test while I develop
+
+    if message_body:
+        send_notifications(message_body)
+    else:
+        print 'Everything seems fine'
+
+# if __name__ == "__main__":
+#    event = 1
+#    context = 1
+#    lambda_handler(event, context)

+ 75 - 0
base/account_standards/files/support_group_check.py

@@ -0,0 +1,75 @@
+import os
+import boto3
+
+iam = boto3.client('iam')
+
+
+def answer_no(x): return True if str(x).lower() in [
+    '0', 'no', 'false'] else False
+
+
+def answer_yes(x): return True if str(x).lower() in [
+    '1', 'yes', 'true'] else False
+
+
+def send_notifications(message):
+    # TODO:
+    return True
+
+
+def if_policy_attached_to_any_group(arn):
+    entities = iam.list_entities_for_policy(
+        PolicyArn=arn,
+        EntityFilter='Group',
+    )
+    return entities['PolicyGroups']
+
+
+def if_any_group_has_users(groups):
+    for group in groups:
+        group_detail = iam.get_group(
+            GroupName=group['GroupName'],
+            MaxItems=1,
+        )
+        if len(group_detail['Users']) > 0:
+            return True
+    return False
+
+
+def lambda_handler(event, context):
+    rc = 1
+    message = "Checking if the AWSSupportAccess policy attached to any group"
+    print message
+
+    paginator = iam.get_paginator('list_policies')
+    page_iterator = paginator.paginate()
+    # Filter with JMESPath and find out instances without an IAM Instance profile
+    filtered_iterator = page_iterator.search(
+        'Policies[?PolicyName == `AWSSupportAccess`].Arn')
+
+    for arn in filtered_iterator:
+        groups = if_policy_attached_to_any_group(arn)
+
+        if len(groups) > 0:
+            groups_has_user = if_any_group_has_users(groups)
+            if groups_has_user:
+                notification = 'Everthing is fine.'
+                print notification
+                message += notification
+                rc = 0
+            else:
+                notification = 'None of the groups have user attached'
+                print notification
+                message += notification
+        else:
+            notification = 'AWSSupportAccess is not attached to any group'
+            print notification
+            message += notification + "\n"
+    send_notifications(message)
+    exit(rc)
+
+
+# if __name__ == "__main__":
+#    event = 1
+#    context = 1
+#    lambda_handler(event, context)

+ 70 - 0
base/account_standards/files/user_policies_check.py

@@ -0,0 +1,70 @@
+import os
+import boto3
+
+iam = boto3.client('iam')
+
+
+def answer_no(x): return True if str(x).lower() in [
+    '0', 'no', 'false'] else False
+
+
+def answer_yes(x): return True if str(x).lower() in [
+    '1', 'yes', 'true'] else False
+
+
+def send_notifications(message):
+    # TO DO
+    return True
+
+
+def detach_policies(users):
+    message_body = 'AGGRESSIVE is set to ' + os.environ['AGGRESSIVE'] \
+        if ('AGGRESSIVE' in os.environ and answer_yes(os.environ['AGGRESSIVE'])) \
+        else 'AGGRESSIVE mode is not active'
+    print message_body
+
+    for user, policies in users.iteritems():
+        notification = 'Processing ' + user
+        print notification
+        message_body += notification + "\n"
+        for policy in policies:
+            notification = policy['PolicyName'] + \
+                ' will be detached from the user'
+            print notification
+            message_body += notification + "\n"
+            if ('DRY_RUN' not in os.environ or answer_no(os.environ['DRY_RUN'])) \
+                    and ('AGGRESSIVE' in os.environ and answer_yes(os.environ['AGGRESSIVE'])):
+                iam.detach_user_policy(
+                    UserName=user, PolicyArn=policy['PolicyArn'])
+            else:
+                notification = 'AGREESIVE is not active or DRY_RUN is enabled, so the policy is not removed'
+                print notification
+                message_body += notification + "\n"
+
+    if len(users) > 0 and ('DRY_RUN' not in os.environ or answer_no(os.environ['DRY_RUN'])):
+        send_notifications(message_body)
+    else:
+        print 'DRY_RUN is active and/or nothing to do'
+
+
+def lambda_handler(event, context):
+    users = iam.list_users()
+    user_policies = {}
+
+    for user in users['Users']:
+        attached_policy_list = iam.list_attached_user_policies(
+            UserName=user['UserName'])
+        user_policy_list = iam.list_user_policies(UserName=user['UserName'])
+
+        if len(attached_policy_list['AttachedPolicies']) > 0 \
+                or len(user_policy_list['PolicyNames']) > 0:
+
+            user_policies[user['UserName']] = attached_policy_list['AttachedPolicies'] + \
+                user_policy_list['PolicyNames']
+    detach_policies(user_policies)
+
+
+# if __name__ == "__main__":
+#    event = 1
+#    context = 1
+#    lambda_handler(event, context)

+ 16 - 0
base/account_standards/lambda_policy.tf

@@ -0,0 +1,16 @@
+# main.tf only contains shared resouces across the module for purpose even the best pracites says
+# keep roles as small as possible and have three files main,variables,outputs.tf
+# So, the motivation in here make the code easily readable.
+# You can open the CIS Benchmark and go step by step to verify or understand how
+# the every other section works.
+# Also, another aventage of this is easy to update the module when the benchmark
+# gets any updates
+#
+# So that, we decided to break down the module into files per section.
+
+# every lambda function uses this assume role policy
+data "template_file" "iam_lambda_assume_role_policy" {
+  template = file("${path.module}/templates/iam_lambda_assume_role_policy.json.tpl")
+}
+
+

+ 4 - 0
base/account_standards/outputs.tf

@@ -1,3 +1,7 @@
 output "key_pair_names" {
   value = [ for k, v in aws_key_pair.key_pair : v.key_name ] # Should just be the keys, but that might change
 }
+
+output "kms_key_id" {
+  value = module.ebs_root_encrypt_decrypt.key_arn
+}

+ 74 - 0
base/account_standards/section-1_12.tf

@@ -0,0 +1,74 @@
+# Inactivity check and disable function
+## IAM Policy
+data "template_file" "root_account_check_policy" {
+  template = file("${path.module}/templates/lambda_root_account_check_policy.json.tpl")
+}
+
+resource "aws_iam_role" "root_account_check" {
+  name               = "${var.resource_name_prefix}-root-account-check"
+  path               = "/lambda/"
+  assume_role_policy = data.template_file.iam_lambda_assume_role_policy.rendered
+}
+
+resource "aws_iam_role_policy" "root_account_check" {
+  name   = "${var.resource_name_prefix}-lambda-root-account-check"
+  role   = aws_iam_role.root_account_check.id
+  policy = data.template_file.root_account_check_policy.rendered
+}
+
+## /IAM Policy
+
+## Create the function
+data "archive_file" "root_account_check" {
+  type        = "zip"
+  source_file = "${path.module}/files/root_account_check.py"
+  output_path = "${var.temp_artifacts_dir}/root_account_check.zip"
+}
+
+resource "aws_lambda_function" "root_account_check" {
+  filename         = "${var.temp_artifacts_dir}/root_account_check.zip"
+  function_name    = "${var.resource_name_prefix}-root-account-check"
+  role             = aws_iam_role.root_account_check.arn
+  handler          = "root_account_check.lambda_handler"
+  source_code_hash = data.archive_file.root_account_check.output_base64sha256
+  runtime          = "python2.7"
+  timeout          = var.lambda_timeout
+
+  environment {
+    variables = {
+      DRY_RUN                = var.lambda_dry_run
+      AGGRESSIVE             = var.lambda_aggressive
+      INACTIVITY_LIMIT       = var.lambda_user_inactivity_limit
+      IGNORE_IAM_USER_PREFIX = var.lambda_mfa_checker_user_prefix
+      IGNORE_IAM_USER_SUFFIX = var.lambda_mfa_checker_user_suffix
+    }
+  }
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+## /Create the function
+
+## Schedule the lambda function
+resource "aws_cloudwatch_event_rule" "root_account_check" {
+  name                = "${var.resource_name_prefix}-root-account-check"
+  description         = "disables inactive users"
+  schedule_expression = var.lambda_cron_schedule
+}
+
+resource "aws_cloudwatch_event_target" "root_account_check" {
+  rule      = aws_cloudwatch_event_rule.root_account_check.name
+  target_id = "${var.resource_name_prefix}-root-account-check"
+  arn       = aws_lambda_function.root_account_check.arn
+}
+
+resource "aws_lambda_permission" "root_account_check" {
+  statement_id  = "AllowExecutionFromCloudWatch"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.root_account_check.function_name
+  principal     = "events.amazonaws.com"
+  source_arn    = aws_cloudwatch_event_rule.root_account_check.arn
+}
+
+## /Schedule the lambda function
+# /MFA check and disable function

+ 73 - 0
base/account_standards/section-1_16.tf

@@ -0,0 +1,73 @@
+# AccessKey age check and delete function
+## IAM Policy
+data "template_file" "user_policies_check_policy" {
+  template = file("${path.module}/templates/lambda_user_policies_check_policy.json.tpl")
+}
+
+resource "aws_iam_role" "user_policies_check" {
+  path               = "/lambda/"
+  name               = "${var.resource_name_prefix}-user-policies-check"
+  assume_role_policy = data.template_file.iam_lambda_assume_role_policy.rendered
+}
+
+resource "aws_iam_role_policy" "user_policies_check" {
+  name   = "${var.resource_name_prefix}-lambda-user-policies-check"
+  role   = aws_iam_role.user_policies_check.id
+  policy = data.template_file.user_policies_check_policy.rendered
+}
+
+## /IAM Policy
+
+## Create the function
+data "archive_file" "user_policies_check" {
+  type        = "zip"
+  source_file = "${path.module}/files/user_policies_check.py"
+  output_path = "${var.temp_artifacts_dir}/user_policies_check.zip"
+}
+
+resource "aws_lambda_function" "user_policies_check" {
+  filename         = "${var.temp_artifacts_dir}/user_policies_check.zip"
+  function_name    = "${var.resource_name_prefix}-user-policies-check"
+  role             = aws_iam_role.user_policies_check.arn
+  handler          = "user_policies_check.lambda_handler"
+  source_code_hash = data.archive_file.user_policies_check.output_base64sha256
+  runtime          = "python2.7"
+  timeout          = var.lambda_timeout
+
+  environment {
+    variables = {
+      DRY_RUN                = var.lambda_dry_run
+      AGGRESSIVE             = var.lambda_aggressive
+      IGNORE_IAM_USER_PREFIX = var.lambda_mfa_checker_user_prefix
+      IGNORE_IAM_USER_SUFFIX = var.lambda_mfa_checker_user_suffix
+    }
+  }
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+## /Create the function
+
+## Schedule the lambda function
+resource "aws_cloudwatch_event_rule" "user_policies_check" {
+  name                = "${var.resource_name_prefix}-user-policies-check"
+  description         = "remove expiring access keys"
+  schedule_expression = var.lambda_cron_schedule
+}
+
+resource "aws_cloudwatch_event_target" "user_policies_check" {
+  rule      = aws_cloudwatch_event_rule.user_policies_check.name
+  target_id = "${var.resource_name_prefix}-user-policies-check"
+  arn       = aws_lambda_function.user_policies_check.arn
+}
+
+resource "aws_lambda_permission" "user_policies_check" {
+  statement_id  = "AllowExecutionFromCloudWatch"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.user_policies_check.function_name
+  principal     = "events.amazonaws.com"
+  source_arn    = aws_cloudwatch_event_rule.user_policies_check.arn
+}
+
+## /Schedule the lambda function
+# /AccessKey age check and delete function

+ 17 - 0
base/account_standards/section-1_17.tfskip

@@ -0,0 +1,17 @@
+data "aws_billing_service_account" "main" {}
+
+data "template_file" "billing-s3-bucket-name" {
+  template = "${file("${path.module}/templates/billing-s3-bucket-name.json.tpl")}"
+
+  vars {
+    bucket_name                     = "${lookup(local.workspace-dps-s3-cloudtrail-bucket,terraform.workspace,"")}"
+    aws_billing_service_account_arn = "${data.aws_billing_service_account.main.arn}"
+  }
+}
+
+resource "aws_s3_bucket" "billing_logs" {
+  bucket = "${lookup(local.workspace-dps-s3-cloudtrail-bucket,terraform.workspace,"")}"
+  acl    = "private"
+
+  policy = "${var.billing_s3_bucket_policy}"
+}

+ 65 - 0
base/account_standards/section-1_22.tf

@@ -0,0 +1,65 @@
+# Support group check and delete function
+## IAM Policy
+data "template_file" "support_group_check_policy" {
+  template = file("${path.module}/templates/lambda_support_group_check_policy.json.tpl")
+}
+
+resource "aws_iam_role" "support_group_check" {
+  path               = "/lambda/"
+  name               = "${var.resource_name_prefix}-support-group-check"
+  assume_role_policy = data.template_file.iam_lambda_assume_role_policy.rendered
+}
+
+resource "aws_iam_role_policy" "support_group_check" {
+  name   = "${var.resource_name_prefix}-lambda-support-group-check"
+  role   = aws_iam_role.support_group_check.id
+  policy = data.template_file.support_group_check_policy.rendered
+}
+
+## /IAM Policy
+
+## Create the function
+data "archive_file" "support_group_check" {
+  type        = "zip"
+  source_file = "${path.module}/files/support_group_check.py"
+  output_path = "${var.temp_artifacts_dir}/support_group_check.zip"
+}
+
+resource "aws_lambda_function" "support_group_check" {
+  filename         = "${var.temp_artifacts_dir}/support_group_check.zip"
+  function_name    = "${var.resource_name_prefix}-support-group-check"
+  role             = aws_iam_role.support_group_check.arn
+  handler          = "support_group_check.lambda_handler"
+  source_code_hash = data.archive_file.support_group_check.output_base64sha256
+  runtime          = "python2.7"
+  timeout          = var.lambda_timeout
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+## /Create the function
+
+## Schedule the lambda function
+resource "aws_cloudwatch_event_rule" "support_group_check" {
+  name                = "${var.resource_name_prefix}-support-group-check"
+  description         = "remove expiring access keys"
+  schedule_expression = var.lambda_cron_schedule
+}
+
+resource "aws_cloudwatch_event_target" "support_group_check" {
+  rule      = aws_cloudwatch_event_rule.support_group_check.name
+  target_id = "${var.resource_name_prefix}-support-group-check"
+  arn       = aws_lambda_function.support_group_check.arn
+}
+
+resource "aws_lambda_permission" "support_group_check" {
+  statement_id  = "AllowExecutionFromCloudWatch"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.support_group_check.function_name
+  principal     = "events.amazonaws.com"
+  source_arn    = aws_cloudwatch_event_rule.support_group_check.arn
+}
+
+## /Schedule the lambda function
+# /Support group check and delete function
+

+ 105 - 0
base/account_standards/section-1_5.tf

@@ -0,0 +1,105 @@
+resource "aws_iam_account_password_policy" "cis" {
+  # 1.5
+  require_uppercase_characters = var.iam_require_uppercase_characters
+
+  # 1.6
+  require_lowercase_characters = var.iam_require_lowercase_characters
+
+  # 1.7
+  require_symbols = var.iam_require_symbols
+
+  # 1.8
+  require_numbers = var.iam_require_numbers
+
+  # 1.9
+  minimum_password_length = var.iam_minimum_password_length
+
+  # 1.10
+  password_reuse_prevention = var.iam_password_reuse_prevention
+
+  # 1.11
+  max_password_age = var.iam_max_password_age
+
+  allow_users_to_change_password = var.iam_allow_users_to_change_password
+
+  hard_expiry = var.iam_hard_expiry
+}
+
+# Password policy check function
+## IAM Policy
+data "template_file" "password_policy_check_policy" {
+  template = file("${path.module}/templates/lambda_password_policy_check_policy.json.tpl")
+}
+
+resource "aws_iam_role" "password_policy_check" {
+  name               = "${var.resource_name_prefix}-password-policy-check"
+  path               = "/lambda/"
+  assume_role_policy = data.template_file.iam_lambda_assume_role_policy.rendered
+}
+
+resource "aws_iam_role_policy" "password_policy_check" {
+  name   = "${var.resource_name_prefix}-lambda-password-policy-check"
+  role   = aws_iam_role.password_policy_check.id
+  policy = data.template_file.password_policy_check_policy.rendered
+}
+
+## /IAM Policy
+
+## Create the function
+data "archive_file" "password_policy_check" {
+  type        = "zip"
+  source_file = "${path.module}/files/password_policy_check.py"
+  output_path = "${var.temp_artifacts_dir}/password_policy_check.zip"
+}
+
+resource "aws_lambda_function" "password_policy_check" {
+  filename         = "${var.temp_artifacts_dir}/password_policy_check.zip"
+  function_name    = "${var.resource_name_prefix}-password-policy-check"
+  role             = aws_iam_role.password_policy_check.arn
+  handler          = "password_policy_check.lambda_handler"
+  source_code_hash = data.archive_file.password_policy_check.output_base64sha256
+  runtime          = "python2.7"
+  timeout          = var.lambda_timeout
+
+  environment {
+    variables = {
+      REQUIRE_UPPERCASE_CHARACTERS   = var.iam_require_uppercase_characters
+      REQUIRE_LOWERCASE_CHARACTERS   = var.iam_require_lowercase_characters
+      REQUIRE_SYMBOLS                = var.iam_require_symbols
+      REQUIRE_NUMBERS                = var.iam_require_numbers
+      MINIMUM_PASSWORD_LENGTH        = var.iam_minimum_password_length
+      PASSWORD_REUSE_PREVENTION      = var.iam_password_reuse_prevention
+      MAX_PASSWORD_AGE               = var.iam_max_password_age
+      ALLOW_USERS_TO_CHANGE_PASSWORD = var.iam_allow_users_to_change_password
+      HARD_EXPIRY                    = var.iam_hard_expiry
+    }
+  }
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+## /Create the function
+
+## Schedule the lambda function
+resource "aws_cloudwatch_event_rule" "password_policy_check" {
+  name                = "${var.resource_name_prefix}-password-policy-check"
+  description         = "Check if password policy is in desired state"
+  schedule_expression = var.lambda_cron_schedule
+}
+
+resource "aws_cloudwatch_event_target" "password_policy_check" {
+  rule      = aws_cloudwatch_event_rule.password_policy_check.name
+  target_id = "${var.resource_name_prefix}-password-policy-check"
+  arn       = aws_lambda_function.password_policy_check.arn
+}
+
+resource "aws_lambda_permission" "password_policy_check" {
+  statement_id  = "AllowExecutionFromCloudWatch"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.password_policy_check.function_name
+  principal     = "events.amazonaws.com"
+  source_arn    = aws_cloudwatch_event_rule.password_policy_check.arn
+}
+
+## /Schedule the lambda function
+# /Password policy check function

+ 135 - 0
base/account_standards/section-2_1.tf.TODO

@@ -0,0 +1,135 @@
+# TODO
+# this needs to be split into two modules:
+#   1) Set up the centralized key and s3 bucket
+#   2) Set up the logging from the client
+data "aws_caller_identity" "current" {}
+
+data "template_file" "cloudtrail_kms" {
+  template = file("${path.module}/templates/cloudtrail_kms_policy.json.tpl")
+
+  vars {
+    aws_account_id = data.aws_caller_identity.current.account_id
+  }
+}
+
+resource "aws_kms_key" "cloudtrail" {
+  description             = "Encrypt/Decrypt cloudtrail logs"
+  deletion_window_in_days = 30
+  is_enabled              = true
+  enable_key_rotation     = true
+
+  policy = var.cloudtrail_kms_policy != "" ? var.cloudtrail_kms_policy : data.template_file.cloudtrail_kms.rendered}
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_kms_alias" "cloudtrail" {
+  name          = "alias/${var.resource_name_prefix}-cloudtrail"
+  target_key_id = "${aws_kms_key.cloudtrail.key_id}"
+}
+
+resource "aws_s3_bucket" "dps-mdr-cloudtrail" {
+  bucket = "${lookup(local.workspace-dps-s3-cloudtrail-bucket,terraform.workspace,"")}"
+  acl    = "private"
+  region = "us-east-1"
+  policy = "${file("${path.module}/templates/${lookup(local.workspace-dps-s3-cloudtrail-bucket-policy,terraform.workspace,"")}")}" 
+
+  tags {
+    Billing = "MSSP - MSOC Infrastrucutre"
+  }
+
+}
+
+resource "aws_cloudtrail" "cloudtrail" {
+  name                          = "${var.resource_name_prefix}-trail"
+  s3_bucket_name                = "${lookup(local.workspace-dps-s3-cloudtrail-bucket,terraform.workspace,"")}"
+  is_multi_region_trail         = true
+  include_global_service_events = true
+  enable_log_file_validation    = true
+  kms_key_id                    = "${aws_kms_key.cloudtrail.arn}"
+  cloud_watch_logs_group_arn    = "${aws_cloudwatch_log_group.aws-cis-logs.arn}"
+  cloud_watch_logs_role_arn     = "${aws_iam_role.cloudtrail_cloudwatchlogs_role.arn}"
+
+  event_selector {
+    read_write_type           = "${var.clodtrail_event_selector_type}"
+    include_management_events = true
+
+    data_resource {
+      type   = "AWS::S3::Object"
+      values = ["arn:aws:s3"]
+    }
+
+    data_resource {
+      type   = "AWS::Lambda::Function"
+      values = ["arn:aws:lambda"]
+    }
+  }
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+# CloudTrail check
+## IAM Policy
+data "template_file" "cloudtrail_status_check_policy" {
+  template = "${file("${path.module}/templates/lambda_cloudtrail_status_check_policy.json.tpl")}"
+}
+
+resource "aws_iam_role" "cloudtrail_status_check" {
+  provider           = "aws.iam_admin"
+  name               = "${var.resource_name_prefix}-cloudtrail-status-check"
+  assume_role_policy = "${data.template_file.iam_lambda_assume_role_policy.rendered}"
+}
+
+resource "aws_iam_role_policy" "cloudtrail_status_check" {
+  provider           = "aws.iam_admin"
+  name   = "${var.resource_name_prefix}-lambda-cloudtrail-status-check"
+  role   = "${aws_iam_role.cloudtrail_status_check.id}"
+  policy = "${data.template_file.cloudtrail_status_check_policy.rendered}"
+}
+
+## /IAM Policy
+
+## Create the function
+data "archive_file" "cloudtrail_status_check" {
+  type        = "zip"
+  source_file = "${path.module}/files/cloudtrail_status_check.py"
+  output_path = "${var.temp_artifacts_dir}/cloudtrail_status_check.zip"
+}
+
+resource "aws_lambda_function" "cloudtrail_status_check" {
+  filename         = "${var.temp_artifacts_dir}/cloudtrail_status_check.zip"
+  function_name    = "${var.resource_name_prefix}-cloudtrail-status-check"
+  role             = "${aws_iam_role.cloudtrail_status_check.arn}"
+  handler          = "cloudtrail_status_check.lambda_handler"
+  source_code_hash = "${data.archive_file.cloudtrail_status_check.output_base64sha256}"
+  runtime          = "python2.7"
+  timeout          = "${var.lambda_timeout}"
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+## /Create the function
+
+## Schedule the lambda function
+resource "aws_cloudwatch_event_rule" "cloudtrail_status_check" {
+  name                = "${var.resource_name_prefix}-cloudtrail-status-check"
+  description         = "remove expiring access keys"
+  schedule_expression = "${var.lambda_cron_schedule}"
+}
+
+resource "aws_cloudwatch_event_target" "cloudtrail_status_check" {
+  rule      = "${aws_cloudwatch_event_rule.cloudtrail_status_check.name}"
+  target_id = "${var.resource_name_prefix}-cloudtrail-status-check"
+  arn       = "${aws_lambda_function.cloudtrail_status_check.arn}"
+}
+
+resource "aws_lambda_permission" "cloudtrail_status_check" {
+  statement_id  = "AllowExecutionFromCloudWatch"
+  action        = "lambda:InvokeFunction"
+  function_name = "${aws_lambda_function.cloudtrail_status_check.function_name}"
+  principal     = "events.amazonaws.com"
+  source_arn    = "${aws_cloudwatch_event_rule.cloudtrail_status_check.arn}"
+}
+
+## /Schedule the lambda function
+# /# CloudTrail check

+ 410 - 0
base/account_standards/section-3.tf

@@ -0,0 +1,410 @@
+# TODO: This could probably be centralized?
+resource "aws_sns_topic" "alarms" {
+  name = var.sns_topic_name
+}
+
+resource "aws_sqs_queue" "alarms_sqs" {
+  name = var.sqs_queue_name
+}
+
+resource "aws_sns_topic_subscription" "dps_alarm_target" {
+  topic_arn = aws_sns_topic.alarms.arn
+  protocol  = "sqs"
+  endpoint  = aws_sqs_queue.alarms_sqs.arn
+}
+
+resource "aws_iam_role" "cloudtrail_cloudwatchlogs_role" {
+  name               = "cloudtrail_cloudwatchlogs_role"
+  path               = "/aws_services/"
+  assume_role_policy = file("${path.module}/templates/${lookup(local.workspace-dps-cloudtrail-cloudwatch-logs-role,var.environment,"")}")
+}
+
+resource "aws_iam_role_policy" "cloudtrail_cloudwatch_policy" {
+  name   = "cloudtrail_cloudwatch_policy"
+  role   = aws_iam_role.cloudtrail_cloudwatchlogs_role.id
+  policy = file("${path.module}/templates/${lookup(local.workspace-dps-cloudtrail-cloudwatch-logs-policy,var.environment,"")}")
+}
+
+resource "aws_cloudwatch_log_group" "aws-cis-logs" { 
+  name = var.cloudtrail_log_group_name
+}
+
+resource "aws_cloudwatch_log_metric_filter" "unauthorized_api_calls" {
+  name           = "UnauthorizedAPICalls"
+  pattern        = "{ ($.errorCode = \"*UnauthorizedOperation\") || ($.errorCode = \"AccessDenied*\") }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "UnauthorizedAPICalls"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "unauthorized_api_calls" {
+  alarm_name                = "UnauthorizedAPICalls"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.unauthorized_api_calls.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "50"
+  alarm_description         = "Monitoring unauthorized API calls will help reveal application errors and may reduce time to detect malicious activity."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_log_metric_filter" "no_mfa_console_signin" {
+  name           = "NoMFAConsoleSignin"
+  pattern        = "{ ($.eventName = \"ConsoleLogin\") && ($.additionalEventData.MFAUsed != \"Yes\") }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "NoMFAConsoleSignin"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "no_mfa_console_signin" {
+  alarm_name                = "NoMFAConsoleSignin"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.no_mfa_console_signin.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring for single-factor console logins will increase visibility into accounts that are not protected by MFA."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "root_usage" {
+  name           = "RootUsage"
+  pattern        = "{ $.userIdentity.type = \"Root\" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != \"AwsServiceEvent\" }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "RootUsage"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "root_usage" {
+  alarm_name                = "RootUsage"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.root_usage.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring for root account logins will provide visibility into the use of a fully privileged account and an opportunity to reduce the use of it."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "iam_changes" {
+  name           = "IAMChanges"
+  pattern        = "{($.eventName=DeleteGroupPolicy)||($.eventName=DeleteRolePolicy)||($.eventName=DeleteUserPolicy)||($.eventName=PutGroupPolicy)||($.eventName=PutRolePolicy)||($.eventName=PutUserPolicy)||($.eventName=CreatePolicy)||($.eventName=DeletePolicy)||($.eventName=CreatePolicyVersion)||($.eventName=DeletePolicyVersion)||($.eventName=AttachRolePolicy)||($.eventName=DetachRolePolicy)||($.eventName=AttachUserPolicy)||($.eventName=DetachUserPolicy)||($.eventName=AttachGroupPolicy)||($.eventName=DetachGroupPolicy)}"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "IAMChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "iam_changes" {
+  alarm_name                = "IAMChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.iam_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to IAM policies will help ensure authentication and authorization controls remain intact."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "cloudtrail_cfg_changes" {
+  name           = "CloudTrailCfgChanges"
+  pattern        = "{ ($.eventName = CreateTrail) || ($.eventName = UpdateTrail) || ($.eventName = DeleteTrail) || ($.eventName = StartLogging) || ($.eventName = StopLogging) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "CloudTrailCfgChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "cloudtrail_cfg_changes" {
+  alarm_name                = "CloudTrailCfgChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.cloudtrail_cfg_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to CloudTrail's configuration will help ensure sustained visibility to activities performed in the AWS account."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "console_signin_failures" {
+  name           = "ConsoleSigninFailures"
+  pattern        = "{ ($.eventName = ConsoleLogin) && ($.errorMessage = \"Failed authentication\") }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "ConsoleSigninFailures"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "console_signin_failures" {
+  alarm_name                = "ConsoleSigninFailures"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.console_signin_failures.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring failed console logins may decrease lead time to detect an attempt to brute force a credential, which may provide an indicator, such as source IP, that can be used in other event correlation."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "disable_or_delete_cmk" {
+  name           = "DisableOrDeleteCMK"
+  pattern        = "{ ($.eventSource = kms.amazonaws.com) && (($.eventName = DisableKey) || ($.eventName = ScheduleKeyDeletion)) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "DisableOrDeleteCMK"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "disable_or_delete_cmk" {
+  alarm_name                = "DisableOrDeleteCMK"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.disable_or_delete_cmk.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring failed console logins may decrease lead time to detect an attempt to brute force a credential, which may provide an indicator, such as source IP, that can be used in other event correlation."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "s3_bucket_policy_changes" {
+  name           = "S3BucketPolicyChanges"
+  pattern        = "{ ($.eventSource = s3.amazonaws.com) && (($.eventName = PutBucketAcl) || ($.eventName = PutBucketPolicy) || ($.eventName = PutBucketCors) || ($.eventName = PutBucketLifecycle) || ($.eventName = PutBucketReplication) || ($.eventName = DeleteBucketPolicy) || ($.eventName = DeleteBucketCors) || ($.eventName = DeleteBucketLifecycle) || ($.eventName = DeleteBucketReplication)) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "S3BucketPolicyChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "s3_bucket_policy_changes" {
+  alarm_name                = "S3BucketPolicyChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.s3_bucket_policy_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to S3 bucket policies may reduce time to detect and correct permissive policies on sensitive S3 buckets."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "aws_config_changes" {
+  name           = "AWSConfigChanges"
+  pattern        = "{ ($.eventSource = config.amazonaws.com) && (($.eventName=StopConfigurationRecorder)||($.eventName=DeleteDeliveryChannel)||($.eventName=PutDeliveryChannel)||($.eventName=PutConfigurationRecorder)) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "AWSConfigChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "aws_config_changes" {
+  alarm_name                = "AWSConfigChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.aws_config_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to AWS Config configuration will help ensure sustained visibility of configuration items within the AWS account."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "security_group_changes" {
+  name           = "SecurityGroupChanges"
+  pattern        = "{ ($.eventName = AuthorizeSecurityGroupIngress) || ($.eventName = AuthorizeSecurityGroupEgress) || ($.eventName = RevokeSecurityGroupIngress) || ($.eventName = RevokeSecurityGroupEgress) || ($.eventName = CreateSecurityGroup) || ($.eventName = DeleteSecurityGroup)}"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "SecurityGroupChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "security_group_changes" {
+  alarm_name                = "SecurityGroupChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.security_group_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to security group will help ensure that resources and services are not unintentionally exposed."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "nacl_changes" {
+  name           = "NACLChanges"
+  pattern        = "{ ($.eventName = CreateNetworkAcl) || ($.eventName = CreateNetworkAclEntry) || ($.eventName = DeleteNetworkAcl) || ($.eventName = DeleteNetworkAclEntry) || ($.eventName = ReplaceNetworkAclEntry) || ($.eventName = ReplaceNetworkAclAssociation) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "NACLChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "nacl_changes" {
+  alarm_name                = "NACLChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.nacl_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to NACLs will help ensure that AWS resources and services are not unintentionally exposed."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "network_gw_changes" {
+  name           = "NetworkGWChanges"
+  pattern        = "{ ($.eventName = CreateCustomerGateway) || ($.eventName = DeleteCustomerGateway) || ($.eventName = AttachInternetGateway) || ($.eventName = CreateInternetGateway) || ($.eventName = DeleteInternetGateway) || ($.eventName = DetachInternetGateway) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "NetworkGWChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "network_gw_changes" {
+  alarm_name                = "NetworkGWChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.network_gw_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to network gateways will help ensure that all ingress/egress traffic traverses the VPC border via a controlled path."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "route_table_changes" {
+  name           = "RouteTableChanges"
+  pattern        = "{ ($.eventName = CreateRoute) || ($.eventName = CreateRouteTable) || ($.eventName = ReplaceRoute) || ($.eventName = ReplaceRouteTableAssociation) || ($.eventName = DeleteRouteTable) || ($.eventName = DeleteRoute) || ($.eventName = DisassociateRouteTable) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "RouteTableChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "route_table_changes" {
+  alarm_name                = "RouteTableChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.route_table_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to route tables will help ensure that all VPC traffic flows through an expected path."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}
+
+resource "aws_cloudwatch_log_metric_filter" "vpc_changes" {
+  name           = "VPCChanges"
+  pattern        = "{ ($.eventName = CreateVpc) || ($.eventName = DeleteVpc) || ($.eventName = ModifyVpcAttribute) || ($.eventName = AcceptVpcPeeringConnection) || ($.eventName = CreateVpcPeeringConnection) || ($.eventName = DeleteVpcPeeringConnection) || ($.eventName = RejectVpcPeeringConnection) || ($.eventName = AttachClassicLinkVpc) || ($.eventName = DetachClassicLinkVpc) || ($.eventName = DisableVpcClassicLink) || ($.eventName = EnableVpcClassicLink) }"
+  log_group_name = var.cloudtrail_log_group_name
+
+  metric_transformation {
+    name      = "VPCChanges"
+    namespace = var.alarm_namespace
+    value     = "1"
+  }
+  depends_on = [ aws_cloudwatch_log_group.aws-cis-logs ]
+}
+
+resource "aws_cloudwatch_metric_alarm" "vpc_changes" {
+  alarm_name                = "VPCChanges"
+  comparison_operator       = "GreaterThanOrEqualToThreshold"
+  evaluation_periods        = "1"
+  metric_name               = aws_cloudwatch_log_metric_filter.vpc_changes.id
+  namespace                 = var.alarm_namespace
+  period                    = "300"
+  statistic                 = "Sum"
+  threshold                 = "1"
+  alarm_description         = "Monitoring changes to VPC will help ensure that all VPC traffic flows through an expected path."
+  alarm_actions             = [aws_sns_topic.alarms.arn]
+  insufficient_data_actions = []
+}

+ 31 - 0
base/account_standards/templates/billing_s3_bucket_policy.json.tpl

@@ -0,0 +1,31 @@
+{
+  "Id": "Policy",
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": [
+        "s3:GetBucketAcl",
+        "s3:GetBucketPolicy"
+      ],
+      "Effect": "Allow",
+      "Resource": "arn:aws:s3:::${bucket_name}",
+      "Principal": {
+        "AWS": [
+          "${aws_billing_service_account_arn}"
+        ]
+      }
+    },
+    {
+      "Action": [
+        "s3:PutObject"
+      ],
+      "Effect": "Allow",
+      "Resource": "arn:aws:s3:::${bucket_name}/*",
+      "Principal": {
+        "AWS": [
+          "${aws_billing_service_account_arn}"
+        ]
+      }
+    }
+  ]
+}

+ 197 - 0
base/account_standards/templates/cis_hardening_iam_role_policy_prod.json.tpl

@@ -0,0 +1,197 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+       {   
+            "Effect": "Allow",
+            "Action": [
+                "sqs:ListQueues",
+                "sqs:GetQueueUrl",
+                "sqs:ListDeadLetterSourceQueues",
+                "sqs:ReceiveMessage",
+                "sqs:GetQueueAttributes",
+                "sqs:ListQueueTags",
+                "sqs:CreateQueue",
+                "sqs:SendMessage",
+                "sqs:SetQueueAttributes",
+                "sqs:TagQueue"
+            ],  
+            "Resource": "*" 
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "cloudtrail:StopLogging",
+                "cloudtrail:StartLogging",
+                "cloudtrail:AddTags",
+                "cloudtrail:DeleteTrail",
+                "cloudtrail:UpdateTrail",
+                "cloudtrail:CreateTrail",
+                "cloudtrail:ListTags",
+                "cloudtrail:GetTrailStatus",
+                "cloudtrail:RemoveTags"
+            ],
+            "Resource": "arn:aws:cloudtrail:us-east-1:477548533976:trail/aws-cis-trail*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "cloudtrail:LookupEvents",
+                "cloudtrail:PutEventSelectors",
+                "cloudtrail:ListPublicKeys",
+                "cloudtrail:ListTags",
+                "cloudtrail:GetEventSelectors",
+                "cloudtrail:DescribeTrails"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:DescribeRule",
+                "events:ListRuleNamesByTarget",
+                "events:EnableRule",
+                "events:ListRules",
+                "events:ListTargetsByRule"
+            ],
+            "Resource": "arn:aws:events:us-east-1:477548533976:rule/aws-cis-cloudtrail-status-check"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:PutTargets",
+                "events:PutRule",
+                "events:TestEventPattern"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:DescribeRule",
+                "events:ListRuleNamesByTarget",
+                "events:EnableRule",
+                "events:ListRules",
+                "events:ListTargetsByRule"
+            ],
+            "Resource": [
+                "arn:aws:events:us-east-1:477548533976:rule/aws-cis-password-policy-check",
+                "arn:aws:events:us-east-1:477548533976:rule/aws-cis-root-account-check",
+                "arn:aws:events:us-east-1:477548533976:rule/aws-cis-user-policies-check",
+                "arn:aws:events:us-east-1:477548533976:rule/aws-cis-support-group-check"
+            ]
+        },
+        {   
+            "Effect": "Allow",
+            "Action": [
+                "lambda:GetFunction",
+                "lambda:ListVersionsByFunction",
+                "lambda:GetPolicy"
+            ],  
+            "Resource": [
+                "arn:aws:lambda:us-east-1:477548533976:function:aws-cis-password-policy-check",
+                "arn:aws:lambda:us-east-1:477548533976:function:aws-cis-root-account-check",
+                "arn:aws:lambda:us-east-1:477548533976:function:aws-cis-user-policies-check",
+                "arn:aws:lambda:us-east-1:477548533976:function:aws-cis-support-group-check",
+                "arn:aws:lambda:us-east-1:477548533976:function:aws-cis-cloudtrail-status-check"
+            ]  
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:PutEvents",
+                "events:PutRule",
+                "events:TestEventPattern"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": "kms:*",
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": "kms:*",
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "logs:ListTagsLogGroup",
+                "logs:DisassociateKmsKey",
+                "logs:DeleteSubscriptionFilter",
+                "logs:DescribeLogGroups",
+                "logs:UntagLogGroup",
+                "logs:DeleteLogGroup",
+                "logs:DescribeLogStreams",
+                "logs:DescribeSubscriptionFilters",
+                "logs:DescribeMetricFilters",
+                "logs:DeleteLogStream",
+                "logs:PutLogEvents",
+                "logs:CreateExportTask",
+                "logs:PutMetricFilter",
+                "logs:CreateLogStream",
+                "logs:DeleteMetricFilter",
+                "logs:TagLogGroup",
+                "logs:DeleteRetentionPolicy",
+                "logs:GetLogEvents",
+                "logs:AssociateKmsKey",
+                "logs:FilterLogEvents",
+                "logs:PutSubscriptionFilter",
+                "logs:PutRetentionPolicy"
+            ],
+            "Resource": [
+                "arn:aws:logs:us-east-1:477548533976:log-group:aws-cis-logs*",
+                "arn:aws:logs:us-east-1:477548533976:log-group::log-stream:"
+             ]
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "logs:DeleteResourcePolicy",
+                "logs:DescribeExportTasks",
+                "logs:PutResourcePolicy",
+                "logs:PutDestinationPolicy",
+                "logs:CancelExportTask",
+                "logs:TestMetricFilter",
+                "logs:DeleteDestination",
+                "logs:CreateLogGroup",
+                "logs:DescribeResourcePolicies",
+                "logs:PutDestination",
+                "logs:DescribeDestinations"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "sns:CreatePlatformApplication",
+                "sns:SetSMSAttributes",
+                "sns:ListTopics",
+                "sns:GetPlatformApplicationAttributes",
+                "sns:CreatePlatformEndpoint",
+                "sns:Unsubscribe",
+                "sns:GetSubscriptionAttributes",
+                "sns:ListSubscriptions",
+                "sns:CheckIfPhoneNumberIsOptedOut",
+                "sns:OptInPhoneNumber",
+                "sns:DeleteEndpoint",
+                "sns:SetEndpointAttributes",
+                "sns:ListPhoneNumbersOptedOut",
+                "sns:ListEndpointsByPlatformApplication",
+                "sns:GetEndpointAttributes",
+                "sns:SetSubscriptionAttributes",
+                "sns:DeletePlatformApplication",
+                "sns:SetPlatformApplicationAttributes",
+                "sns:ListPlatformApplications",
+                "sns:GetSMSAttributes"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": "sns:*",
+            "Resource": "arn:aws:sns:us-east-1:477548533976:dps-alarm"
+        }
+    ]
+}

+ 197 - 0
base/account_standards/templates/cis_hardening_iam_role_policy_test.json.tpl

@@ -0,0 +1,197 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+       {
+            "Effect": "Allow",
+            "Action": [
+                "sqs:ListQueues",
+                "sqs:GetQueueUrl",
+                "sqs:ListDeadLetterSourceQueues",
+                "sqs:ReceiveMessage",
+                "sqs:GetQueueAttributes",
+                "sqs:ListQueueTags",
+                "sqs:CreateQueue",
+                "sqs:SendMessage",
+                "sqs:SetQueueAttributes",
+                "sqs:TagQueue"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "cloudtrail:StopLogging",
+                "cloudtrail:StartLogging",
+                "cloudtrail:AddTags",
+                "cloudtrail:DeleteTrail",
+                "cloudtrail:UpdateTrail",
+                "cloudtrail:CreateTrail",
+                "cloudtrail:ListTags",
+                "cloudtrail:GetTrailStatus",
+                "cloudtrail:RemoveTags"
+            ],
+            "Resource": "arn:aws:cloudtrail:us-east-1:527700175026:trail/aws-cis-trail*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "cloudtrail:LookupEvents",
+                "cloudtrail:PutEventSelectors",
+                "cloudtrail:ListPublicKeys",
+                "cloudtrail:ListTags",
+                "cloudtrail:GetEventSelectors",
+                "cloudtrail:DescribeTrails"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:DescribeRule",
+                "events:ListRuleNamesByTarget",
+                "events:EnableRule",
+                "events:ListRules",
+                "events:ListTargetsByRule"
+            ],
+            "Resource": "arn:aws:events:us-east-1:527700175026:rule/aws-cis-cloudtrail-status-check"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:PutTargets",
+                "events:PutRule",
+                "events:TestEventPattern"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:DescribeRule",
+                "events:ListRuleNamesByTarget",
+                "events:EnableRule",
+                "events:ListRules",
+                "events:ListTargetsByRule"
+            ],
+            "Resource": [
+                "arn:aws:events:us-east-1:527700175026:rule/aws-cis-password-policy-check",
+                "arn:aws:events:us-east-1:527700175026:rule/aws-cis-root-account-check",
+                "arn:aws:events:us-east-1:527700175026:rule/aws-cis-user-policies-check",
+                "arn:aws:events:us-east-1:527700175026:rule/aws-cis-support-group-check"
+            ]
+        },
+        {   
+            "Effect": "Allow",
+            "Action": [
+                "lambda:GetFunction",
+                "lambda:ListVersionsByFunction",
+                "lambda:GetPolicy"
+            ],  
+            "Resource": [
+                "arn:aws:lambda:us-east-1:527700175026:function:aws-cis-password-policy-check",
+                "arn:aws:lambda:us-east-1:527700175026:function:aws-cis-root-account-check",
+                "arn:aws:lambda:us-east-1:527700175026:function:aws-cis-user-policies-check",
+                "arn:aws:lambda:us-east-1:527700175026:function:aws-cis-support-group-check",
+                "arn:aws:lambda:us-east-1:527700175026:function:aws-cis-cloudtrail-status-check"
+            ]  
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "events:PutEvents",
+                "events:PutRule",
+                "events:TestEventPattern"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": "kms:*",
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": "kms:*",
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "logs:ListTagsLogGroup",
+                "logs:DisassociateKmsKey",
+                "logs:DeleteSubscriptionFilter",
+                "logs:DescribeLogGroups",
+                "logs:UntagLogGroup",
+                "logs:DeleteLogGroup",
+                "logs:DescribeLogStreams",
+                "logs:DescribeSubscriptionFilters",
+                "logs:DescribeMetricFilters",
+                "logs:DeleteLogStream",
+                "logs:PutLogEvents",
+                "logs:CreateExportTask",
+                "logs:PutMetricFilter",
+                "logs:CreateLogStream",
+                "logs:DeleteMetricFilter",
+                "logs:TagLogGroup",
+                "logs:DeleteRetentionPolicy",
+                "logs:GetLogEvents",
+                "logs:AssociateKmsKey",
+                "logs:FilterLogEvents",
+                "logs:PutSubscriptionFilter",
+                "logs:PutRetentionPolicy"
+            ],
+            "Resource": [
+                "arn:aws:logs:us-east-1:527700175026:log-group:aws-cis-logs*",
+                "arn:aws:logs:us-east-1:527700175026:log-group::log-stream:"
+             ]
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "logs:DeleteResourcePolicy",
+                "logs:DescribeExportTasks",
+                "logs:PutResourcePolicy",
+                "logs:PutDestinationPolicy",
+                "logs:CancelExportTask",
+                "logs:TestMetricFilter",
+                "logs:DeleteDestination",
+                "logs:CreateLogGroup",
+                "logs:DescribeResourcePolicies",
+                "logs:PutDestination",
+                "logs:DescribeDestinations"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": [
+                "sns:CreatePlatformApplication",
+                "sns:SetSMSAttributes",
+                "sns:ListTopics",
+                "sns:GetPlatformApplicationAttributes",
+                "sns:CreatePlatformEndpoint",
+                "sns:Unsubscribe",
+                "sns:GetSubscriptionAttributes",
+                "sns:ListSubscriptions",
+                "sns:CheckIfPhoneNumberIsOptedOut",
+                "sns:OptInPhoneNumber",
+                "sns:DeleteEndpoint",
+                "sns:SetEndpointAttributes",
+                "sns:ListPhoneNumbersOptedOut",
+                "sns:ListEndpointsByPlatformApplication",
+                "sns:GetEndpointAttributes",
+                "sns:SetSubscriptionAttributes",
+                "sns:DeletePlatformApplication",
+                "sns:SetPlatformApplicationAttributes",
+                "sns:ListPlatformApplications",
+                "sns:GetSMSAttributes"
+            ],
+            "Resource": "*"
+        },
+        {
+            "Effect": "Allow",
+            "Action": "sns:*",
+            "Resource": "arn:aws:sns:us-east-1:527700175026:dps-alarm"
+        }
+    ]
+}

+ 25 - 0
base/account_standards/templates/cloudtrail_cloudwatch_logs_inline_policy_prod.json.tpl

@@ -0,0 +1,25 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "AWSCloudTrailCreateLogStream20141101",
+            "Effect": "Allow",
+            "Action": [
+                "logs:CreateLogStream"
+            ],
+            "Resource": [
+                "arn:aws:logs:us-east-1:477548533976:log-group:aws-cis-logs:log-stream:477548533976_CloudTrail_us-east-1*"
+            ]
+        },
+        {
+            "Sid": "AWSCloudTrailPutLogEvents20141101",
+            "Effect": "Allow",
+            "Action": [
+                "logs:PutLogEvents"
+            ],
+            "Resource": [
+                "arn:aws:logs:us-east-1:477548533976:log-group:aws-cis-logs:log-stream:477548533976_CloudTrail_us-east-1*"
+            ]
+        }
+    ]
+}

+ 25 - 0
base/account_standards/templates/cloudtrail_cloudwatch_logs_inline_policy_test.json.tpl

@@ -0,0 +1,25 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "AWSCloudTrailCreateLogStream20141101",
+            "Effect": "Allow",
+            "Action": [
+                "logs:CreateLogStream"
+            ],
+            "Resource": [
+                "arn:aws:logs:us-east-1:527700175026:log-group:aws-cis-logs:log-stream:527700175026_CloudTrail_us-east-1*"
+            ]
+        },
+        {
+            "Sid": "AWSCloudTrailPutLogEvents20141101",
+            "Effect": "Allow",
+            "Action": [
+                "logs:PutLogEvents"
+            ],
+            "Resource": [
+                "arn:aws:logs:us-east-1:527700175026:log-group:aws-cis-logs:log-stream:527700175026_CloudTrail_us-east-1*"
+            ]
+        }
+    ]
+}

+ 13 - 0
base/account_standards/templates/cloudtrail_cloudwatch_logs_role_policy_prod.json.tpl

@@ -0,0 +1,13 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {   
+      "Action": "sts:AssumeRole",
+      "Principal": {
+        "Service": "cloudtrail.amazonaws.com"
+      },  
+      "Effect": "Allow",
+      "Sid": ""
+    }   
+  ]
+}

+ 13 - 0
base/account_standards/templates/cloudtrail_cloudwatch_logs_role_policy_test.json.tpl

@@ -0,0 +1,13 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {   
+      "Action": "sts:AssumeRole",
+      "Principal": {
+        "Service": "cloudtrail.amazonaws.com"
+      },  
+      "Effect": "Allow",
+      "Sid": ""
+    }   
+  ]
+}

+ 95 - 0
base/account_standards/templates/cloudtrail_kms_policy.json.tpl

@@ -0,0 +1,95 @@
+{
+  "Version": "2012-10-17",
+  "Id": "Key policy created by CloudTrail",
+  "Statement": [
+    {
+      "Sid": "Enable IAM User Permissions",
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": [
+          "arn:aws:iam::${aws_account_id}:root"
+        ]
+      },
+      "Action": "kms:*",
+      "Resource": "*"
+    },
+    {
+      "Sid": "Allow CloudTrail to encrypt logs",
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "cloudtrail.amazonaws.com"
+      },
+      "Action": "kms:GenerateDataKey*",
+      "Resource": "*",
+      "Condition": {
+        "StringLike": {
+          "kms:EncryptionContext:aws:cloudtrail:arn": "arn:aws:cloudtrail:*:${aws_account_id}:trail/*"
+        }
+      }
+    },
+    {
+      "Sid": "Allow CloudTrail to describe key",
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "cloudtrail.amazonaws.com"
+      },
+      "Action": "kms:DescribeKey",
+      "Resource": "*"
+    },
+    {
+      "Sid": "Allow principals in the account to decrypt log files",
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": "*"
+      },
+      "Action": [
+        "kms:Decrypt",
+        "kms:ReEncryptFrom"
+      ],
+      "Resource": "*",
+      "Condition": {
+        "StringEquals": {
+          "kms:CallerAccount": "${aws_account_id}"
+        },
+        "StringLike": {
+          "kms:EncryptionContext:aws:cloudtrail:arn": "arn:aws:cloudtrail:*:${aws_account_id}:trail/*"
+        }
+      }
+    },
+    {
+      "Sid": "Allow alias creation during setup",
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": "*"
+      },
+      "Action": "kms:CreateAlias",
+      "Resource": "*",
+      "Condition": {
+        "StringEquals": {
+          "kms:CallerAccount": "${aws_account_id}",
+          "kms:ViaService": "ec2.us-east-1.amazonaws.com"
+        }
+      }
+    },
+    {
+      "Sid": "Enable cross account log decryption",
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": "*"
+      },
+      "Action": [
+        "kms:Decrypt",
+        "kms:ReEncryptFrom"
+      ],
+      "Resource": "*",
+      "Condition": {
+        "StringEquals": {
+          "kms:CallerAccount": "${aws_account_id}"
+        },
+        "StringLike": {
+          "kms:EncryptionContext:aws:cloudtrail:arn": "arn:aws:cloudtrail:*:${aws_account_id}:trail/*"
+        }
+      }
+    }
+  ]
+}

+ 30 - 0
base/account_standards/templates/cloudtrail_s3_policy.json.tpl

@@ -0,0 +1,30 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "AWSCloudTrailAclCheck",
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "cloudtrail.amazonaws.com"
+            },
+            "Action": "s3:GetBucketAcl",
+            "Resource": "arn:aws:s3:::dps-mdr-cloudtrail"
+        },
+        {
+            "Sid": "AWSCloudTrailWrite",
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "cloudtrail.amazonaws.com"
+            },
+            "Action": "s3:PutObject",
+            "Resource": [
+                "arn:aws:s3:::dps-mdr-cloudtrail/AWSLogs/350838957895/*"
+            ],
+            "Condition": {
+                "StringEquals": {
+                    "s3:x-amz-acl": "bucket-owner-full-control"
+                }
+            }
+        }
+    ]
+}

+ 30 - 0
base/account_standards/templates/cloudtrail_s3_policy_prod.json.tpl

@@ -0,0 +1,30 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {   
+            "Sid": "AWSCloudTrailAclCheck",
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "cloudtrail.amazonaws.com"
+            },  
+            "Action": "s3:GetBucketAcl",
+            "Resource": "arn:aws:s3:::dps-mdr-cloudtrail-prod"
+        },  
+        {   
+            "Sid": "AWSCloudTrailWrite",
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "cloudtrail.amazonaws.com"
+            },  
+            "Action": "s3:PutObject",
+            "Resource": [
+                "arn:aws:s3:::dps-mdr-cloudtrail-prod/AWSLogs/477548533976/*"
+            ],  
+            "Condition": {
+                "StringEquals": {
+                    "s3:x-amz-acl": "bucket-owner-full-control"
+                }   
+            }   
+        }   
+    ]   
+}

+ 30 - 0
base/account_standards/templates/cloudtrail_s3_policy_test.json.tpl

@@ -0,0 +1,30 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Sid": "AWSCloudTrailAclCheck",
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "cloudtrail.amazonaws.com"
+            },
+            "Action": "s3:GetBucketAcl",
+            "Resource": "arn:aws:s3:::dps-mdr-cloudtrail-test"
+        },
+        {
+            "Sid": "AWSCloudTrailWrite",
+            "Effect": "Allow",
+            "Principal": {
+                "Service": "cloudtrail.amazonaws.com"
+            },
+            "Action": "s3:PutObject",
+            "Resource": [
+                "arn:aws:s3:::dps-mdr-cloudtrail-test/AWSLogs/527700175026/*"
+            ],
+            "Condition": {
+                "StringEquals": {
+                    "s3:x-amz-acl": "bucket-owner-full-control"
+                }
+            }
+        }
+    ]
+}

+ 13 - 0
base/account_standards/templates/iam_lambda_assume_role_policy.json.tpl

@@ -0,0 +1,13 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": "sts:AssumeRole",
+      "Principal": {
+        "Service": "lambda.amazonaws.com"
+      },
+      "Effect": "Allow",
+      "Sid": ""
+    }
+  ]
+}

+ 21 - 0
base/account_standards/templates/lambda_cloudtrail_status_check_policy.json.tpl

@@ -0,0 +1,21 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "logs:CreateLogGroup",
+        "logs:CreateLogStream",
+        "logs:PutLogEvents"
+      ],
+      "Resource": "arn:aws:logs:*:*:*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "cloudtrail:DescribeTrails"
+      ],
+      "Resource": "*"
+    }
+  ]
+}

+ 21 - 0
base/account_standards/templates/lambda_password_policy_check_policy.json.tpl

@@ -0,0 +1,21 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "logs:CreateLogGroup",
+        "logs:CreateLogStream",
+        "logs:PutLogEvents"
+      ],
+      "Resource": "arn:aws:logs:*:*:*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iam:GetAccountPasswordPolicy"
+      ],
+      "Resource": "*"
+    }
+  ]
+}

+ 21 - 0
base/account_standards/templates/lambda_root_account_check_policy.json.tpl

@@ -0,0 +1,21 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "logs:CreateLogGroup",
+        "logs:CreateLogStream",
+        "logs:PutLogEvents"
+      ],
+      "Resource": "arn:aws:logs:*:*:*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iam:GetAccountSummary"
+      ],
+      "Resource": "*"
+    }
+  ]
+}

+ 23 - 0
base/account_standards/templates/lambda_support_group_check_policy.json.tpl

@@ -0,0 +1,23 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "logs:CreateLogGroup",
+        "logs:CreateLogStream",
+        "logs:PutLogEvents"
+      ],
+      "Resource": "arn:aws:logs:*:*:*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iam:ListEntitiesForPolicy",
+        "iam:GetGroup",
+        "iam:ListPolicies"
+      ],
+      "Resource": "*"
+    }
+  ]
+}

+ 21 - 0
base/account_standards/templates/lambda_user_policies_check_policy.json.tpl

@@ -0,0 +1,21 @@
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": [
+        "logs:CreateLogGroup",
+        "logs:CreateLogStream",
+        "logs:PutLogEvents"
+      ],
+      "Resource": "arn:aws:logs:*:*:*"
+    },
+    {
+      "Effect": "Allow",
+      "Action": [
+        "iam:GetAccountPasswordPolicy"
+      ],
+      "Resource": "*"
+    }
+  ]
+}

+ 204 - 4
base/account_standards/vars.tf

@@ -1,10 +1,210 @@
-# No local module inputs (yet)
+variable "key_pairs" {
+  description = "Public SSH keys"
+  type = map
+}
 
+variable "tags" {
+  description = "Extra tags for the module."
+  type = map
+  default = { } 
+}
 
 # ----------------------------------
 # Below this line are variables inherited from higher levels, so they
 # do not need to be explicitly passed to this module.
-variable "key_pairs" {
-  description = "Public SSH keys"
-  type = map
+variable "standard_tags" { type = map }
+variable "aws_account_id" { type = string }
+variable "aws_partition" { type = string }
+variable "environment" { type = string }
+
+# ----------------------------------
+# Below this line are variables from the CIS standards
+locals {
+  workspace-cis-hardening-policy = {
+    "default"     = "cis_hardening_iam_role_policy.json.tpl"
+    "prod"        = "cis_hardening_iam_role_policy_prod.json.tpl"
+    "common"      = "cis_hardening_iam_role_policy_prod.json.tpl"
+    "test"        = "cis_hardening_iam_role_policy_test.json.tpl"
+  }
+  workspace-dps-s3-cloudtrail-bucket = {
+    "default"     = "dps-mdr-cloudtrail"
+    "prod"        = "dps-mdr-cloudtrail-prod"
+    "common"      = "dps-mdr-cloudtrail-prod"
+    "test"        = "dps-mdr-cloudtrail-test"
+    "description" = "S3 bucket name for CloudTrail Logs"
+  }
+  workspace-dps-s3-cloudtrail-bucket-policy = { 
+    "default"     = "cloudtrail_s3_policy.json.tpl"
+    "prod"        = "cloudtrail_s3_policy_prod.json.tpl"
+    "common"      = "cloudtrail_s3_policy_prod.json.tpl"
+    "test"        = "cloudtrail_s3_policy_test.json.tpl"
+    "description" = "S3 IAM Policy for Cloudtrail Bucket" 
+  }
+
+  workspace-dps-cloudtrail-cloudwatch-logs-role = {
+    "default"     = "cloudtrail_cloudwatch_logs_role_policy.json.tpl"
+    "prod"        = "cloudtrail_cloudwatch_logs_role_policy_prod.json.tpl"
+    "common"      = "cloudtrail_cloudwatch_logs_role_policy_prod.json.tpl"
+    "test"        = "cloudtrail_cloudwatch_logs_role_policy_test.json.tpl"
+    "description" = "IAM Role for Cloudtrails Cloudwatch Logs" 
+  }
+
+  workspace-dps-cloudtrail-cloudwatch-logs-policy = {
+    "default"     = "cloudtrail_cloudwatch_logs_inline_policy.json.tpl"
+    "prod"        = "cloudtrail_cloudwatch_logs_inline_policy_prod.json.tpl"
+    "common"      = "cloudtrail_cloudwatch_logs_inline_policy_prod.json.tpl"
+    "test"        = "cloudtrail_cloudwatch_logs_inline_policy_test.json.tpl"
+    "description" = "IAM Role for Cloudtrails Cloudwatch Logs" 
+  }
+
+  workspace-billing-s3-bucket-name = {
+    "default"     = "dps-mdr-billing"
+    "prod"        = "dps-mdr-billing-prod"
+    "common"      = "dps-mdr-billing-prod"
+    "test"        = "dps-mdr-billing-test"
+    "description" = "S3 bucket name for Billing Logs"
+  }
+ 
+  workspace-ebs-kms-key-encryption-policy = {
+    "prod"        = "ebs_root_encrypt_decrypt_policy_prod.json.tpl"
+    "common"      = "ebs_root_encrypt_decrypt_policy_prod.json.tpl"
+    "test"        = "ebs_root_encrypt_decrypt_policy_test.json.tpl"
+  }
+}
+
+variable "alarm_namespace" {
+  description = "The namespace in which all alarms are set up."
+  default     = "dps-alarm-benchmark"
+}
+
+variable "cloudtrail_log_group_name" {
+  description = "The name of the CloudWatch Logs group to which CloudTrail events are delivered."
+  default     = "aws-cis-logs"
+}
+
+variable "sns_topic_name" {
+  description = "The name of the SNS Topic which will be notified when any alarm is performed."
+  default     = "dps-alarm"
+}
+
+variable "sqs_queue_name" {
+  description = "The name of the SQS queue to receive alerts from cloudwatch"
+  default     = "dps-alarm-sqs"
+}
+
+variable "resource_name_prefix" {
+  description = "All the resources will be prefixed with this varible"
+  default     = "aws-cis"
+}
+
+variable "lambda_timeout" {
+  description = "Default timeout of lambda fucntions"
+  default     = 180
+}
+
+variable "lambda_dry_run" {
+  description = "Sets DRY_RUN environment variable for all lambda functions"
+  default     = false
+}
+
+variable "lambda_aggressive" {
+  description = "Sets AGGRESSIVE mode as true for lambda fucntions"
+  default     = true
+}
+
+variable "lambda_mfa_checker_user_prefix" {
+  description = "Comma separated list of prefixes that mfa checker lambda helper will ignore"
+  default     = ""
+}
+
+variable "lambda_mfa_checker_user_suffix" {
+  description = "Comma separated list of suffixes that mfa checker lambda helper will ignore"
+  default     = ""
+}
+
+variable "lambda_user_inactivity_limit" {
+  description = "Disable inactive users more than N days"
+  default     = 90
+}
+
+variable "lambda_access_key_age_max" {
+  description = "Expire access keys after N days"
+  default     = 90
+}
+
+variable "lambda_access_key_age_notify" {
+  description = "Start to send notifications for expiring keys N before"
+  default     = 7
+}
+
+variable "lambda_cron_schedule" {
+  description = "Default Cron schedule for lambda helpers"
+  default     = "cron(0 6 * * ? *)"
+}
+
+variable "temp_artifacts_dir" {
+  description = "The path for creating the zip file"
+  default     = "/tmp/terraform-aws-cis-fundatentals/artifacts"
+}
+
+variable "iam_require_uppercase_characters" {
+  description = "Require at least one uppercase letter in passwords"
+  default     = true
+}
+
+variable "iam_require_lowercase_characters" {
+  description = "Require at least one lowercase letter in passwords"
+  default     = true
+}
+
+variable "iam_require_symbols" {
+  description = "Require at least one symbol in passwords"
+  default     = true
+}
+
+variable "iam_require_numbers" {
+  description = "Require at least one number in passwords"
+  default     = true
+}
+
+variable "iam_minimum_password_length" {
+  description = "Require minimum lenght of password"
+  default     = 14
+}
+
+variable "iam_password_reuse_prevention" {
+  description = "Prevent password reuse N times"
+  default     = 24
+}
+
+variable "iam_max_password_age" {
+  description = "Passwords expire in N days"
+  default     = 90
+}
+
+variable "iam_allow_users_to_change_password" {
+  description = "Can users change their own password"
+  default     = true
+}
+
+variable "iam_hard_expiry" {
+  description = "Everyone needs hard reset for expired passwords"
+  default     = true
+}
+
+variable "billing_s3_bucket_policy" {
+  description = "Custom S3 bucket policy for billing logs. The default policy will be used if not defined"
+  default     = ""
+}
+
+# The default policy will be used if this left empty
+variable "cloudtrail_kms_policy" {
+  description = "KMS policy for Cloudtrail logs."
+  default     = ""
+}
+
+# "ReadOnly", "WriteOnly", "All".
+variable "clodtrail_event_selector_type" {
+  description = "Log type for event selectors"
+  default     = "All"
 }

+ 2 - 0
submodules/kms/ebs-key/README.md

@@ -0,0 +1,2 @@
+# Create a KMS key for encrypting/decrypting EBS
+

+ 104 - 0
submodules/kms/ebs-key/main.tf

@@ -0,0 +1,104 @@
+# Roles carried over from the tf11 code have been commented out but may
+# need to be re-added.
+#
+# HOWEVER, it would be better to simply create an additional KMS key
+# with the corresponding service. This key is available as a fallback,
+# but better to create one per service.
+resource "aws_kms_key" "key" {
+  description             = var.description
+  policy = data.aws_iam_policy_document.kms_policy.json
+  tags = merge(
+    var.standard_tags,
+    { "Name" = var.name },
+    var.tags
+  )
+}
+
+resource "aws_kms_alias" "alias" {
+  name          = var.alias
+  target_key_id = aws_kms_key.key.key_id
+}
+
+data "aws_iam_policy_document" "kms_policy" {
+  policy_id = "${var.name}-policy"
+
+  statement {
+      sid = "Enable IAM User Permissions"
+      effect = "Allow"
+      principals {
+        type = "AWS"
+        identifiers = [ 
+          # The 'root' account is the entire account, we don't want that
+          #"arn:${var.aws_partition}:iam::${var.aws_account_id}:root" 
+          "arn:${var.aws_partition}:iam::${var.aws_account_id}:user/MDRAdmin", # MDRAdmin as a break glass
+          "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer" # Terraformer always gets full access
+        ]
+      }
+      actions = [ "kms:*" ]
+      resources = [ "*" ]
+  }
+
+  statement {
+      sid = "Allow access for Key Administrators"
+      effect = "Allow"
+      principals {
+        type = "AWS"
+         identifiers = concat(var.key_admin_arns, [ "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer" ])
+      }
+
+      actions = [
+        "kms:Create*",
+        "kms:Describe*",
+        "kms:Enable*",
+        "kms:List*",
+        "kms:Put*",
+        "kms:Update*",
+        "kms:Revoke*",
+        "kms:Disable*",
+        "kms:Get*",
+        "kms:Delete*",
+        "kms:TagResource",
+        "kms:UntagResource",
+        "kms:ScheduleKeyDeletion",
+        "kms:CancelKeyDeletion"
+      ]
+      resources = [ "*" ]
+    }
+
+  statement {
+      sid =  "Allow use of the key"
+      effect = "Allow"
+      principals {
+        type = "AWS"
+        identifiers = concat(var.key_user_arns, [ "arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer" ] )
+      }
+      actions = [
+        "kms:Encrypt",
+        "kms:Decrypt",
+        "kms:ReEncrypt*",
+        "kms:GenerateDataKey*",
+        "kms:DescribeKey"
+      ]
+      resources = [ "*" ]
+    }
+
+  statement {
+      sid = "Allow attachment of persistent resources"
+      effect = "Allow"
+      principals {
+        type = "AWS"
+        identifiers = concat(var.key_attacher_arns, ["arn:${var.aws_partition}:iam::${var.aws_account_id}:role/user/mdr_terraformer"])
+      }
+      actions = [
+        "kms:CreateGrant",
+        "kms:ListGrants",
+        "kms:RevokeGrant"
+      ]
+      resources = [ "*" ]
+      condition {
+        test = "Bool"
+        variable =  "kms:GrantIsForAWSResource"
+        values = [ "true" ]
+        }
+      }
+}

+ 3 - 0
submodules/kms/ebs-key/outputs.tf

@@ -0,0 +1,3 @@
+output "key_arn" {
+  value = aws_kms_key.key.arn
+}

+ 11 - 0
submodules/kms/ebs-key/vars.tf

@@ -0,0 +1,11 @@
+variable "name" { type = string }
+variable "alias" { type = string }
+variable "description" { type = string}
+variable "tags" { type = map}
+variable "key_admin_arns" { type = list }
+variable "key_user_arns" { type = list }
+variable "key_attacher_arns" { type = list }
+
+variable "standard_tags" { type = map }
+variable "aws_account_id" { type = string }
+variable "aws_partition" { type = string }

+ 3 - 0
submodules/kms/ebs-key/version.tf

@@ -0,0 +1,3 @@
+terraform {
+  required_version = "~> 0.12"
+}