Initial commit: Terraform certificate automation module
- Add Vault AppRole and Ansible integration for certificates - Configure policies and secret engines - Add comprehensive documentation
This commit is contained in:
commit
47aaaa2143
58
.gitignore
vendored
Executable file
58
.gitignore
vendored
Executable file
@ -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
|
||||
146
README.md
Executable file
146
README.md
Executable file
@ -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
|
||||
21
terraform/backend.tf
Normal file
21
terraform/backend.tf
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
3
terraform/data.tf
Executable file
3
terraform/data.tf
Executable file
@ -0,0 +1,3 @@
|
||||
#data "vault_generic_secret" "dns" {
|
||||
# path = "secret/dns"
|
||||
#}
|
||||
18
terraform/files/scripts/lvresize-k8s-master.sh
Executable file
18
terraform/files/scripts/lvresize-k8s-master.sh
Executable file
@ -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
|
||||
16
terraform/files/scripts/lvresize-k8s-worker.sh
Executable file
16
terraform/files/scripts/lvresize-k8s-worker.sh
Executable file
@ -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
|
||||
44
terraform/main.tf
Executable file
44
terraform/main.tf
Executable file
@ -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 = <<EOT
|
||||
path "${local.secret_path}" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
EOT
|
||||
}
|
||||
|
||||
resource "vault_approle_auth_backend_role" "cert_role" {
|
||||
backend = "approle"
|
||||
role_name = local.approle_name
|
||||
token_policies = [vault_policy.cert_access.name]
|
||||
token_ttl = "1h"
|
||||
token_max_ttl = "4h"
|
||||
secret_id_ttl = "24h"
|
||||
}
|
||||
|
||||
resource "vault_approle_auth_backend_role_secret_id" "cert_role_secret" {
|
||||
backend = "approle"
|
||||
role_name = vault_approle_auth_backend_role.cert_role.role_name
|
||||
}
|
||||
|
||||
resource "ansible_host" "consul_template_node" {
|
||||
inventory_hostname = var.short_hostname
|
||||
groups = ["consul_template"]
|
||||
|
||||
vars = {
|
||||
ansible_user = "ansible"
|
||||
ansible_ssh_private_key_file = "~/.ssh/id_ed25519"
|
||||
ansible_python_interpreter = "/usr/bin/python3"
|
||||
vault_approle_role_id = vault_approle_auth_backend_role.cert_role.role_id
|
||||
vault_approle_secret_id = vault_approle_auth_backend_role_secret_id.cert_role_secret.secret_id
|
||||
vault_address = var.vault_address
|
||||
environment = var.environment
|
||||
short_hostname = var.short_hostname
|
||||
}
|
||||
}
|
||||
7
terraform/outputs.tf
Executable file
7
terraform/outputs.tf
Executable file
@ -0,0 +1,7 @@
|
||||
output "approle_credentials" {
|
||||
value = {
|
||||
role_id = vault_approle_auth_backend_role.cert_role.role_id
|
||||
secret_id = vault_approle_auth_backend_role_secret_id.cert_role_secret.secret_id
|
||||
}
|
||||
sensitive = true
|
||||
}
|
||||
26
terraform/provider.tf
Executable file
26
terraform/provider.tf
Executable file
@ -0,0 +1,26 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
ansible = {
|
||||
source = "ansible/ansible"
|
||||
}
|
||||
vault = {
|
||||
source = "hashicorp/vault"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Configure the Vault provider
|
||||
provider "vault" {
|
||||
address = var.vault_address
|
||||
auth_login {
|
||||
path = "auth/approle/login"
|
||||
parameters = {
|
||||
role_id = var.role_id
|
||||
secret_id = var.secret_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Ansible Provider
|
||||
provider "ansible" {
|
||||
}
|
||||
21
terraform/templates/configure-openbsd.tpl
Normal file
21
terraform/templates/configure-openbsd.tpl
Normal file
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Set hostname
|
||||
echo "${hostname}" > /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
|
||||
|
||||
12
terraform/templates/host_script.tpl
Executable file
12
terraform/templates/host_script.tpl
Executable file
@ -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 ~}
|
||||
|
||||
12
terraform/templates/host_script2.tpl
Executable file
12
terraform/templates/host_script2.tpl
Executable file
@ -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 ~}
|
||||
|
||||
33
terraform/templates/hosts.tpl
Executable file
33
terraform/templates/hosts.tpl
Executable file
@ -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
|
||||
8
terraform/templates/vm_hosts.tpl
Executable file
8
terraform/templates/vm_hosts.tpl
Executable file
@ -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 ~}
|
||||
|
||||
30
terraform/variables.tf
Executable file
30
terraform/variables.tf
Executable file
@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user