Skip to content

Commit b7ce7fa

Browse files
sean-navapbcSean Thomas
andauthored
Add scheduled workflow to renew TLS certificates (#23)
* Add scheduled workflow to renew TLS certificates Add a monthly CI job that runs terraform plan (for verification) targeting module.domain in the network layer for dev, staging, and prod networks. This prevents ACME-issued certificates from expiring on stable networks that don't get regular terraform applies. Supports manual dispatch with optional network name filter. * Add push trigger on branch for testing terraform plan * Add plan/apply flow with GitHub issue notifications * Fix PIPESTATUS to capture terraform exit code through tee * Address PR review: remove push trigger, unused var, auto-create label * Change schedule to twice a month (1st and 15th) * Refactor: use credentials action, tighter targets, always apply - Update configure-azure-credentials to accept optional network_name input, matching the pattern from template-infra's configure-aws-credentials - Scope terraform targets to individual certificate resources instead of the entire domain module to avoid touching DNS infrastructure - Remove issue creation flow in favor of always applying (set and forget) - Use the credentials action instead of inlined auth logic * Fix dead link to main.tf.jinja in set-up-network docs The file was renamed from main.tf to main.tf.jinja but the doc link was not updated, causing the markdown link checker to fail. * Remove dead variable and add concurrency guard - Remove unused network_name variable in configure-azure-credentials "Get Azure client id" step (only account_name is used) - Add concurrency key per network to prevent overlapping terraform applies against the same state * Move cert renewal logic to bin script, revert docs link fix - Create bin/renew-tls-certificates script that wraps terraform-init and terraform-apply with the certificate-specific targets - Simplify workflow to call the bin script instead of inline terraform - Revert set-up-network.md link back to main.tf (installed docs don't have the .jinja extension) * Pass network_name variable to terraform in renew-tls-certificates The script was missing -var="network_name=..." which caused terraform to fail with "No value for required variable" since the networks module has no .tfvars files and relies on the variable being passed explicitly. * Stop exporting ARM_SUBSCRIPTION_ID as env var The infra code specifies subscription_id in each provider block, so the ARM_SUBSCRIPTION_ID env var is unnecessary and can cause issues with cross-subscription resources (e.g. domain provider referencing a different subscription than the network). Pass the subscription id to azure/login via step output instead. * Set up OIDC federated token for Azure SDK auth The ACME provider's DNS solver uses the Go Azure SDK's DefaultAzureCredential, which doesn't understand ARM_* env vars. Request a GitHub OIDC token and write it to a file so the SDK's WorkloadIdentityCredential can authenticate for DNS challenges. * Remove explicit OIDC token setup, rely on Azure CLI credential azure/login already authenticates the CLI, so DefaultAzureCredential should pick it up via AzureCLICredential without needing to manually fetch and store an OIDC token. * Ignore main.tf link in markdown link checker infra/networks/main.tf only exists in instantiated repos (it's main.tf.jinja in the template), so the link checker always fails on this file reference in set-up-network.md. * Revert markdownlint config change, move to separate PR --------- Co-authored-by: Sean Thomas <sean.thomas@navapbc.com>
1 parent 7e4b170 commit b7ce7fa

3 files changed

Lines changed: 96 additions & 9 deletions

File tree

.github/actions/configure-azure-credentials/action.yml

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
name: Configure Azure Credentials
22
description: |
3-
Configure Azure Credentials for a given application and environment so that
4-
the GitHub Actions workflow can access Azure resources.
3+
Configure Azure Credentials so that the GitHub Actions workflow can access
4+
Azure resources.
55
66
This is a wrapper around https://github.com/azure/login that first determines
77
the tenant, subscription, and client ids based on the configuration in
88
app-config.
99
10+
Authenticate using one of the following options:
11+
1. Authenticate by network_name
12+
2. Authenticate by app_name and environment
13+
1014
inputs:
15+
network_name:
16+
description: 'Name of network (e.g. dev, staging, prod)'
17+
required: false
1118
app_name:
1219
description: 'Name of application folder under /infra'
13-
required: true
20+
required: false
1421
environment:
1522
description: 'Name of environment (dev, staging, prod) that resources live in, or "shared" for resources that are shared across environments'
16-
required: true
23+
required: false
1724

1825
runs:
1926
using: "composite"
@@ -33,7 +40,7 @@ runs:
3340
- name: Get account name from network
3441
id: get-account-name
3542
run: |
36-
network_name="${{ steps.get-network-name.outputs.network_name }}"
43+
network_name="${{ inputs.network_name || steps.get-network-name.outputs.network_name }}"
3744
echo "Get account name for network: ${network_name}"
3845
3946
account_name=$(./bin/account-name-for-network "${network_name}")
@@ -43,10 +50,10 @@ runs:
4350
shell: bash
4451

4552
- name: Get Azure client id, tenant id, and subscription id
53+
id: get-azure-config
4654
run: |
4755
echo "::group::Azure account authentication details"
4856
49-
network_name="${{ steps.get-network-name.outputs.network_name }}"
5057
account_name="${{ steps.get-account-name.outputs.account_name }}"
5158
5259
terraform -chdir=infra/project-config init > /dev/null
@@ -63,14 +70,20 @@ runs:
6370
6471
echo "::endgroup::"
6572
66-
echo "Setting env vars ARM_TENANT_ID, ARM_SUBSCRIPTION_ID, and ARM_CLIENT_ID..."
73+
# Set ARM_TENANT_ID and ARM_CLIENT_ID as env vars for terraform.
74+
# ARM_SUBSCRIPTION_ID is intentionally not set — the infra code
75+
# specifies subscription_id in each provider block, and setting
76+
# the env var can cause issues with cross-subscription resources.
6777
echo "ARM_TENANT_ID=$TENANT_ID" >> "$GITHUB_ENV"
68-
echo "ARM_SUBSCRIPTION_ID=$account_id" >> "$GITHUB_ENV"
6978
echo "ARM_CLIENT_ID=$CLIENT_ID" >> "$GITHUB_ENV"
79+
80+
# Pass subscription id as step output for azure/login only
81+
echo "subscription_id=${account_id}" >> "$GITHUB_OUTPUT"
7082
shell: bash
7183

7284
- uses: azure/login@v2
7385
with:
7486
tenant-id: ${{ env.ARM_TENANT_ID }}
75-
subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }}
87+
subscription-id: ${{ steps.get-azure-config.outputs.subscription_id }}
7688
client-id: ${{ env.ARM_CLIENT_ID }}
89+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Renew TLS Certificates
2+
3+
on:
4+
schedule:
5+
# Run twice a month (1st and 15th) at midnight UTC
6+
# Gives ample buffer on 90-day Let's Encrypt certs
7+
- cron: "0 0 1,15 * *"
8+
workflow_dispatch:
9+
inputs:
10+
network_name:
11+
description: "Network to renew certificates for (leave empty for all)"
12+
required: false
13+
type: string
14+
15+
permissions:
16+
contents: read
17+
id-token: write
18+
19+
jobs:
20+
renew-certs:
21+
name: Renew certs (${{ matrix.network_name }})
22+
runs-on: ubuntu-latest
23+
concurrency: renew-tls-certs-${{ matrix.network_name }}
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
network_name: [dev, staging, prod]
28+
29+
steps:
30+
- name: Check if network requested
31+
id: check
32+
run: |
33+
if [[ -n "${{ inputs.network_name }}" && "${{ inputs.network_name }}" != "${{ matrix.network_name }}" ]]; then
34+
echo "skip=true" >> "$GITHUB_OUTPUT"
35+
fi
36+
37+
- uses: actions/checkout@v4
38+
if: steps.check.outputs.skip != 'true'
39+
40+
- name: Set up Terraform
41+
if: steps.check.outputs.skip != 'true'
42+
uses: ./.github/actions/setup-terraform
43+
44+
- name: Configure Azure credentials
45+
if: steps.check.outputs.skip != 'true'
46+
uses: ./.github/actions/configure-azure-credentials
47+
with:
48+
network_name: ${{ matrix.network_name }}
49+
50+
- name: Renew certificates
51+
if: steps.check.outputs.skip != 'true'
52+
run: ./bin/renew-tls-certificates "${{ matrix.network_name }}"
53+
env:
54+
ARM_USE_OIDC: true

bin/renew-tls-certificates

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env bash
2+
# -----------------------------------------------------------------------------
3+
# Renew TLS certificates for the specified network by running terraform apply
4+
# targeted at the certificate resources in the domain module.
5+
#
6+
# Positional parameters:
7+
# network_name (required) - The network to renew certificates for (e.g. dev, staging, prod)
8+
# -----------------------------------------------------------------------------
9+
set -euo pipefail
10+
11+
network_name="$1"
12+
13+
./bin/terraform-init infra/networks "${network_name}"
14+
./bin/terraform-apply infra/networks "${network_name}" \
15+
-var="network_name=${network_name}" \
16+
-target='module.domain.acme_registration.reg' \
17+
-target='module.domain.acme_certificate.certificate' \
18+
-target='module.domain.azurerm_key_vault_certificate.key_vault_certificate' \
19+
-auto-approve \
20+
-input=false

0 commit comments

Comments
 (0)