Skip to content

Certificate Automation

Eric Fitzgerald edited this page Apr 8, 2026 · 2 revisions

Certificate Automation

This guide covers automatic TLS certificate provisioning for TMI using Let's Encrypt with OCI Functions and DNS-01 challenges.

Overview

TMI provides automatic certificate management using:

  • Let's Encrypt - Free, automated TLS certificates
  • OCI Functions - Serverless certificate lifecycle management
  • DNS-01 Challenges - Domain validation via DNS TXT records
  • OCI Vault - Secure certificate and key storage
  • OCI Load Balancer - Automatic certificate deployment

Architecture

                                 +------------------+
                                 |  Let's Encrypt   |
                                 |  ACME Server     |
                                 +--------+---------+
                                          |
                                          | ACME Protocol (DNS-01)
                                          v
+--------------------------------------------------------------------------------+
|                              OCI Tenancy                                        |
|                                                                                 |
|  +----------------+         +------------------+                                |
|  |  Scheduler     |-------->|  OCI Function    |                                |
|  |  (External)    |         |  (certmgr)       |                                |
|  +----------------+         +--------+---------+                                |
|                                      |                                          |
|        +-----------------------------+-----------------------------+            |
|        |                             |                             |            |
|        v                             v                             v            |
|  +----------------+     +------------------+     +------------------+           |
|  | DNS Zone (OCI) |     | OCI Vault        |     | OCI Load Balancer|           |
|  | (TXT records)  |     | (cert storage)   |     | (HTTPS:443)      |           |
|  +----------------+     +------------------+     +---------+--------+           |
|                                                           |                     |
|                                                           v                     |
|                                                 +------------------+            |
|                                                 | TMI Application  |            |
|                                                 +------------------+            |
+--------------------------------------------------------------------------------+

Prerequisites

OCI Resources

  1. DNS Zone - Domain must be hosted in OCI DNS
  2. OCI Vault - For storing certificates and ACME account key
  3. Load Balancer - Target for certificate deployment
  4. Compartment - With appropriate IAM permissions

Required IAM Permissions

The certificate manager function requires:

  • DNS: Manage TXT records in the specified zone
  • Vault: Read and write secrets
  • Load Balancer: Update certificates and listeners

These are automatically created when create_dynamic_group = true.

Quick Start

1. Enable Certificate Automation

In your terraform.tfvars:

# Enable certificate automation
enable_certificate_automation = true

# Domain configuration
domain_name        = "tmi.example.com"
dns_zone_id        = "ocid1.dns-zone.oc1..<unique_id>"
acme_contact_email = "admin@example.com"

# Start with staging for testing
acme_directory = "staging"

# Certificate manager function image
certmgr_image_url = "us-ashburn-1.ocir.io/<namespace>/certmgr:latest"

2. Build and Deploy Function Image

The Makefile targets wrap the Fn Project CLI (fn). The FN_APP environment variable must be set to your OCI Function Application name.

# Build the certificate manager function (runs `fn build`)
make fn-build-certmgr

# Deploy to OCI Functions (runs `fn deploy --app $FN_APP`)
FN_APP=tmi-certmgr make fn-deploy-certmgr

3. Deploy Infrastructure

# Use oci-private or oci-public depending on your deployment
cd terraform/environments/oci-private
terraform init
terraform plan
terraform apply

4. Test with Manual Invocation

# Invoke the function manually
make fn-invoke-certmgr

# Or use the OCI CLI command shown in Terraform outputs
terraform output -module=certificates invoke_command

5. Switch to Production

After verifying the flow works with staging:

acme_directory = "production"

Then terraform apply to update.

Configuration Reference

Terraform Variables

Environment-level variables (e.g. oci-private/variables.tf)

Variable Type Default Description
enable_certificate_automation bool false Enable the certificates module
domain_name string (required) Domain name for TLS certificate
dns_zone_id string (required) OCID of the OCI DNS zone
acme_contact_email string (required) Email for Let's Encrypt notifications
acme_directory string staging ACME directory: staging or production
certificate_renewal_days number 30 Days before expiry to trigger renewal (7-60)
certmgr_image_url string (required) Container image URL for the function

Module-level variables (additional inputs accepted by the certificates module)

Variable Type Default Description
compartment_id string (required) OCI compartment OCID
tenancy_ocid string (required) OCI tenancy OCID (for dynamic group creation)
name_prefix string tmi Prefix for resource names
subnet_id string (required) Subnet OCID for the Function Application
load_balancer_id string (required) OCID of the OCI Load Balancer
vault_id string (required) OCID of the OCI Vault
vault_key_id string (required) OCID of the Vault encryption key
function_memory_mb number 256 Function memory in MB (128, 256, 512, or 1024)
function_timeout_seconds number 300 Function timeout in seconds (30-300)
create_dynamic_group bool true Create dynamic group and IAM policies
existing_dynamic_group_name string null Existing dynamic group name (if create_dynamic_group is false)
tags map(string) {} Freeform tags for all resources

Function Environment Variables

These are automatically configured by Terraform:

Variable Description
CERTMGR_DOMAIN Domain name for certificate
CERTMGR_DNS_ZONE_ID OCI DNS zone OCID
CERTMGR_ACME_EMAIL Let's Encrypt contact email
CERTMGR_RENEWAL_DAYS Days before expiry to renew
CERTMGR_LB_ID OCI Load Balancer OCID
CERTMGR_VAULT_ID OCI Vault OCID
CERTMGR_VAULT_KEY_ID OCI Vault master key OCID
CERTMGR_COMPARTMENT_ID Compartment OCID
CERTMGR_NAME_PREFIX Prefix for resource names
CERTMGR_ACME_DIRECTORY Full ACME directory URL
CERTMGR_DRY_RUN Set to true to skip actual operations (testing only)

Certificate Lifecycle

Initial Issuance

  1. Function starts and loads configuration (4-minute internal timeout)
  2. Checks for existing certificate in Vault
  3. If no certificate or expiring within certificate_renewal_days:
    • Gets or generates ACME account key (ECDSA P-256, stored in Vault)
    • Registers account with Let's Encrypt
    • Requests certificate authorization (DNS-01 challenge)
    • Creates _acme-challenge.<domain> TXT record in OCI DNS (TTL 60s)
    • Waits 60 seconds for DNS propagation
    • Accepts the challenge and waits for authorization
    • Generates a new ECDSA P-256 certificate key and CSR
    • Finalizes the order and receives the certificate chain
    • Stores certificate and private key in Vault
    • Creates (or replaces) certificate on the Load Balancer
    • Updates the HTTPS listener to reference the new certificate
    • Cleans up the DNS TXT record (deferred, runs even on failure)

Renewal Check

  1. Function retrieves certificate from Vault
  2. Checks certificate expiry date
  3. If expiring within certificate_renewal_days:
    • Triggers renewal flow (same as initial issuance)
  4. If not expiring: logs status and exits

Scheduling

OCI does not have a native Terraform resource for function scheduling. Set up daily invocation via one of these methods:

Option 1: OCI Console Resource Scheduler

  1. Navigate to Resource Scheduler in OCI Console
  2. Create a new scheduled job:
    • Type: Function
    • Function: Select {name_prefix}-certmgr
    • Schedule: Daily at desired time (e.g., 0 2 * * * for 2 AM UTC)

Option 2: External Cron

On a machine with OCI CLI configured:

# Add to crontab
0 2 * * * oci fn function invoke --function-id <function-ocid> --file - --body ''

Option 3: GitHub Actions

name: Certificate Renewal Check
on:
  schedule:
    - cron: '0 2 * * *'  # Daily at 2 AM UTC

jobs:
  check-certificate:
    runs-on: ubuntu-latest
    steps:
      - name: Setup OCI CLI
        uses: oracle-actions/configure-oci-cli@v1.3.0
        with:
          user: ${{ secrets.OCI_USER_OCID }}
          tenancy: ${{ secrets.OCI_TENANCY_OCID }}
          fingerprint: ${{ secrets.OCI_FINGERPRINT }}
          private_key: ${{ secrets.OCI_PRIVATE_KEY }}
          region: ${{ secrets.OCI_REGION }}

      - name: Invoke Certificate Manager
        run: |
          oci fn function invoke \
            --function-id ${{ secrets.CERTMGR_FUNCTION_ID }} \
            --file - --body ''

Makefile Targets

All targets except fn-build-certmgr require the FN_APP environment variable to be set to the OCI Function Application name. All targets require the Fn Project CLI (fn) to be installed.

Target Description
fn-build-certmgr Build the certificate manager function (fn build)
fn-deploy-certmgr Build and deploy function to OCI Functions (fn deploy --app $FN_APP)
fn-invoke-certmgr Manually invoke the function (fn invoke $FN_APP certmgr)
fn-logs-certmgr View function logs (fn logs $FN_APP certmgr)

