Last active
October 4, 2023 09:49
-
-
Save atrull/555b39aa305a93d5b8200cced3c7cda1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
## Complete end to end mTLS in Cloudflare with a Vault-based CA.. and signing client certs with that | |
## | |
# This assumes you have a CA already setup in vault - I recommend a root and an intermediate | |
# | |
# We assume that you have an existing vault state that incorporates these outputs like these: | |
# output "prod_ca_cert" { | |
# value = "${vault_pki_secret_backend_root_sign_intermediate.intermediate.certificate}\n${tls_self_signed_cert.root_ca.cert_pem}" | |
# } | |
# output "prod_ca_int_path" { | |
# value = vault_mount.ca_int.path | |
# } | |
# output "prod_ca_int_client_signing_role" { | |
# value = vault_pki_secret_backend_role.role-client-cert.name | |
# } | |
# output "prod_ca_int_server_signing_role" { | |
# value = vault_pki_secret_backend_role.role-server-cert.name | |
# } | |
# used for details for the CA process | |
data "terraform_remote_state" "vault_secrets" { | |
backend = "s3" # etc | |
config = { | |
# your bits here | |
} | |
} | |
# we get the zone id from domain | |
data "cloudflare_zones" "ourcorp_com" { | |
filter { | |
name = "ourcorp.com" | |
} | |
} | |
# we get the env | |
data "external" "env" { program = ["jq", "-n", "env"] } | |
# We upload our CA cert to Cloudflare | |
resource "cloudflare_mtls_certificate" "prod_ca_cert" { | |
account_id = var.ourcorp_cloudflare_account_id | |
name = "OurCorp Prod CA" | |
certificates = data.terraform_remote_state.vault_secrets.outputs.prod_ca_cert | |
ca = true | |
} | |
# curl2 style hostname association - we associate the hostname with the CA cert so that we can get mTLS working | |
# we do it this way because the current cloudflare provider doesn't have a resource to do this that I can find. | |
data "curl2" "associate_ca_cert_with_login2_staging_ourcorp_com" { | |
http_method = "PUT" | |
uri = "https://api.cloudflare.com/client/v4/zones/${data.cloudflare_zones.ourcorp_com.zones[0].id}/certificate_authorities/hostname_associations" | |
auth_type = "Bearer" | |
bearer_token = data.external.env.result.CLOUDFLARE_API_TOKEN | |
json = jsonencode( | |
{ | |
hostnames = ["login2.staging.ourcorp.com"], | |
mtls_certificate_id = cloudflare_mtls_certificate.prod_ca_cert.id | |
} | |
) | |
} | |
# null_resource style hostname association - probably more trustworthy but uglier | |
# https://developers.cloudflare.com/api/operations/client-certificate-for-a-zone-put-hostname-associations | |
resource "null_resource" "associate_ca_cert_with_login2_staging_ourcorp_com" { | |
triggers = { | |
always_run = "${timestamp()}" | |
} | |
provisioner "local-exec" { | |
Bootstrap script called with private_ip of each node in the cluster | |
command = <<EOT | |
curl --request PUT \ | |
--url https://api.cloudflare.com/client/v4/zones/${data.cloudflare_zones.ourcorp_com.zones[0].id}/certificate_authorities/hostname_associations \ | |
--header 'Content-Type: application/json' \ | |
--header 'Authorization: Bearer ${data.external.env.result.CLOUDFLARE_API_TOKEN}' \ | |
--data '{ "hostnames": [ "login2.staging.ourcorp.com" ], "mtls_certificate_id": "${cloudflare_mtls_certificate.prod_ca_cert.id}" }' | |
EOT | |
} | |
} | |
# WAF rule... to enforce the client cert validity on the specific url | |
resource "cloudflare_ruleset" "firewall_waf_ourcorp_com" { | |
zone_id = data.cloudflare_zones.ourcorp_com.zones[0].id | |
name = "ourcorp.com Custom Firewall WAF" | |
description = "Zone-level WAF Custom Rules config for ourcorp.com" | |
kind = "zone" | |
phase = "http_request_firewall_custom" | |
rules { | |
action = "block" | |
description = "mTLS-enforced authentication to login2 for Ourcorp" | |
enabled = true | |
expression = "((not cf.tls_client_auth.cert_verified or cf.tls_client_auth.cert_revoked) and (http.request.full_uri eq \"https://login2.staging.ourcorp.com\"))" | |
action_parameters { | |
response { | |
content = "Please provide a valid client certificate" | |
content_type = "text/plain" | |
status_code = 403 | |
} | |
} | |
} | |
} | |
# Testing Client Cert Access | |
# client cert | |
resource "vault_pki_secret_backend_cert" "ourcorp_client_testing" { | |
backend = data.terraform_remote_state.vault_secrets.outputs.prod_ca_int_path | |
name = data.terraform_remote_state.vault_secrets.outputs.prod_ca_int_client_signing_role | |
common_name = "ourcorp-client-testing" | |
ttl = 2592000 # shortlived testing - 1 month cert | |
} | |
# local certs for ssl test | |
resource "local_sensitive_file" "ourcorp_client_testing_cert" { | |
content = vault_pki_secret_backend_cert.ourcorp_client_testing.certificate | |
filename = "${path.module}/ourcorp_client_testing_cert.pem" | |
} | |
resource "local_sensitive_file" "ourcorp_client_testing_private_key" { | |
content = vault_pki_secret_backend_cert.ourcorp_client_testing.private_key | |
filename = "${path.module}/ourcorp_client_testing_private_key.pem" | |
} | |
# real clients would probably have a longer cert and so forth, and ideally they provide their own CSR | |
# curl -sv --cert ourcorp_client_testing_cert.pem --key ourcorp_client_testing_private_key.pem https://login2.staging.ourcorp.com/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment