Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions modules/kms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,41 +19,50 @@ components:
enabled: true
```


<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements

No requirements.
| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | ~> 4.0 |

## Providers

No providers.
| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | ~> 4.0 |

## Modules

| Name | Source | Version |
|------|--------|---------|
| <a name="module_iam_roles"></a> [iam\_roles](#module\_iam\_roles) | git::ssh://git@github.com/spenmo/infrastructure.git//components/terraform/account-map/modules/iam-roles | n/a |
| <a name="module_introspection"></a> [introspection](#module\_introspection) | cloudposse/label/null | 0.25.0 |
| <a name="module_allowed_role_map"></a> [allowed\_role\_map](#module\_allowed\_role\_map) | ../account-map/modules/roles-to-principals | n/a |
| <a name="module_iam_roles"></a> [iam\_roles](#module\_iam\_roles) | ../account-map/modules/iam-roles | n/a |
| <a name="module_kms_key"></a> [kms\_key](#module\_kms\_key) | cloudposse/kms-key/aws | 0.12.1 |
| <a name="module_monorepo"></a> [monorepo](#module\_monorepo) | git::ssh://git@github.com/spenmo/infrastructure.git | n/a |
| <a name="module_this"></a> [this](#module\_this) | cloudposse/label/null | 0.25.0 |

## Resources

No resources.
| Name | Type |
|------|------|
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.key_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_additional_tag_map"></a> [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.<br>This is for some rare cases where resources want additional configuration of tags<br>and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no |
| <a name="input_alias"></a> [alias](#input\_alias) | The display name of the alias. The name must start with the word `alias` followed by a forward slash. If not specified, the alias name will be auto-generated. | `string` | n/a | yes |
| <a name="input_alias"></a> [alias](#input\_alias) | The display name of the alias. The name must start with the word alias followed by a forward slash. If not specified, the alias name will be auto-generated. | `string` | `null` | no |
| <a name="input_allowed_principal_arns"></a> [allowed\_principal\_arns](#input\_allowed\_principal\_arns) | List of AWS principal ARNs allowed to assume the role. | `list(string)` | `[]` | no |
| <a name="input_allowed_roles"></a> [allowed\_roles](#input\_allowed\_roles) | Map of account:[role, role...] specifying roles allowed to assume the role.<br>Roles are symbolic names like `ops` or `terraform`. Use `*` as role for entire account. | `map(list(string))` | `{}` | no |
| <a name="input_attributes"></a> [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,<br>in the order they appear in the list. New attributes are appended to the<br>end of the list. The elements of the list are joined by the `delimiter`<br>and treated as a single ID element. | `list(string)` | `[]` | no |
| <a name="input_context"></a> [context](#input\_context) | Single object for setting entire context at once.<br>See description of individual variables for details.<br>Leave string and numeric variables as `null` to use default value.<br>Individual variable settings (non-null) override settings in context object,<br>except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | <pre>{<br> "additional_tag_map": {},<br> "attributes": [],<br> "delimiter": null,<br> "descriptor_formats": {},<br> "enabled": true,<br> "environment": null,<br> "id_length_limit": null,<br> "label_key_case": null,<br> "label_order": [],<br> "label_value_case": null,<br> "labels_as_tags": [<br> "unset"<br> ],<br> "name": null,<br> "namespace": null,<br> "regex_replace_chars": null,<br> "stage": null,<br> "tags": {},<br> "tenant": null<br>}</pre> | no |
| <a name="input_customer_master_key_spec"></a> [customer\_master\_key\_spec](#input\_customer\_master\_key\_spec) | Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`. | `string` | `"SYMMETRIC_DEFAULT"` | no |
| <a name="input_deletion_window_in_days"></a> [deletion\_window\_in\_days](#input\_deletion\_window\_in\_days) | Duration in days after which the key is deleted after destruction of the resource | `number` | `10` | no |
| <a name="input_delimiter"></a> [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.<br>Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
| <a name="input_description"></a> [description](#input\_description) | The description of the key as viewed in AWS console | `string` | `"Parameter Store KMS master key"` | no |
| <a name="input_description"></a> [description](#input\_description) | The description for the KMS Key. | `string` | `null` | no |
| <a name="input_descriptor_formats"></a> [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.<br>Map of maps. Keys are names of descriptors. Values are maps of the form<br>`{<br> format = string<br> labels = list(string)<br>}`<br>(Type is `any` so the map values can later be enhanced to provide additional options.)<br>`format` is a Terraform format string to be passed to the `format()` function.<br>`labels` is a list of labels, in order, to pass to `format()` function.<br>Label values will be normalized before being passed to `format()` so they will be<br>identical to how they appear in `id`.<br>Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no |
| <a name="input_enable_key_rotation"></a> [enable\_key\_rotation](#input\_enable\_key\_rotation) | Specifies whether key rotation is enabled | `bool` | `true` | no |
| <a name="input_enabled"></a> [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
Expand All @@ -72,7 +81,6 @@ No resources.
| <a name="input_policy"></a> [policy](#input\_policy) | A valid KMS policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy. | `string` | `""` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.<br>Characters matching the regex will be removed from the ID elements.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_region"></a> [region](#input\_region) | AWS Region | `string` | n/a | yes |
| <a name="input_required_tags"></a> [required\_tags](#input\_required\_tags) | List of required tag names | `list(string)` | `[]` | no |
| <a name="input_stage"></a> [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).<br>Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no |
| <a name="input_tenant"></a> [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no |
Expand All @@ -82,6 +90,7 @@ No resources.
| Name | Description |
|------|-------------|
| <a name="output_kms_key"></a> [kms\_key](#output\_kms\_key) | Output for KMS module |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

## References

Expand Down
71 changes: 68 additions & 3 deletions modules/kms/main.tf
Original file line number Diff line number Diff line change
@@ -1,15 +1,80 @@
locals {
account_id = data.aws_caller_identity.current.account_id
account_principal = "arn:aws:iam::${local.account_id}:root"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the data source here for govcloud

Suggested change
account_principal = "arn:aws:iam::${local.account_id}:root"
account_principal = "arn:${data.aws_partition.current.partition}:iam::${local.account_id}:root"

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition


administration_principals = [
local.account_principal
]

Comment on lines +5 to +8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This local doesn't seem very useful

Suggested change
administration_principals = [
local.account_principal
]

principals = sort(distinct(concat(
var.allowed_principal_arns,
module.allowed_role_map.principals,
)))
}

data "aws_caller_identity" "current" {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
data "aws_caller_identity" "current" {}
data "aws_caller_identity" "current" {}
data "aws_partition" "current" {}


module "allowed_role_map" {
source = "../account-map/modules/roles-to-principals"

privileged = false
role_map = var.allowed_roles

context = module.this.context
}

module "kms_key" {
source = "cloudposse/kms-key/aws"
version = "0.12.1"

description = var.description
alias = var.alias == null ? "alias/${module.this.id}" : var.alias
description = var.description == null ? "${module.this.id} KMS Key. Managed by Terraform." : var.description
deletion_window_in_days = var.deletion_window_in_days
enable_key_rotation = var.enable_key_rotation
alias = var.alias
policy = var.policy
key_usage = var.key_usage
customer_master_key_spec = var.customer_master_key_spec
multi_region = var.multi_region

policy = var.policy != "" ? var.policy : data.aws_iam_policy_document.key_policy.json

context = module.this.context
}

data "aws_iam_policy_document" "key_policy" {

statement {
sid = "KeyAdministration"
effect = "Allow"

actions = [
"kms:*",
]

resources = ["*"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to allow overriding the resources here so users of this component can give granular permissions to kms. These would also allay warnings by bridgecrew shown above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resources in this case is just the key that is being attached to, so I don't believe it is ever needed to be overwritten. If it is not supposed, AWS states that the policy will not work. In AWS docs, this is always * in their examples. See docs here.

CleanShot 2024-10-02 at 19 52 39


principals {
type = "AWS"
identifiers = local.administration_principals
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
identifiers = local.administration_principals
identifiers = [local.account_principal]

}
}

statement {
sid = "KeyUsage"
effect = "Allow"

actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey",
]

resources = ["*"]

principals {
type = "AWS"
identifiers = local.principals
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of hard coding this policy here, can we utilize this module and expose the inputs as variables so we can be as flexible as possible from the YAML?

https://github.com/cloudposse/terraform-aws-iam-policy

e.g.

components:
  terraform:
    kms:
      vars:
        policy:
          iam_policy_statements:
            KeyAdministration:
              effect: "Allow"
              actions:
              - "kms:*"
              resources:
              - "*"
              principals:
                type: "AWS"
                # This may be something we can contribute to the upstream module to dynamically fill these %s in
                # or it can be added by the HCL for this component
                identifiers:
                - "arn:%s:iam::%s:root"
            KeyUsage:
              effect: "Allow"
              actions:
              - "kms:Encrypt"
              - "kms:Decrypt"
              - "kms:ReEncrypt*"
              - "kms:GenerateDataKey*"
              - "kms:DescribeKey"
              resources:
              - "*"
              principals:
                type: "AWS"
                # Same here, these can be provided by the HCL unless this `identifiers` key is provided
                identifiers:
                - "..."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is totally valid, it's non-trivial and others haven't done this work / needed this yet, so to move this PR along I'm going to consider this out of scope. If we decide we need this to merge, I can make the changes necessary 👍

55 changes: 35 additions & 20 deletions modules/kms/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@ variable "region" {
description = "AWS Region"
}

variable "alias" {
type = string
description = "The display name of the alias. The name must start with the word alias followed by a forward slash. If not specified, the alias name will be auto-generated."
default = null
}

variable "description" {
type = string
description = "The description for the KMS Key."
default = null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the description isn't set to the original value of Parameter Store KMS master key, won't that cause people's kms keys to be recreated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure honestly as it has been a long time, but updated regardless 😅

}

variable "deletion_window_in_days" {
type = number
default = 10
Expand All @@ -15,16 +27,22 @@ variable "enable_key_rotation" {
description = "Specifies whether key rotation is enabled"
}

variable "description" {
variable "key_usage" {
type = string
default = "Parameter Store KMS master key"
description = "The description of the key as viewed in AWS console"
default = "ENCRYPT_DECRYPT"
description = "Specifies the intended use of the key. Valid values: `ENCRYPT_DECRYPT` or `SIGN_VERIFY`."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a validation rule here or is this already done by the upstream module?

}

variable "alias" {
variable "multi_region" {
type = bool
default = false
description = "Indicates whether the KMS key is a multi-Region (true) or regional (false) key."
}

variable "customer_master_key_spec" {
type = string
default = ""
description = "The display name of the alias. The name must start with the word `alias` followed by a forward slash. If not specified, the alias name will be auto-generated."
default = "SYMMETRIC_DEFAULT"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a breaking change or is this the normal default and we're just being explicit?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the downstream child module default. I believe all is good on this one, not a breaking change. I've added validation.

description = "Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`."
}

variable "policy" {
Expand All @@ -33,20 +51,17 @@ variable "policy" {
description = "A valid KMS policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy."
}

variable "key_usage" {
type = string
default = "ENCRYPT_DECRYPT"
description = "Specifies the intended use of the key. Valid values: `ENCRYPT_DECRYPT` or `SIGN_VERIFY`."
}

variable "customer_master_key_spec" {
type = string
default = "SYMMETRIC_DEFAULT"
description = "Specifies whether the key contains a symmetric key or an asymmetric key pair and the encryption algorithms or signing algorithms that the key supports. Valid values: `SYMMETRIC_DEFAULT`, `RSA_2048`, `RSA_3072`, `RSA_4096`, `ECC_NIST_P256`, `ECC_NIST_P384`, `ECC_NIST_P521`, or `ECC_SECG_P256K1`."
variable "allowed_roles" {
type = map(list(string))
description = <<-EOT
Map of account:[role, role...] specifying roles allowed to assume the role.
Roles are symbolic names like `ops` or `terraform`. Use `*` as role for entire account.
EOT
default = {}
}

variable "multi_region" {
type = bool
default = false
description = "Indicates whether the KMS key is a multi-Region (true) or regional (false) key."
variable "allowed_principal_arns" {
type = list(string)
description = "List of AWS principal ARNs allowed to assume the role."
default = []
}