Secrets Storage

The certificates module creates these secrets in OCI Vault:

Secret Name Content Purpose
{name_prefix}-acme-account-key PEM-encoded private key ACME account authentication
{name_prefix}-certificate PEM-encoded certificate TLS certificate chain
{name_prefix}-private-key PEM-encoded private key TLS private key

All secrets are:

  • Encrypted with the Vault master key (AES-256)
  • Accessible only via function Resource Principal
  • Version-controlled (previous versions retained)

Troubleshooting

Function Invocation Fails

  1. Check function logs:

    make fn-logs-certmgr
  2. Verify function has correct IAM policies:

    • Dynamic group includes the function
    • Policies grant DNS, Vault, and LB access
  3. Check function timeout (default: 300s should be sufficient)

DNS Challenge Fails

  1. Verify DNS zone OCID is correct
  2. Check that the zone contains the target domain
  3. Ensure IAM policy allows DNS record management
  4. Check DNS propagation:
    dig TXT _acme-challenge.tmi.example.com

Certificate Not Appearing on Load Balancer

  1. Verify function completed successfully (check logs)
  2. Check Load Balancer has HTTPS listener configured
  3. Verify IAM policy allows Load Balancer updates
  4. Check certificate in Vault contains valid PEM data

ACME Rate Limits

Let's Encrypt has rate limits:

  • Staging: No practical limits (for testing)
  • Production: 50 certificates per domain per week

If rate limited:

  1. Wait for rate limit window to reset (1 week)
  2. Use staging for testing
  3. Consider wildcard certificate for subdomains

Function Timeout

The function has a 5-minute (300 second) timeout, which is the OCI Functions maximum. The code enforces a 4-minute internal context deadline to allow for cleanup. Typical execution:

  • DNS propagation wait: 60 seconds (hardcoded)
  • ACME challenge verification: ~30 seconds
  • Load Balancer work requests: ~30 seconds
  • Total: ~2-3 minutes

If timing out:

  1. The default function_timeout_seconds is already 300 (the OCI maximum). Verify it has not been reduced.
  2. Check DNS zone TTL settings
  3. Review function logs for delays in ACME or Load Balancer work requests

Staging vs Production

Aspect Staging Production
URL acme-staging-v02.api.letsencrypt.org acme-v02.api.letsencrypt.org
Rate Limits Very generous 50 certs/domain/week
Browser Trust Not trusted Trusted
Use Case Testing, development Production deployments

Best Practice: Always test with staging first, then switch to production.

Free Tier Impact

The certificate automation uses these Free Tier resources:

Resource Free Tier Limit Usage
Functions 2M invocations/month ~30/month (daily)
Functions Memory 400K GB-seconds ~1.3 GB-sec/month
Vault Secrets 20 secrets +3 secrets
DNS Queries 1000/day per zone ~5/day

Impact: Minimal - well within Free Tier limits.

Security Considerations

Resource Principal Authentication

The function uses OCI Resource Principal for authentication:

  • No static credentials in configuration
  • Automatic credential rotation by OCI
  • Scoped to specific compartment and resources

Least Privilege IAM

The created policies grant only necessary permissions:

  • DNS: Only TXT records in specified zone
  • Vault: Only secrets in specified vault
  • Load Balancer: Only specified load balancer

Secret Protection

  • ACME account key stored encrypted in Vault
  • Private key never leaves OCI (Vault → Load Balancer)
  • Certificate chain stored for transparency

TMI Server Direct TLS

The certificate automation described above deploys certificates to the OCI Load Balancer, which terminates TLS before forwarding traffic to the TMI application. The TMI server itself also supports direct TLS termination, configured via these environment variables:

Variable Description
TMI_SERVER_TLS_ENABLED Set to true to enable TLS on the server
TMI_SERVER_TLS_CERT_FILE Path to PEM-encoded certificate file
TMI_SERVER_TLS_KEY_FILE Path to PEM-encoded private key file
TMI_SERVER_TLS_SUBJECT_NAME Expected TLS subject name
TMI_SERVER_HTTP_TO_HTTPS_REDIRECT Set to true to redirect HTTP to HTTPS

When using OCI Load Balancer TLS termination (the recommended approach for OCI deployments), these server-level TLS settings are not required. The Load Balancer handles HTTPS and forwards plain HTTP to the TMI container.

Related Pages

Clone this wiki locally