From 47aaaa2143aea1215d39febdfe5c429c3d4d1a64 Mon Sep 17 00:00:00 2001 From: Patrick de Ruiter Date: Sat, 1 Nov 2025 06:18:46 +0100 Subject: [PATCH] Initial commit: Terraform certificate automation module - Add Vault AppRole and Ansible integration for certificates - Configure policies and secret engines - Add comprehensive documentation --- .gitignore | 58 +++++++ README.md | 146 ++++++++++++++++++ terraform/backend.tf | 21 +++ terraform/data.tf | 3 + .../files/scripts/lvresize-k8s-master.sh | 18 +++ .../files/scripts/lvresize-k8s-worker.sh | 16 ++ terraform/main.tf | 44 ++++++ terraform/outputs.tf | 7 + terraform/provider.tf | 26 ++++ terraform/templates/configure-openbsd.tpl | 21 +++ terraform/templates/host_script.tpl | 12 ++ terraform/templates/host_script2.tpl | 12 ++ terraform/templates/hosts.tpl | 33 ++++ terraform/templates/vm_hosts.tpl | 8 + terraform/variables.tf | 30 ++++ 15 files changed, 455 insertions(+) create mode 100755 .gitignore create mode 100755 README.md create mode 100644 terraform/backend.tf create mode 100755 terraform/data.tf create mode 100755 terraform/files/scripts/lvresize-k8s-master.sh create mode 100755 terraform/files/scripts/lvresize-k8s-worker.sh create mode 100755 terraform/main.tf create mode 100755 terraform/outputs.tf create mode 100755 terraform/provider.tf create mode 100644 terraform/templates/configure-openbsd.tpl create mode 100755 terraform/templates/host_script.tpl create mode 100755 terraform/templates/host_script2.tpl create mode 100755 terraform/templates/hosts.tpl create mode 100755 terraform/templates/vm_hosts.tpl create mode 100755 terraform/variables.tf diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..46562e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,58 @@ +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +*tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Ignore lock files (optional, some prefer to commit these) +.terraform.lock.hcl + +# Ansible directories +ansible/ +**/ansible/ + +# SSH keys and sensitive files +*.pem +*.key +**/files/kubernetes_key* +**/files/*_key + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# Backup files +*.backup +*.bak diff --git a/README.md b/README.md new file mode 100755 index 0000000..e7f6d40 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# Terraform Certificate Automation Module + +This Terraform module automates TLS certificate deployment by creating Vault AppRole authentication and policies for automated certificate retrieval and renewal. + +## Purpose + +This module sets up the infrastructure needed to automatically fetch and deploy TLS certificates from HashiCorp Vault to target servers. It creates: + +- Vault policies with read-only access to certificate secrets +- AppRole authentication backend configuration +- AppRole credentials for secure authentication +- Ansible inventory entries for automated deployment + +## What It Does + +1. **Creates Vault Policy**: Generates a read-only policy for accessing certificate secrets at a specific path +2. **Configures AppRole**: Sets up an AppRole with appropriate token TTLs and secret ID expiration +3. **Generates Credentials**: Creates AppRole role_id and secret_id for authentication +4. **Configures Ansible Host**: Registers the target server in Ansible inventory with Vault credentials + +## Prerequisites + +- HashiCorp Vault server with AppRole auth method enabled +- Terraform >= 0.13 +- Vault provider configured with appropriate credentials +- Ansible provider (for inventory management) + +## Usage + +### Basic Example + +```hcl +module "certificate_automation" { + source = "./terraform-certificate-automation/terraform" + + environment = "production" + short_hostname = "web01" + vault_address = "https://vault.example.com:8200" +} +``` + +### Complete Example + +```hcl +module "certificate_automation" { + source = "./terraform-certificate-automation/terraform" + + environment = "prod" + short_hostname = "api-server" + vault_address = "https://vault.internal.example.com:8200" +} + +# Access the generated credentials (sensitive) +output "approle_creds" { + value = module.certificate_automation.approle_credentials + sensitive = true +} +``` + +## Inputs + +| Name | Type | Description | Required | Validation | +|------|------|-------------|----------|------------| +| `environment` | string | Environment name (e.g., dev, staging, prod) | Yes | Alphanumeric, hyphens, and underscores only | +| `short_hostname` | string | Short hostname for the target server | Yes | Alphanumeric and hyphens only | +| `vault_address` | string | Vault server address | Yes | Must be valid HTTP/HTTPS URL | + +## Outputs + +| Name | Description | Sensitive | +|------|-------------|-----------| +| `approle_credentials` | Object containing role_id and secret_id | Yes | + +## Generated Resources + +This module creates the following Vault resources: + +- **Policy**: `{environment}-{short_hostname}-cert-policy` + - Path: `secret/data/{environment}/{short_hostname}/certificate` + - Capability: read-only + +- **AppRole**: `{environment}-{short_hostname}-approle` + - Token TTL: 1 hour + - Token Max TTL: 4 hours + - Secret ID TTL: 24 hours + +- **Ansible Host**: Added to `consul_template` group with Vault credentials + +## Secret Path Convention + +Certificates are expected to be stored in Vault at: +``` +secret/data/{environment}/{short_hostname}/certificate +``` + +Example: `secret/data/production/web01/certificate` + +## Token and Secret TTLs + +- **Token TTL**: 1 hour (tokens automatically renew) +- **Token Max TTL**: 4 hours (maximum lifetime before re-authentication) +- **Secret ID TTL**: 24 hours (secret_id expires after 24 hours) + +## Integration with Ansible + +This module automatically creates an Ansible inventory entry with: + +- Inventory hostname: `{short_hostname}` +- Group: `consul_template` +- Variables: + - `vault_approle_role_id` + - `vault_approle_secret_id` + - `vault_address` + - `environment` + - `short_hostname` + +The generated inventory can be used with the included Ansible playbooks in the `ansible/` directory to deploy consul-template for automated certificate retrieval. + +## Deployment Steps + +1. Deploy Vault AppRoles and policies with Terraform +2. Generate Ansible Vault credentials (`ansible_vault_output.sh`) +3. Run Ansible playbook to deploy consul-template +4. consul-template automatically fetches and renews certificates from Vault + +## Security Considerations + +- AppRole credentials are marked as sensitive in outputs +- Policies follow the principle of least privilege (read-only) +- Secret IDs are automatically rotated (24-hour TTL) +- Tokens have limited lifetime (max 4 hours) +- Ensure sensitive files (`vault_credentials.yml`) are always encrypted and handled securely + +## Related Components + +This module works in conjunction with: + +- **Ansible Playbooks** (in `ansible/` directory): Deploy consul-template to target servers +- **Consul-Template**: Automatically fetches and renews certificates from Vault +- **Vault PKI**: Stores certificates that this module provides access to + +## Notes + +- Ensure the Vault AppRole auth backend is enabled before using this module +- The Ansible directory should be ignored when using this as a Terraform module +- Certificate secrets must be manually populated in Vault at the expected path diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000..b742446 --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,21 @@ +terraform { + backend "s3" { + endpoints = { + s3 = "https://minio.bsdserver.nl:443" + } + + bucket = "home-terraform" + key = "home/security/encryption/certificate-automation.tfstate" + + # Configure credentials via environment variables: + # export AWS_ACCESS_KEY_ID="your-access-key" + # export AWS_SECRET_ACCESS_KEY="your-secret-key" + + region = "main" + skip_credentials_validation = true + skip_metadata_api_check = true + skip_requesting_account_id = true + skip_region_validation = true + use_path_style = true + } +} diff --git a/terraform/data.tf b/terraform/data.tf new file mode 100755 index 0000000..8a2c63a --- /dev/null +++ b/terraform/data.tf @@ -0,0 +1,3 @@ +#data "vault_generic_secret" "dns" { +# path = "secret/dns" +#} diff --git a/terraform/files/scripts/lvresize-k8s-master.sh b/terraform/files/scripts/lvresize-k8s-master.sh new file mode 100755 index 0000000..2750fb7 --- /dev/null +++ b/terraform/files/scripts/lvresize-k8s-master.sh @@ -0,0 +1,18 @@ +#!/bin/sh +#sudo /usr/bin/growpart /dev/sda 2 +#sudo /usr/sbin/pvresize -y -q /dev/sda2 +#sudo /usr/sbin/lvresize -y -q -r -L 6G /dev/vg_sys/lv_usr +#sudo /usr/sbin/lvresize -y -q -r -L 45G /dev/vg_sys/lv_var +#sudo /usr/sbin/lvresize -y -q -r -L 2G /dev/vg_sys/lv_var_log +#sudo /usr/sbin/resize2fs -fF /dev/vg_sys/lv_usr +#sudo /usr/sbin/resize2fs -fF /dev/vg_sys/lv_var +#sudo /usr/sbin/resize2fs -fF /dev/vg_sys/lv_var_log +export DEBIAN_FRONTEND=noninteractive +export DEBIAN_PRIORITY=critical +sudo -E apt-get -qy update +sudo -E apt-get -qy -o "Dpkg::Options::=--force-confdef" -o "Dpkg::Options::=--force-confold" upgrade +sudo -E apt-get -qy -o "Dpkg::Options::=--force-confdef" -o "Dpkg::Options::=--force-confold" dist-upgrade +sudo -E apt-get -qy autoclean +sudo mv /etc/netplan/50-cloud-init.yaml /etc/netplan/00-installer-config.yaml +sudo netplan apply +exit 0 diff --git a/terraform/files/scripts/lvresize-k8s-worker.sh b/terraform/files/scripts/lvresize-k8s-worker.sh new file mode 100755 index 0000000..7e7c4ed --- /dev/null +++ b/terraform/files/scripts/lvresize-k8s-worker.sh @@ -0,0 +1,16 @@ +#!/bin/sh +sudo /usr/bin/growpart /dev/sda 2 +sudo /usr/sbin/pvresize -y -q /dev/sda2 +sudo /usr/sbin/lvresize -y -q -r -L 6G /dev/vg_sys/lv_usr +sudo /usr/sbin/lvresize -y -q -r -L 20G /dev/vg_sys/lv_var +sudo /usr/sbin/lvresize -y -q -r -L 2G /dev/vg_sys/lv_var_log +sudo /usr/sbin/resize2fs -fF /dev/vg_sys/lv_usr +sudo /usr/sbin/resize2fs -fF /dev/vg_sys/lv_var +sudo /usr/sbin/resize2fs -fF /dev/vg_sys/lv_var_log +#export DEBIAN_FRONTEND=noninteractive +#export DEBIAN_PRIORITY=critical +#sudo -E apt-get -qy update +#sudo -E apt-get -qy -o "Dpkg::Options::=--force-confdef" -o "Dpkg::Options::=--force-confold" upgrade +#sudo -E apt-get -qy -o "Dpkg::Options::=--force-confdef" -o "Dpkg::Options::=--force-confold" dist-upgrade +#sudo -E apt-get -qy autoclean +exit 0 diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100755 index 0000000..3b722d7 --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,44 @@ +locals { + secret_path = "secret/data/${var.environment}/${var.short_hostname}/certificate" + policy_name = "${var.environment}-${var.short_hostname}-cert-policy" + approle_name = "${var.environment}-${var.short_hostname}-approle" +} + +resource "vault_policy" "cert_access" { + name = local.policy_name + policy = < /etc/myname + +# Set DNS +echo "nameserver ${dns}" > /etc/resolv.conf + +# Configure network interfaces (Assuming the interface is vmx0) +echo "inet ${ip} ${netmask}" > /etc/hostname.vmx0 + +# Configure default gateway +echo "${gateway}" > /etc/mygate +# Any additional commands go here + +# Restart networking service or apply changes +/etc/netstart + +# Exit the script +exit 0 + diff --git a/terraform/templates/host_script.tpl b/terraform/templates/host_script.tpl new file mode 100755 index 0000000..1ab2d5a --- /dev/null +++ b/terraform/templates/host_script.tpl @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# +echo "Setting SSH Key" +# +ssh-add ~/.ssh/id_ed25519 + +# +echo "Adding vsphere nodes hostnames to known hosts" +%{ for hostname in k8s_master_name ~} +ssh-keyscan -H ${hostname} >> ~/.ssh/known_hosts +%{ endfor ~} + diff --git a/terraform/templates/host_script2.tpl b/terraform/templates/host_script2.tpl new file mode 100755 index 0000000..db8f3d3 --- /dev/null +++ b/terraform/templates/host_script2.tpl @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# +echo "Setting SSH Key" +# +ssh-add ~/.ssh/id_ed25519 + +# +echo "Adding vsphere nodes hostnames to known hosts" +%{ for hostname in vsphere_vm_name ~} +ssh-keyscan -H ${hostname} >> ~/.ssh/known_hosts +%{ endfor ~} + diff --git a/terraform/templates/hosts.tpl b/terraform/templates/hosts.tpl new file mode 100755 index 0000000..391fe6d --- /dev/null +++ b/terraform/templates/hosts.tpl @@ -0,0 +1,33 @@ +# ## Configure 'ip' variable to bind kubernetes services on a +# ## different ip than the default iface +# ## We should set etcd_member_name for etcd cluster. The node that is not a etcd member do not need to set the value, or can set the empty string value. +[all] +%{ for ip in k8s_master_ip ~} +${ip}.${domain} +%{ endfor ~} + +%{ for ip in k8s_worker_name ~} +${ip}.${domain} +%{ endfor ~} + +[kube_control_plane] +%{ for ip in k8s_master_ip ~} +${ip}.${domain} +%{ endfor ~} + +[etcd] +%{ for ip in k8s_master_ip ~} +${ip}.${domain} +%{ endfor ~} + +[kube_node] +%{ for ip in k8s_worker_name ~} +${ip}.${domain} +%{ endfor ~} + +[calico_rr] + +[k8s_cluster:children] +kube_control_plane +kube_node +calico_rr diff --git a/terraform/templates/vm_hosts.tpl b/terraform/templates/vm_hosts.tpl new file mode 100755 index 0000000..2f983ec --- /dev/null +++ b/terraform/templates/vm_hosts.tpl @@ -0,0 +1,8 @@ +# ## Configure 'ip' variable to bind kubernetes services on a +# ## different ip than the default iface +# ## We should set etcd_member_name for etcd cluster. The node that is not a etcd member do not need to set the value, or can set the empty string value. +[all] +%{ for ip in vsphere_vm_ip ~} +${ip}.${domain} +%{ endfor ~} + diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100755 index 0000000..f9d8818 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,30 @@ +variable "environment" { + type = string + description = "Environment name (e.g., dev, staging, prod)" + + validation { + condition = can(regex("^[a-zA-Z0-9-_]+$", var.environment)) + error_message = "Environment must contain only alphanumeric characters, hyphens, and underscores." + } +} + +variable "short_hostname" { + type = string + description = "Short hostname for the target server" + + validation { + condition = can(regex("^[a-zA-Z0-9-]+$", var.short_hostname)) + error_message = "Hostname must contain only alphanumeric characters and hyphens." + } +} + +variable "vault_address" { + type = string + description = "Vault server address (e.g., https://vault.example.com:8200)" + + validation { + condition = can(regex("^https?://", var.vault_address)) + error_message = "Vault address must be a valid HTTP or HTTPS URL." + } +} +