Browse Source

Creates `salt-master-inventory` role and `salt-master` user

User `salt-master` created in commercial only along with access keys.
This is for the inventory script to use in order to inventory assets in
commercial (since the instance is in govcloud).

'salt-master-inventory` role to be created in all accounts for the
inventory script. It can be assumed into by either the instance role (in
govcloud) or the user (in commercial).

Note: No support for the legacy salt-master is here. We only need one
instance performing the inventory.

To be tagged v0.8.8
Fred Damstra 5 years ago
parent
commit
f9f45d0f38

+ 9 - 0
base/salt_master_inventory_role/README.md

@@ -0,0 +1,9 @@
+This role is created separate from account standards even though it is required in all accounts. The role must be created after the salt master instance or the trust policy can't be applied.
+
+PREREQUISITES:
+
+Order gets very important in this module, unfortunately. The following sequence is required:
+* The salt-master instances must be created in C2 test and C2 prod (in govcloud).
+* This module must be run in commercial C2 prod (The user is created, which is trusted by all others)
+* This module must be run in commercial C2 test (The user is created, which is trusted by the rest of test)
+* Then the module can be run in all the other accounts

+ 100 - 0
base/salt_master_inventory_role/inventory_role.tf

@@ -0,0 +1,100 @@
+# Contains roles used for gathering inventory across AWS accounts
+# These roles are assumed into from the salt-master instance in order to
+# gather data about the instances.
+#
+# This is a low risk policy that provides view only access to select
+# services.
+
+locals {
+  # Trust these ARNs:
+  #
+  #   Commercial - Trust the user in C2
+  #   GovCloud   - Trust the role in C2
+  #
+  #   Test       - Trust both prod and test C2 arns
+  #   Prod       - Trust only the prod C2 arns
+  #
+  #   Note: No support for the legacy salt master is included. The
+  #         new govcloud salt masters will be 100% repsonsible for
+  #         the inventory.
+  trusted_arns_map = {
+    "test" = {
+      "aws" = [
+        "arn:aws:iam::045312110490:user/instance/salt-master", # mdr-prod-c2
+        "arn:aws:iam::816914342178:user/instance/salt-master", # mdr-test-c2
+      ],
+      "aws-us-gov" = [
+        "arn:aws-us-gov:iam::721817724804:role/salt-master-instance-role", # mdr-prod-c2-gov
+        "arn:aws-us-gov:iam::738800754746:role/salt-master-instance-role", # mdr-test-c2-gov
+      ]
+    },
+    "prod" = {
+      "aws" = [
+        "arn:aws:iam::045312110490:user/instance/salt-master", # mdr-prod-c2
+      ],
+      "aws-us-gov" = [
+        "arn:aws-us-gov:iam::721817724804:role/salt-master-instance-role", # mdr-prod-c2-gov
+      ]
+    },
+    "common" = {
+      "aws" = [
+        "arn:aws:iam::045312110490:user/instance/salt-master", # mdr-prod-c2
+      ],
+      "aws-us-gov" = [
+        "arn:aws-us-gov:iam::721817724804:role/salt-master-instance-role", # mdr-prod-c2-gov
+      ]
+    }
+  }
+  trusted_arns = local.trusted_arns_map[var.environment][var.aws_partition]
+}
+
+resource "aws_iam_role" "salt_master_inventory_role" {
+  depends_on = [ aws_iam_user.salt-master ]
+  name = "salt-master-inventory-role"
+  path  = "/service/"
+  force_detach_policies = true # causes "DeleteConflict" if not present
+
+  # the extra_trusted_salt variable allows the addition of additional
+  # trusted sources, such as the dev salt master (for dev environments)
+  # and developer users.
+  assume_role_policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Principal": {
+        "AWS": ${jsonencode(local.trusted_arns)}
+      },
+      "Action": "sts:AssumeRole"
+    }
+  ]
+}
+EOF
+}
+
+resource "aws_iam_role_policy_attachment" "salt_master_inventory_policy_attach" {
+  role = aws_iam_role.salt_master_inventory_role.name
+  policy_arn = aws_iam_policy.salt_master_inventory_policy.arn
+}
+
+resource "aws_iam_policy" "salt_master_inventory_policy" {
+  name  = "salt-master-inventory-policy"
+  path  = "/service/"
+  description = "Policy which allows the salt master to perform inventory."
+  policy = data.aws_iam_policy_document.salt_master_inventory_policy_doc.json
+}
+
+data "aws_iam_policy_document" "salt_master_inventory_policy_doc" {
+  statement {
+    sid = "DescribeAllAssets"
+    effect = "Allow"
+    actions = [
+      "ec2:DescribeInstances",
+      "ec2:DescribeRegions",
+      "rds:DescribeDBInstances",
+      "rds:ListTagsForResource"
+    ]
+    resources = [ "*" ]
+  }
+}

