Skip to content

IaC for elyclover.com hosting in Azure via Classic CDN

Notifications You must be signed in to change notification settings

kevholmes/elyclover.com-infra

Repository files navigation

elyclover.com-infra

Pulumi project written in Go with multiple stacks for dev, staging, prod support.

This uses an existing Billing Account/DNS Resource Group created outside of IaC at this time.

Purpose

This will replace the GH Pages hosted version of elyclover.com that lives here.

The goal is to have multiple environments for elyclover.com [dev, stg, prod] that are deployed to via GitOps-like principles using GitHub Actions automation workflows.

  • PRs to the web repo are built and deployed to <dev.elyclover.com>
  • Release Candidates (PRs generated by Release-Please automation workflows) are deployed to <stg.elyclover.com>
  • Releases with Semantic Versions generated by merging Release Candidate PRs into main are deployed to <elyclover.com>

Pulumi state is currently managed in Pulumi Cloud.

Stack overview

  1. Create a unique Azure Resource Group per Pulumi Stack, e.g.: dev stack gets a dev Resource Group with globally unique ID.
  2. Create a unique Azure Storage Account and enable static web hosting sourced from the Blob Container $web
  3. Create an Azure CDN Profile with some sane defaults.
  4. Create an Azure CDN Endpoint and point it at our Storage Account's Endpoint, taking care to ensure we set the proper Origin Header to support tie-in to the Storage Account and keep Azure security policies happy.
  5. Get the ref to an external Azure-hosted DNS Zone in another Resource Group created outside of this Pulumi code. Set as Pulumi config options dnsResourceGroup and dnsZoneName.
  6. Create either a CNAME (if nonprod e.g. dev.tld.com) or an apex A record (if prod e.g. tld.com) in this upstream dnsResourceGroup/dnsZoneName in Azure DNS.
  7. Set up a Custom Domain for our Azure CDN Endpoint previously created in step #4. This takes a while (~5-60 min) and will auto-provision a TLS certificate for the record created in step #6. This might fail at times and need another pulumi up to get it over the line. Pulumi does not appear to support an easy "native" retry here as they put the onus on the providers to handle such things with their own logic.

Usage

pulumi stack ls
# pick a stack
pulumi stack select dev
pulumi up

SOPS usage

I've included a makefile with an encrypt + decrypt target, and helper script at scripts/sops.sh to help handle basic use-cases. The SOPS project-wide config is located at .sops.yaml in the project's root.

An Azure Key Vault + key is being used to encrypt/decrypt these secrets at rest. There's an addition to .gitignore to try and ensure decrypted files (ending in .dec) are not comitted if a user hasn't installed the GitGuardian pre-commit hook/check which would also catch an accidental secret being added to a commit/PR.

To decrypt if you are modifying infrastructure

make decrypt
pulumi stack select prod
pulumi up

Basic cert rotation process

cd scripts
./pem-to-pfx.sh yourkey.key yourcrt.crt outpfx.pfx.dec
# this cert fileame is set in Pulumi.prod.yaml ->  elyclover.com-infra:prodPfxCertPath
mv outpfx.pfx.dec ../assets/tls/
cd ..
pulumi stack select prod
pulumi up
make encrypt
git add assets/tls

pre-commit hooks

ggshield

This will auto-detect any potential secrets before they make it into a commit.

The first time I ran this I had to auth like so:

# ggshield is located in a unique folder to each environment installed as a python venv by pre-commit tooling
# GGSHIELD=$(find ~/.cache -name ggshield | grep bin)
$GGSHIELD auth login