Skip to content

Commit

Permalink
add acmpca
Browse files Browse the repository at this point in the history
  • Loading branch information
badra001 committed Jan 5, 2024
1 parent 3c0087c commit d96a05f
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 8 deletions.
4 changes: 2 additions & 2 deletions acm/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- BEGIN_TF_DOCS -->
# About : aws-certificate/acm

This module creates and ACM certificate, using the general purpose (ca1) ACM-PCA in the local region. It will automatically
This module creates an ACM certificate, using the general purpose (ca1) ACM-PCA in the local region. It will automatically
include the DNS name in the SAN. You may add additonal SAN fully qualified domain names, but only DNS names are supported
in the SAN for an ACM certificate.

Expand Down Expand Up @@ -54,8 +54,8 @@ the ARN if completed. You'll use the ARN for an AWS LB Listener.

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 0.14 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.0 |
| <a name="requirement_http"></a> [http](#requirement\_http) | >= 2.1.0 |
| <a name="requirement_local"></a> [local](#requirement\_local) | >= 2.1.0 |
| <a name="requirement_null"></a> [null](#requirement\_null) | >= 3.1.0 |
| <a name="requirement_tls"></a> [tls](#requirement\_tls) | >= 3.1.0 |
Expand Down
2 changes: 1 addition & 1 deletion acm/main.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* # About : aws-certificate/acm
*
* This module creates and ACM certificate, using the general purpose (ca1) ACM-PCA in the local region. It will automatically
* This module creates an ACM certificate, using the general purpose (ca1) ACM-PCA in the local region. It will automatically
* include the DNS name in the SAN. You may add additonal SAN fully qualified domain names, but only DNS names are supported
* in the SAN for an ACM certificate.
*
Expand Down
1 change: 1 addition & 0 deletions acmpca/.terraform-docs.yml
110 changes: 110 additions & 0 deletions acmpca/certificate.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
locals {
cert_dns = lower(var.certificate_dns)
cert_san = distinct([for f in compact(concat([local.cert_dns], var.certificate_san)) : lower(f)])

ca_mode = lookup(local._defaults["mode"], var.certificate_mode, null)
ca_type = lookup(local._defaults["template"], var.cerificate_type, null)
ca_settings = var.certificate_mode == "general" ? local.ca_longterm_settings : local.ca_shortterm_settings

output_file_directory = var.output_file_directory != null ? var.output_file_directory : format("%v/%v", path.root, "certs")

}

resource "tls_private_key" "certificate" {
algorithm = "RSA"
rsa_bits = 2048
}

resource "tls_cert_request" "certificate" {
private_key_pem = tls_private_key.certificate.private_key_pem
dns_names = local.ca_cert_san

subject {
common_name = local.ca_dns_name
country = lookup(var.certificate_subject_override, "c", local._defaults.certificate["c"])
organization = lookup(var.certificate_subject_override, "o", local._defaults.certificate["o"])
organizational_unit = lookup(var.certificate_subject_override, "ou", local._defaults.certificate["ou"])
}
}

resource "aws_acmpca_certificate" "certificate" {
certificate_authority_arn = local.ca_settings.arn
certificate_signing_request = tls_cert_request.certificate.cert_request_pem
signing_algorithm = "SHA384WITHRSA"
validity {
type = "DAYS"
value = var.validity_days
}
template_arn = local.certificate_settings.template_arns["SubordinateCACertificate_PathLen0/V1"]
lifecycle {
create_before_destroy = true
precondition {
condition = var.certificate_mode == "general" || (var.certificate_mode == "short" && var.certificate_type == "end-entity")
error_message = "certificate_mode and certificate_type conflict."
}
precondition {
condition = var.certificate_mode == "general" || (var.certificate_mode == "short" && var.validity_days <= 7)
error_message = "certificate_mode short must have validity <= 7 days."
}
}

# tags = merge(
# local.base_tags,
# var.tags,
# { "boc:pki:mail" = var.contact_email },
# )
}

locals {
certificate_tls_key = base64encode(tls_private_key.certificate.private_key_pem)
certificate_chain = replace(aws_acmpca_certificate.certificate.certificate_chain, "/\r/", "")
certificate_crt = aws_acmpca_certificate.certificate.certificate
certificate_tls_crt = base64encode(join("\n", [local.certificate_crt, local.certificate_chain]))
}

resource "null_source" "output_directory" {
count = var.create_files && local.output_file_directory != null ? 1 : 0
local-exec {
command = "test -d ${local.output_file_directory} || mkdir -p ${local.output_file_directory}"
}
}

locals {
filename_key = var.key_filename != null ? var.key_filename : format("%v/%v.%v", local.output_file_directory, local.cert_dns, "key")
filename_csr = var.csr_filename != null ? var.csr_filename : format("%v/%v.%v", local.output_file_directory, local.cert_dns, "csr")
filename_crt = var.certificate_filename != null ? var.certificate_filename : format("%v/%v.%v", local.output_file_directory, local.cert_dns, "crt")
filename_chain = var.certificate_chain_filename != null ? var.certificate_chain_filename : format("%v/%v.%v", local.output_file_directory, local.cert_dns, "chain.crt")
}

resource "local_sensitive_file" "certificate_key" {
count = var.create_files && local.output_file_directory != null ? 1 : 0
filename = local.filename_key
file_permission = "0644"
directory_permission = "0755"
content = tls_private_key.certificate.private_key_pem
}

resource "local_sensitive_file" "certificate_csr" {
count = var.create_files && local.output_file_directory != null ? 1 : 0
filename = local.filename_csr
file_permission = "0644"
directory_permission = "0755"
content = tls_cert_request.certificate.cert_request_pem
}

resource "local_sensitive_file" "certificate_cert" {
count = var.create_files && local.output_file_directory != null ? 1 : 0
filename = local.filename_crt
file_permission = "0644"
directory_permission = "0755"
content = aws_acmpca_certificate.certificate.certificate
}

resource "local_sensitive_file" "certificate_cert_chain" {
count = var.create_files && local.output_file_directory != null ? 1 : 0
filename = local.filename_chain
file_permission = "0644"
directory_permission = "0755"
content = local.certificate_chain
}

1 change: 1 addition & 0 deletions acmpca/data.acmpca-parameters.tf
1 change: 1 addition & 0 deletions acmpca/data.tf
1 change: 1 addition & 0 deletions acmpca/defaults.tf
9 changes: 9 additions & 0 deletions acmpca/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
locals {
account_id = var.account_id != "" ? var.account_id : data.aws_caller_identity.current.account_id
account_environment = data.aws_arn.current.partition == "aws-us-gov" ? "gov" : "ew"

base_tags = {
"boc:tf_module_version" = local._module_version
"boc:created_by" = "terraform"
}
}
49 changes: 49 additions & 0 deletions acmpca/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* # About : aws-certificate/acmpca
*
* This module creates an ACM certificate, using the general purpose (ca1) ACM-PCA or short term (ca2) in the local region. It will automatically
* include the DNS name in the SAN. You may add additonal SAN fully qualified domain names, URIs, or
* in the SAN for an ACM certificate. The [CLI documentation](https://docs.aws.amazon.com/cli/latest/reference/acm-pca/issue-certificate.html) indicates
* you can use any of the standard types for a SAN (DNS, URI, email, DNS, etc.) along with the [API](https://docs.aws.amazon.com/privateca/latest/APIReference/API_IssueCertificate.html)
* reference. Other documentation states otherwise (TBD -- find link).
*
* It expects an SSM parameter `/enterprise/pki/ca1` for general purpose and `/enterprise/pki/ca2` for short term CA to exist in the account (distributed to all OUs from a central account).
* If this parameter does not exist, this module will fail.
*
* It returns:
*
* # Usage
* This shows the module call with how you would use it.
*
* ```hcl
* module "cert" {
* source = "git@github.e.it.census.gov:terraform-modules/aws-certificates//acm"
*
* certificate_dns = "test.domain.census.gov"
* contact_email = "cio.engineering.alert.list@census.gov"
*
* ## optional
* ## add additional names to SAN
* # certificate_san = [ "otherdomain.domain.census.gov" ]
* }
*
* # associating it with the ALB listener
* resource "aws_lb_listener" "app_443" {
* count = module.cert.certificate_arn != null ? 1 : 0
* load_balancer_arn = aws_lb.app.arn
* port = 443
* protocol = "HTTPS"
* ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
* certificate_arn = module.cert.certificate_arn
*
* default_action {
* type = "forward"
* target_group_arn = aws_lb_target_group.app.arn
* }
* }
* ```
*
* The output value to look at is `certificate_arn`. This is null if the certificate is incomplete or failed to load into ACM, or
* the ARN if completed. You'll use the ARN for an AWS LB Listener.
*/

37 changes: 37 additions & 0 deletions acmpca/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
output "certificate_key" {
description = "PEM format RSA Key"
sensitive = true
value = tls_private_key.certificate.private_key_pem
}

output "certificate_csr" {
description = "PEM format Certificate Signing Request"
sensitive = false
value = tls_cert_request.certificate.cert_request_pem
}

output "certificate" {
description = "PEM format for signed certificate"
sensitive = false
value = aws_acmpca_certificate.certificate.certificate
}

output "certificate_chain" {
description = "PEM format for certificate chain (issuer through root)"
sensitive = false
value = local.certificate_chain
}

output "certificate_files" {
description = "Map of certificate file names"
sensitive = false
value = {
enabled = var.create_files
key = local.filename_key
csr = local.filename_csr
certificate = local.filename_crt
chain = local.filename_chain
}
}


1 change: 1 addition & 0 deletions acmpca/prefixes.tf
1 change: 1 addition & 0 deletions acmpca/variables.common.tf
97 changes: 97 additions & 0 deletions acmpca/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
variable "certificate_dns" {
description = "DNS Name to be used for the certificate. For ACM certificate, the subject and CN may not be customized."
type = string
}

variable "certificate_san" {
description = "The Subject Alternate Names (SAN), a list of FQDNs to include in the ACM Certificate. Only DNS names are supported. See docs at https://docs.aws.amazon.com/cli/latest/reference/acm/request-certificate.html"
type = list(string)
default = []
}

variable "contact_email" {
description = "Email address in @census.gov of contact for the certificate. This is strongly recommended to be a group email address."
type = string
}


variable "certificate_cn" {
description = "CommonName (CN) to use for certificate, defaults in c=US,o=U.S. Census Bureau,ou=Servers. This will typically be the DNS name. Uses certificate_dns if not provided."
type = string
default = null
}

variable "certificate_subject_overrides" {
description = "Map of c, o, and ou to override certificate signing request settings. Note that only a single OU is permitted."
type = map(string)
default = {}
}

variable "validity_days" {
description = "Number of days for which the certificate is valid. For the short lived certificate, this must be <= 7"
type = number
default = 365

validation {
condition = var.validity_days > 0
error_message = "validity_days must be larger than 0."
}
}

variable "create_files" {
description = "Flag controlling the creation of output files for the key, CSR, and certificate and bundle."
type = bool
default = false
}

variable "output_file_directory" {
description = "File path for resultant files when create_files is used. Defaults to path.root/certs"
type = string
default = null
}

variable "key_filename" {
description = "Filename for RSA private key. Defaults to {certificate_dns}.key"
type = string
default = null
}

variable "csr_filename" {
description = "Filename for Certificate Signing Request (CSR). Defaults to {certificate_dns}.csr"
type = string
default = null
}

variable "certificate_filename" {
description = "Filename for Certificate. Defaults to {certificate_dns}.crt"
type = string
default = null
}

variable "certificate_authority_mode" {
description = "String indicating whether to use the general purpose (general) or short lived (short) CA (general is ca1, short lived is ca2)"
type = string
default = "general"

validation {
condition = contains(["general", "short"], var.certificate_authority_mode)
error_message = "certificate_authority_mode must be one of 'general' | 'short'."
}
}

variable "certificate_authority_template" {
description = "String indicating which specific ACMPCA template to use"
type = string
default = null
}

variable "certificate_type" {
description = "Selection of type of certificate, either end-entity or subordinate-ca. Note that the subordinate-ca type is not available for the short lived CA mode"
type = string
default = "end-entity"

validation {
condition = contains(["end-entity", "subordinate-ca"], var.certificate_type)
error_message = "certificate_type must be one of 'end-entity' | 'subordinate-ca'."
}
}
1 change: 1 addition & 0 deletions acmpca/version.tf
1 change: 1 addition & 0 deletions acmpca/versions.tf
8 changes: 8 additions & 0 deletions common/defaults.tf
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,13 @@ locals {
"o" = "U.S. Census Bureau",
"ou" = "Servers",
}
"template" = {
"end-entity" = "EndEntityCertificate/V1"
"subordinate-ca" = "SubordinateCACertificate_PathLen0/V1"
}
"mode" = {
"general" = "GENERAL_PURPOSE"
"short" = "SHORT_LIVED_CERTIFICATE"
}
}
}
10 changes: 5 additions & 5 deletions common/versions.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
terraform {
# required_version = ">= 0.13"
required_version = ">= 0.14"
required_providers {
aws = {
source = "hashicorp/aws"
Expand All @@ -17,9 +17,9 @@ terraform {
source = "hashicorp/tls"
version = ">= 3.1.0"
}
http = {
source = "hashicorp/http"
version = ">= 2.1.0"
}
# http = {
# source = "hashicorp/http"
# version = ">= 2.1.0"
# }
}
}

0 comments on commit d96a05f

Please sign in to comment.