+ 3 - 0
base/salt_master_inventory_role/outputs.tf

@@ -0,0 +1,3 @@
+output "salt-master-c2-commercial-user-arn" {
+  value = local.user_count == 0 ? null : aws_iam_user.salt-master[0].arn
+}

+ 112 - 0
base/salt_master_inventory_role/user.tf

@@ -0,0 +1,112 @@
+######################
+# Access keys
+#
+# For rotation purposes, there are two of these. Delete the oldest one, 
+# add a new one (with a higher version number), and then update the output
+#
+# Possible futue improvement:
+# We could specify a pgp_key attribute, and then the secret will be encrypted
+# in both the state file and in the output. If we used the salt PGP key,
+# no user would ever have to see the secret key.
+locals {
+  # The user in this file will only be created for Commercial C2
+  is_commercial = var.aws_partition == "aws-us-gov" ? false : true
+  is_c2         = contains(["045312110490", "816914342178" ], var.aws_account_id)
+  user_count    = local.is_commercial && local.is_c2 ? 1 : 0
+}
+
+resource "aws_iam_access_key" "salt-master-v0" {
+  count = local.user_count
+  user = aws_iam_user.salt-master[count.index].name
+}
+
+resource "aws_iam_access_key" "salt-master-v1" {
+  count = local.user_count
+  user = aws_iam_user.salt-master[count.index].name
+}
+
+output access_keys {
+  # Only output the keys if there _are_ keys
+  value = local.user_count == 0 ? null : {
+      "current" = { 
+        "aws_access_key_id": aws_iam_access_key.salt-master-v1[0].id
+        "aws_secret_access_key": aws_iam_access_key.salt-master-v1[0].secret
+      },
+      "previous" = {
+        "aws_access_key_id": aws_iam_access_key.salt-master-v0[0].id
+        "aws_secret_access_key": aws_iam_access_key.salt-master-v0[0].secret
+      }
+    }
+}
+
+######################
+# The policy is attached to both the user and the instance profile
+data "aws_iam_policy_document" "salt_master_policy_doc" {
+  statement {
+    sid    = "AllowSaltSecretsCommunication"
+    effect = "Allow"
+
+    actions = [
+      "secretsmanager:GetResourcePolicy",
+      "secretsmanager:GetSecretValue",
+      "secretsmanager:DescribeSecret",
+      "secretsmanager:ListSecretVersionIds"
+    ]
+
+    resources = [
+      "arn:${var.aws_partition}:secretsmanager:*:*:secret:saltmaster/*"
+    ]
+  }
+
+  statement {
+    sid    = "AllowAssumeRole"
+    effect = "Allow"
+
+    actions = [
+      "sts:AssumeRole"
+    ]
+
+    resources = [
+      "arn:${var.aws_partition}:iam::*:role/service/salt-master-inventory-role"
+    ]
+  }
+}
+
+resource "aws_iam_policy" "salt-master" {
+  count       = local.user_count
+  name        = "salt_master_sm"
+  path        = "/"
+  policy      = data.aws_iam_policy_document.salt_master_policy_doc.json
+}
+
+######################
+# the user
+#
+# Note: CIS requires that policies _NOT_ be directly attached to a user. Users must
+# be members of groups, and those groups can have policies.
+resource "aws_iam_user" "salt-master" {
+  count = local.user_count
+  name = "salt-master"
+  path = "/instance/"
+
+  tags = merge(var.standard_tags, var.tags)
+}
+
+resource "aws_iam_group" "salt-master" {
+  count = local.user_count
+  name = "salt-master"
+  path = "/instance/"
+}
+
+resource "aws_iam_user_group_membership" "salt-master" {
+  count = local.user_count
+  user = aws_iam_user.salt-master[count.index].name
+
+  groups = [ aws_iam_group.salt-master[count.index].name ]
+}
+
+resource "aws_iam_group_policy_attachment" "salt-master-group" {
+  count = local.user_count
+  group      = aws_iam_group.salt-master[count.index].name
+  policy_arn = aws_iam_policy.salt-master[count.index].arn
+}

+ 10 - 0
base/salt_master_inventory_role/vars.tf

@@ -0,0 +1,10 @@
+variable "tags" {
+  description = "Tags to add to the resource (in addition to global standard tags)"
+  type        = map
+  default     = { }
+}
+variable "standard_tags" { type = map }
+variable "environment" { type = string }
+variable "aws_partition" { type = string }
+variable "aws_partition_alias" { type = string }
+variable "aws_account_id" { type = string }

+ 3 - 0
base/salt_master_inventory_role/version.tf

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