From 1dfadc68d6f10e4b5372994d549f6898d71074b0 Mon Sep 17 00:00:00 2001 From: Al Cutter Date: Tue, 6 Feb 2024 13:15:31 +0000 Subject: [PATCH] Add Terraform for CI HAB PKI (#78) --- deployment/build_and_release/main.tf | 6 +- deployment/build_and_release/terraform.tfvars | 4 +- deployment/hab_pki/main.tf | 335 ++++++++++++++++++ deployment/hab_pki/terraform.tfvars | 5 + deployment/hab_pki/variables.tf | 32 ++ 5 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 deployment/hab_pki/main.tf create mode 100644 deployment/hab_pki/terraform.tfvars create mode 100644 deployment/hab_pki/variables.tf diff --git a/deployment/build_and_release/main.tf b/deployment/build_and_release/main.tf index 959fc0ff..2b0c62ab 100644 --- a/deployment/build_and_release/main.tf +++ b/deployment/build_and_release/main.tf @@ -114,7 +114,7 @@ resource "google_storage_bucket" "armored_witness_firmware_log_ci_1" { uniform_bucket_level_access = true } -# KMS key rings +# KMS key rings & data sources resource "google_kms_key_ring" "firmware_release_ci" { location = var.signing_keyring_location name = "firmware-release-ci" @@ -227,6 +227,10 @@ resource "google_kms_key_ring" "firmware_release_prod" { # } #} +############################################################ +## Terraform state bucket +############################################################ + resource "google_kms_key_ring" "terraform_state" { name = "armored-witness-bucket-tfstate" location = var.tf_state_location diff --git a/deployment/build_and_release/terraform.tfvars b/deployment/build_and_release/terraform.tfvars index e3d99d75..5ce050ab 100644 --- a/deployment/build_and_release/terraform.tfvars +++ b/deployment/build_and_release/terraform.tfvars @@ -1,3 +1,3 @@ -project_id = "1071548024491" +project_id = "armored-witness" signing_keyring_location = "global" -tf_state_location = "europe-west2" \ No newline at end of file +tf_state_location = "europe-west2" diff --git a/deployment/hab_pki/main.tf b/deployment/hab_pki/main.tf new file mode 100644 index 00000000..68bb50d9 --- /dev/null +++ b/deployment/hab_pki/main.tf @@ -0,0 +1,335 @@ +# Configure remote terraform backend for state. +terraform { + backend "gcs" { + bucket = "armored-witness-bucket-tfstate" + prefix = "terraform/hab_pki/state" + } +} + +# Project +provider "google" { + project = var.project_id +} + +data "google_project" "project" { + project_id = var.project_id +} + +# Enable necessary APIs +resource "google_project_service" "cloudkms_googleapis_com" { + service = "cloudkms.googleapis.com" +} +resource "google_project_service" "logging_googleapis_com" { + service = "logging.googleapis.com" +} +resource "google_project_service" "serviceusage_googleapis_com" { + service = "serviceusage.googleapis.com" +} +resource "google_project_service" "privateca_api" { + service = "privateca.googleapis.com" + disable_on_destroy = false +} + +# KMS key rings & data sources +resource "google_kms_key_ring" "hab" { + location = var.signing_keyring_location + name = "hab-ci" +} +data "google_kms_key_ring" "hab" { + location = google_kms_key_ring.hab.location + name = google_kms_key_ring.hab.name +} + +### KMS keys + +# CI HAB CSF key & data sources for each of the SRK intermediates below. +# +# The resource creates the key within the keyring, and the data sections below +# ultimately provide a mechanism for getting the public key out from the HSM +# so that it can be certified by the leaf certificates of the HAB PKI below. +resource "google_kms_crypto_key" "hab_csf" { + for_each = toset(local.hab_intermediates) + + key_ring = google_kms_key_ring.hab.id + name = format("hab-csf%d-rev%d-ci", each.value, var.hab_ci_revision) + purpose = "ASYMMETRIC_SIGN" + version_template { + algorithm = format("RSA_SIGN_PKCS1_%d_SHA256", var.hab_keylength) + protection_level = "HSM" + } +} +data "google_kms_crypto_key" "hab_csf" { + for_each = toset(local.hab_intermediates) + + name = format("hab-csf%s-rev%d-ci", each.value, var.hab_ci_revision) + key_ring = data.google_kms_key_ring.hab.id + + depends_on = [ + google_kms_crypto_key.hab_csf + ] +} +data "google_kms_crypto_key_version" "hab_csf" { + for_each = toset(local.hab_intermediates) + + crypto_key = data.google_kms_crypto_key.hab_csf[each.key].id +} +# CI HAB IMG key & data sources for each of the SRK intermediates below. +resource "google_kms_crypto_key" "hab_img" { + for_each = toset(local.hab_intermediates) + + key_ring = google_kms_key_ring.hab.id + name = format("hab-img%d-rev%d-ci", each.value, var.hab_ci_revision) + purpose = "ASYMMETRIC_SIGN" + version_template { + algorithm = format("RSA_SIGN_PKCS1_%d_SHA256", var.hab_keylength) + protection_level = "HSM" + } +} +data "google_kms_crypto_key" "hab_img" { + for_each = toset(local.hab_intermediates) + + name = format("hab-img%s-rev%d-ci", each.value, var.hab_ci_revision) + key_ring = data.google_kms_key_ring.hab.id + + depends_on = [ + google_kms_crypto_key.hab_img + ] +} +data "google_kms_crypto_key_version" "hab_img" { + for_each = toset(local.hab_intermediates) + + crypto_key = data.google_kms_crypto_key.hab_img[each.key].id +} + +########################################################################### +## CI HAB Certificate Authority config. +## +## This should construct a CA hierarchy as below for use with HAB signing: +## .--------. +## | Root | +## `--------' +## | +## --------------------------------------------------- +## | | | | +## .--------. .--------. .--------. .--------. +## | SRK1 | | SRK2 | | SRK3 | | SRK4 | +## `--------' `--------' `--------' `--------' +## / \ / \ / \ / \ +## .----. .----. .----. .----. .----. .----. .----. .----. +## |CSF1| |IMG1| |CSF2| |IMG2| |CSF3| |IMG3| |CSF4| |IMG4| +## `----' `----' `----' `----' `----' `----' `----' `----' +########################################################################### + +# CI HAB CA pool +resource "google_privateca_ca_pool" "hab" { + name = "aw-hab-ca-pool-rev0-ci" + location = "us-central1" + tier = "ENTERPRISE" + publishing_options { + publish_ca_cert = true + publish_crl = false + } + issuance_policy { + baseline_values { + ca_options { + is_ca = true + } + key_usage { + base_key_usage { + cert_sign = true + crl_sign = true + digital_signature = true + } + extended_key_usage { + } + } + } + } +} + +# CI HAB Root CA authority +resource "google_privateca_certificate_authority" "hab_root" { + pool = google_privateca_ca_pool.hab.name + certificate_authority_id = format("hab-root-rev%d-ci", var.hab_ci_revision) + location = "us-central1" + lifetime = format("%ds", var.hab_pki_lifetime) + deletion_protection = true + + type = "SELF_SIGNED" + config { + subject_config { + subject { + organization = "TrustFabric" + organizational_unit = "ArmoredWitness CI" + common_name = "ArmoredWitness Root CI" + } + } + x509_config { + ca_options { + # is_ca *MUST* be true for certificate authorities + is_ca = true + } + key_usage { + base_key_usage { + # cert_sign and crl_sign *MUST* be true for certificate authorities + cert_sign = true + crl_sign = true + } + extended_key_usage { + } + } + } + } + key_spec { + algorithm = format("RSA_PKCS1_%d_SHA256", var.hab_keylength) + } +} + +locals { + // This simply gives us a list we can use, in combination with the for_each meta attribute, to create + // multiple instances of the subordinate CAs & certs below. + hab_intermediates = [for i in range(1, 1 + var.hab_num_intermediates) : format("%s", i)] +} + +# CI HAB SRK intermediates (one each for hab_intermediates above) +resource "google_privateca_certificate_authority" "hab_srk" { + for_each = toset(local.hab_intermediates) + + pool = google_privateca_ca_pool.hab.name + certificate_authority_id = format("hab-srk%s-rev%d-ci", each.value, var.hab_ci_revision) + location = "us-central1" + lifetime = format("%ds", var.hab_pki_lifetime) + deletion_protection = "true" + + type = "SUBORDINATE" + subordinate_config { + certificate_authority = google_privateca_certificate_authority.hab_root.name + } + config { + subject_config { + subject { + organization = "TrustFabric" + organizational_unit = "ArmoredWitness CI" + common_name = format("ArmoredWitness SRK%s CI", each.value) + } + } + x509_config { + ca_options { + is_ca = true + # Force the sub CA to only issue leaf certs + max_issuer_path_length = 0 + } + key_usage { + base_key_usage { + cert_sign = true + crl_sign = true + } + extended_key_usage { + } + } + } + } + key_spec { + algorithm = format("RSA_PKCS1_%d_SHA256", var.hab_keylength) + } +} + +# CI HAB CSF cert for each of the SRK intermediates above. +resource "google_privateca_certificate" "hab_csf" { + for_each = google_privateca_certificate_authority.hab_srk + + name = format("hab-csf%s-rev%d-ci", each.key, var.hab_ci_revision) + location = "us-central1" + pool = each.value.pool + certificate_authority = each.value.certificate_authority_id + lifetime = format("%ds", var.hab_pki_lifetime) + config { + subject_config { + subject { + organization = "TrustFabric" + organizational_unit = "ArmoredWitness CI" + common_name = format("ArmoredWitness SRK%s CSF CI", each.key) + } + } + x509_config { + ca_options { + is_ca = false + } + key_usage { + base_key_usage { + digital_signature = true + } + extended_key_usage { + } + } + } + public_key { + format = "PEM" + key = base64encode(data.google_kms_crypto_key_version.hab_csf[each.key].public_key[0].pem) + } + } +} + +# CI HAB IMG cert for each of the SRK intermediates above. +resource "google_privateca_certificate" "hab_img" { + for_each = google_privateca_certificate_authority.hab_srk + + name = format("hab-img%s-rev%d-ci", each.key, var.hab_ci_revision) + location = "us-central1" + pool = each.value.pool + certificate_authority = each.value.certificate_authority_id + lifetime = format("%ds", var.hab_pki_lifetime) + config { + subject_config { + subject { + organization = "TrustFabric" + organizational_unit = "ArmoredWitness CI" + common_name = format("ArmoredWitness SRK%s IMG CI", each.key) + } + } + x509_config { + ca_options { + is_ca = false + } + key_usage { + base_key_usage { + digital_signature = true + } + extended_key_usage { + } + } + } + public_key { + format = "PEM" + key = base64encode(data.google_kms_crypto_key_version.hab_img[each.key].public_key[0].pem) + } + } +} + +############################################################ +## Terraform state bucket +############################################################ + +resource "google_kms_key_ring" "terraform_state" { + name = "armored-witness-bucket-tfstate" + location = var.tf_state_location +} + +resource "google_kms_crypto_key" "terraform_state_bucket" { + name = "terraform-state-bucket" + key_ring = google_kms_key_ring.terraform_state.id +} + +resource "google_storage_bucket" "terraform_state" { + name = "armored-witness-bucket-tfstate" + force_destroy = false + location = var.tf_state_location + storage_class = "STANDARD" + versioning { + enabled = true + } + encryption { + default_kms_key_name = google_kms_crypto_key.terraform_state_bucket.id + } + uniform_bucket_level_access = true +} \ No newline at end of file diff --git a/deployment/hab_pki/terraform.tfvars b/deployment/hab_pki/terraform.tfvars new file mode 100644 index 00000000..09c69816 --- /dev/null +++ b/deployment/hab_pki/terraform.tfvars @@ -0,0 +1,5 @@ +project_id = "armored-witness" +signing_keyring_location = "global" +tf_state_location = "europe-west2" + +hab_ci_revision = 4 \ No newline at end of file diff --git a/deployment/hab_pki/variables.tf b/deployment/hab_pki/variables.tf new file mode 100644 index 00000000..be27e398 --- /dev/null +++ b/deployment/hab_pki/variables.tf @@ -0,0 +1,32 @@ +variable "project_id" { + description = "The project ID to host the cluster in" +} + +variable "signing_keyring_location" { + description = "The GCP location to create the signing keyring" +} + +variable "tf_state_location" { + description = "The GCP location to store Terraform remote state" +} + +variable "hab_keylength" { + description = "HAB CA RSA key length" + type = number + // From https://github.com/usbarmory/crucible/blob/master/hab/const.go#L13 + default = 2048 +} + +variable "hab_num_intermediates" { + description = "Number of HAB intermediate CAs" + default = 4 +} + +variable "hab_pki_lifetime" { + description = "Lifetime for HAB PKI certs in seconds" + default = 788400000 // 25 years +} + +variable "hab_ci_revision" { + description = "Revision count for CI HAB PKI certs. This must be incremented if these certs are regenerated for any reason" +} \ No newline at end of file