1
0
Fork 0
mirror of https://github.com/imjasonh/terraform-playground synced 2026-07-06 23:12:22 +00:00

rm all the other stuff

Signed-off-by: Jason Hall <jason@chainguard.dev>
This commit is contained in:
Jason Hall 2026-02-03 13:34:52 -05:00
parent b99c8adb26
commit 622d10317b
18 changed files with 0 additions and 1041 deletions

View file

@ -1,97 +0,0 @@
# Health check for the backend service
resource "google_compute_health_check" "default" {
name = local.name
check_interval_sec = 5
timeout_sec = 5
healthy_threshold = 2
unhealthy_threshold = 2
http_health_check {
port = 80
request_path = "/"
}
}
# Unmanaged instance group for the single VM
resource "google_compute_instance_group" "default" {
name = local.name
zone = local.zone
named_port {
name = "http"
port = 80
}
lifecycle {
ignore_changes = [instances]
}
}
# Manage instance group membership separately
resource "google_compute_instance_group_membership" "default" {
instance_group = google_compute_instance_group.default.id
instance = google_compute_instance_from_template.instance.self_link
zone = local.zone
}
# Backend service
resource "google_compute_backend_service" "default" {
name = local.name
protocol = "HTTP"
port_name = "http"
timeout_sec = 30
load_balancing_scheme = "EXTERNAL_MANAGED"
backend {
group = google_compute_instance_group.default.self_link
balancing_mode = "UTILIZATION"
capacity_scaler = 1.0
}
health_checks = [google_compute_health_check.default.id]
}
# URL map
resource "google_compute_url_map" "default" {
name = local.name
default_service = google_compute_backend_service.default.id
}
# HTTP proxy
resource "google_compute_target_http_proxy" "default" {
name = local.name
url_map = google_compute_url_map.default.id
}
# Forwarding rule (creates external IP)
resource "google_compute_global_forwarding_rule" "default" {
name = local.name
target = google_compute_target_http_proxy.default.id
port_range = "80"
load_balancing_scheme = "EXTERNAL_MANAGED"
}
# Firewall rule to allow health checks from Google Cloud health checkers
resource "google_compute_firewall" "allow_health_checks" {
name = "allow-health-checks"
network = "default"
allow {
protocol = "tcp"
ports = ["80"]
}
# Google Cloud health check IP ranges
source_ranges = [
"35.191.0.0/16",
"130.211.0.0/22",
]
target_tags = ["allow-health-check"]
}
# Output the load balancer IP
output "load_balancer_ip" {
value = google_compute_global_forwarding_rule.default.ip_address
description = "External IP address of the load balancer"
}

View file

@ -1,66 +0,0 @@
locals {
name = "container-vm"
project_id = "jason-chainguard"
region = "us-east4"
zone = "${local.region}-a"
}
provider "google" {
project = local.project_id
}
resource "google_service_account" "sa" {
account_id = local.name
}
resource "google_artifact_registry_repository" "container_images" {
location = local.region
repository_id = local.name
format = "DOCKER"
}
resource "google_artifact_registry_repository_iam_member" "sa_reader" {
location = google_artifact_registry_repository.container_images.location
repository = google_artifact_registry_repository.container_images.name
role = "roles/artifactregistry.reader"
member = "serviceAccount:${google_service_account.sa.email}"
}
resource "google_project_iam_member" "observability_roles" {
for_each = toset([
"roles/logging.logWriter",
"roles/monitoring.metricWriter",
"roles/cloudtrace.agent",
"roles/cloudprofiler.agent",
])
project = local.project_id
role = each.key
member = "serviceAccount:${google_service_account.sa.email}"
}
module "container-vm" {
source = "./container-vm"
project_id = local.project_id
region = local.region
containers = {
"nginx" = {
image = "${local.region}-docker.pkg.dev/${local.project_id}/${local.name}/nginx@sha256:553f64aecdc31b5bf944521731cd70e35da4faed96b2b7548a3d8e2598c52a42"
ports = ["80:80"]
}
}
service_account_email = google_service_account.sa.email
network = "default"
subnetwork = "default"
}
resource "google_compute_instance_from_template" "instance" {
name = local.name
zone = "${local.region}-a"
tags = ["allow-health-check"]
source_instance_template = module.container-vm.instance_template_self_link
}

View file

@ -1,14 +0,0 @@
FROM smallstep/step-ca:latest
USER root
# Create directories for the secret mounts
RUN mkdir -p /home/step/secrets /home/step/config
RUN chown -R step:step /home/step
USER step
# We will mount the secrets to these locations via Cloud Run config
ENV STEPPATH=/home/step
# The entrypoint command
CMD ["/usr/local/bin/step-ca", "/home/step/config/ca.json"]

View file

@ -1,103 +0,0 @@
# step-ca on Cloud Run with Cloud SQL
Deploy step-ca PKI to Google Cloud Run, backed by Cloud SQL for state management.
## Prerequisites
- `gcloud` CLI authenticated
- `terraform` installed
- `step` CLI installed (`brew install step`)
- GCP project with billing enabled
## Deployment Steps
### 1. Bootstrap the PKI
Generate CA certificates and SSH keys, then upload to Secret Manager:
```bash
cd step-ca/
./bootstrap.sh
```
Save the root CA fingerprint output - you'll need it for client setup.
### 2. Deploy Infrastructure
```bash
terraform init
terraform apply
```
This creates:
- Cloud SQL PostgreSQL instance
- VPC network with private service connection
- Secret Manager secrets (populated by bootstrap script)
- Cloud Run service with step-ca
- Artifact Registry repository for the container image
### 3. Update Configuration
After deployment, update the `step-ca-config` secret with the actual Cloud Run URL and database password:
```bash
# Get outputs
CLOUD_RUN_URL=$(terraform output -raw step_ca_url)
DB_PASSWORD=$(terraform output -raw db_password)
# Create updated ca.json
cat > ca.json <<EOF
{
"address": ":9000",
"dnsNames": ["${CLOUD_RUN_URL#https://}"],
"db": {
"type": "postgresql",
"dataSource": "host=/cloudsql/$(terraform output -raw cloud_sql_connection_name) user=step password=${DB_PASSWORD} dbname=step_ca sslmode=disable"
},
"crt": "/home/step/secrets/intermediate_ca.crt",
"key": "/home/step/secrets/intermediate_ca.key",
"root": "/home/step/secrets/root_ca.crt",
"password": "",
"authority": <copy from existing secret>,
"tls": <copy from existing secret>,
"ssh": {
"hostKey": "/home/step/secrets/ssh_host_ca_key",
"userKey": "/home/step/secrets/ssh_user_ca_key"
}
}
EOF
gcloud secrets versions add step-ca-config --data-file=ca.json
```
Force a new Cloud Run revision to pick up the updated config:
```bash
gcloud run services update step-ca --region us-east4
```
## Client Setup
On client machines that need to use the CA:
```bash
# Bootstrap with your CA
step ca bootstrap --ca-url https://YOUR-SERVICE.run.app --fingerprint <FINGERPRINT>
# Get an SSH certificate
step ssh login user@example.com
```
## Architecture
- **Compute**: Cloud Run (serverless, auto-scaling)
- **Database**: Cloud SQL PostgreSQL (for certificate state/revocation)
- **Secrets**: Secret Manager (for CA keys and configuration)
- **Networking**: VPC with Cloud SQL private IP, VPC connector for Cloud Run
## Notes
- Cloud Run handles TLS termination, step-ca runs in insecure mode internally
- All CA keys are stored in Secret Manager, mounted as files at runtime
- Database password is randomly generated and stored in Terraform state
- The service is publicly accessible but requires valid provisioner credentials

View file

@ -1,101 +0,0 @@
#!/bin/bash
set -euo pipefail
# Bootstrap script for step-ca PKI setup
# This script generates the CA certificates and uploads them to GCP Secret Manager
PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project)}"
REGION="${REGION:-us-east4}"
CA_NAME="${CA_NAME:-MyGCPCA}"
DNS_NAME="${DNS_NAME:-ca.example.com}"
echo "==> Bootstrapping step-ca PKI for project: $PROJECT_ID"
# Create a temporary directory for PKI files
TEMP_DIR=$(mktemp -d)
trap "rm -rf $TEMP_DIR" EXIT
export STEPPATH="$TEMP_DIR"
# Generate random passwords
openssl rand -base64 32 > "$TEMP_DIR/password"
openssl rand -base64 32 > "$TEMP_DIR/provisioner-password"
echo "==> Initializing PKI with step ca init"
step ca init \
--name="$CA_NAME" \
--dns="$DNS_NAME" \
--address=":9000" \
--provisioner="admin" \
--password-file="$TEMP_DIR/password" \
--provisioner-password-file="$TEMP_DIR/provisioner-password" \
--ssh
echo "==> Uploading secrets to GCP Secret Manager"
# Upload root CA certificate
gcloud secrets versions add step-root-ca \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/certs/root_ca.crt" || \
gcloud secrets create step-root-ca \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/certs/root_ca.crt"
# Upload intermediate CA certificate
gcloud secrets versions add step-intermediate-crt \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/certs/intermediate_ca.crt" || \
gcloud secrets create step-intermediate-crt \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/certs/intermediate_ca.crt"
# Upload intermediate CA key
gcloud secrets versions add step-intermediate-key \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/secrets/intermediate_ca_key" || \
gcloud secrets create step-intermediate-key \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/secrets/intermediate_ca_key"
# Upload SSH host CA key
gcloud secrets versions add step-ssh-host-ca-key \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/secrets/ssh_host_ca_key" || \
gcloud secrets create step-ssh-host-ca-key \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/secrets/ssh_host_ca_key"
# Upload SSH user CA key
gcloud secrets versions add step-ssh-user-ca-key \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/secrets/ssh_user_ca_key" || \
gcloud secrets create step-ssh-user-ca-key \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/secrets/ssh_user_ca_key"
# Upload ca.json (will be updated after terraform apply)
gcloud secrets versions add step-ca-config \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/config/ca.json" || \
gcloud secrets create step-ca-config \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/config/ca.json"
# Upload intermediate CA password
gcloud secrets versions add step-intermediate-password \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/password" || \
gcloud secrets create step-intermediate-password \
--project="$PROJECT_ID" \
--data-file="$TEMP_DIR/password"
echo ""
echo "==> Bootstrap complete!"
echo ""
echo "Root CA fingerprint (save this for clients):"
step certificate fingerprint "$TEMP_DIR/certs/root_ca.crt"
echo ""
echo "Next steps:"
echo " 1. Run 'terraform apply' to deploy the infrastructure"
echo " 2. After deployment, update the ca.json secret with the actual Cloud Run URL and DB password"
echo " 3. Use 'step ca bootstrap --ca-url https://YOUR-SERVICE.run.app --fingerprint FINGERPRINT' on clients"

View file

@ -1,208 +0,0 @@
# Service account for Cloud Run
resource "google_service_account" "step_ca" {
account_id = "step-ca"
display_name = "step-ca Cloud Run Service Account"
}
# Grant Cloud SQL client permissions
resource "google_project_iam_member" "step_ca_sql_client" {
project = var.project_id
role = "roles/cloudsql.client"
member = "serviceAccount:${google_service_account.step_ca.email}"
}
# Cloud Run service
resource "google_cloud_run_v2_service" "step_ca" {
name = "step-ca"
location = var.region
template {
service_account = google_service_account.step_ca.email
vpc_access {
connector = google_vpc_access_connector.connector.id
egress = "PRIVATE_RANGES_ONLY"
}
containers {
image = "smallstep/step-ca:latest"
ports {
container_port = 9000
}
env {
name = "STEPDEBUG"
value = "1"
}
env {
name = "STEPPATH"
value = "/home/step"
}
env {
name = "STEP_TLS_INSECURE"
value = "true"
}
env {
name = "CONFIG_HASH"
value = filesha256("${path.module}/secrets/step-ca-config")
}
# Mount secrets as volumes
volume_mounts {
name = "root-ca"
mount_path = "/mnt/root-ca"
}
volume_mounts {
name = "intermediate-crt"
mount_path = "/mnt/intermediate-crt"
}
volume_mounts {
name = "intermediate-key"
mount_path = "/mnt/intermediate-key"
}
volume_mounts {
name = "ssh-host-key"
mount_path = "/mnt/ssh-host-key"
}
volume_mounts {
name = "ssh-user-key"
mount_path = "/mnt/ssh-user-key"
}
volume_mounts {
name = "password"
mount_path = "/mnt/password"
}
volume_mounts {
name = "ca-config"
mount_path = "/home/step/config"
}
command = ["/bin/sh", "-c"]
args = ["mkdir -p /home/step/secrets && cp /mnt/*/* /home/step/secrets/ && tr -d '\\n' < /home/step/secrets/password > /tmp/pass && mv /tmp/pass /home/step/secrets/password && echo 'Files copied, password stripped' && ls -la /home/step/secrets/ && echo 'Password length:' && wc -c /home/step/secrets/password && /usr/local/bin/step-ca /home/step/config/ca.json"]
}
volumes {
name = "root-ca"
secret {
secret = google_secret_manager_secret.secret["step-root-ca"].secret_id
default_mode = 0444
items {
version = "latest"
path = "root_ca.crt"
}
}
}
volumes {
name = "intermediate-crt"
secret {
secret = google_secret_manager_secret.secret["step-intermediate-crt"].secret_id
default_mode = 0444
items {
version = "latest"
path = "intermediate_ca.crt"
}
}
}
volumes {
name = "intermediate-key"
secret {
secret = google_secret_manager_secret.secret["step-intermediate-key"].secret_id
default_mode = 0400
items {
version = "latest"
path = "intermediate_ca_key"
}
}
}
volumes {
name = "ssh-host-key"
secret {
secret = google_secret_manager_secret.secret["step-ssh-host-ca-key"].secret_id
default_mode = 0400
items {
version = "latest"
path = "ssh_host_ca_key"
}
}
}
volumes {
name = "ssh-user-key"
secret {
secret = google_secret_manager_secret.secret["step-ssh-user-ca-key"].secret_id
default_mode = 0400
items {
version = "latest"
path = "ssh_user_ca_key"
}
}
}
volumes {
name = "password"
secret {
secret = google_secret_manager_secret.secret["step-intermediate-password"].secret_id
default_mode = 0400
items {
version = "latest"
path = "password"
}
}
}
volumes {
name = "ca-config"
secret {
secret = google_secret_manager_secret.secret["step-ca-config"].secret_id
default_mode = 0444
items {
version = "latest"
path = "ca.json"
}
}
}
}
depends_on = [
google_project_iam_member.step_ca_sql_client,
google_secret_manager_secret_iam_member.secret_access,
]
}
# Make the service publicly accessible
resource "google_cloud_run_v2_service_iam_member" "public_access" {
name = google_cloud_run_v2_service.step_ca.name
location = google_cloud_run_v2_service.step_ca.location
role = "roles/run.invoker"
member = "allUsers"
}
output "step_ca_url" {
value = google_cloud_run_v2_service.step_ca.uri
description = "The URL of the step-ca Cloud Run service"
}
output "cloud_sql_connection_name" {
value = google_sql_database_instance.step_ca.connection_name
description = "Cloud SQL connection name"
}
output "db_password" {
value = random_password.db_password.result
description = "Database password for step user"
sensitive = true
}

View file

@ -1,95 +0,0 @@
#!/bin/bash
set -euo pipefail
cd /Users/jason/git/terraform-playground/step-ca
mkdir -p secrets
TEMP_DIR=$(mktemp -d)
export STEPPATH="$TEMP_DIR"
# Generate root and intermediate CAs manually with no password
step certificate create "Root CA" \
"$TEMP_DIR/root_ca.crt" \
"$TEMP_DIR/root_ca_key" \
--profile root-ca \
--no-password --insecure
step certificate create "Intermediate CA" \
"$TEMP_DIR/intermediate_ca.crt" \
"$TEMP_DIR/intermediate_ca_key" \
--profile intermediate-ca \
--ca "$TEMP_DIR/root_ca.crt" \
--ca-key "$TEMP_DIR/root_ca_key" \
--no-password --insecure
# Generate SSH CA keys
step crypto keypair "$TEMP_DIR/ssh_user_ca_key.pub" "$TEMP_DIR/ssh_user_ca_key" \
--kty EC --curve P-256 --no-password --insecure
step crypto keypair "$TEMP_DIR/ssh_host_ca_key.pub" "$TEMP_DIR/ssh_host_ca_key" \
--kty EC --curve P-256 --no-password --insecure
# Generate a JWK provisioner
step crypto jwk create "$TEMP_DIR/provisioner.pub" "$TEMP_DIR/provisioner.key" \
--kty EC --curve P-256 --no-password --insecure
# Read the JWK
PROVISIONER_JWK=$(cat "$TEMP_DIR/provisioner.key")
# Copy all files to secrets directory
cp "$TEMP_DIR/root_ca.crt" secrets/step-root-ca
cp "$TEMP_DIR/intermediate_ca.crt" secrets/step-intermediate-crt
cp "$TEMP_DIR/intermediate_ca_key" secrets/step-intermediate-key
cp "$TEMP_DIR/ssh_host_ca_key" secrets/step-ssh-host-ca-key
cp "$TEMP_DIR/ssh_user_ca_key" secrets/step-ssh-user-ca-key
echo -n "" > secrets/step-intermediate-password
# Create ca.json
cat > secrets/step-ca-config <<EOFCONFIG
{
"root": "/home/step/secrets/root_ca.crt",
"crt": "/home/step/secrets/intermediate_ca.crt",
"key": "/home/step/secrets/intermediate_ca_key",
"password": "",
"address": ":9000",
"dnsNames": ["ca.example.com"],
"ssh": {
"hostKey": "/home/step/secrets/ssh_host_ca_key",
"userKey": "/home/step/secrets/ssh_user_ca_key"
},
"logger": {
"format": "json"
},
"db": {
"type": "badgerv2",
"dataSource": "/home/step/db"
},
"authority": {
"provisioners": [
{
"type": "JWK",
"name": "admin",
"key": $PROVISIONER_JWK,
"claims": {
"enableSSHCA": true
}
}
]
},
"tls": {
"cipherSuites": [
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
],
"minVersion": 1.2,
"maxVersion": 1.3,
"renegotiation": false
}
}
EOFCONFIG
echo "Root CA fingerprint (save this):"
step certificate fingerprint "$TEMP_DIR/root_ca.crt"
echo ""
echo "Files saved to secrets/"
ls -lh secrets/

View file

@ -1,78 +0,0 @@
terraform {
required_providers {
google = { source = "hashicorp/google" }
}
}
provider "google" {
project = var.project_id
region = var.region
}
# Cloud SQL instance
resource "google_sql_database_instance" "step_ca" {
name = "step-ca-db"
database_version = "POSTGRES_15"
region = var.region
settings {
tier = "db-f1-micro"
ip_configuration {
ipv4_enabled = false
# Enable Cloud Run to connect via private IP
private_network = google_compute_network.step_ca.id
}
backup_configuration {
enabled = true
}
}
deletion_protection = false
}
resource "google_sql_database" "step_ca" {
name = "step_ca"
instance = google_sql_database_instance.step_ca.name
}
resource "google_sql_user" "step" {
name = "step"
instance = google_sql_database_instance.step_ca.name
password = random_password.db_password.result
}
resource "random_password" "db_password" {
length = 32
special = false
}
# VPC for Cloud SQL private IP
resource "google_compute_network" "step_ca" {
name = "step-ca-network"
}
resource "google_compute_global_address" "private_ip_address" {
name = "step-ca-private-ip"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.step_ca.id
}
resource "google_service_networking_connection" "private_vpc_connection" {
network = google_compute_network.step_ca.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.private_ip_address.name]
}
# VPC connector for Cloud Run
resource "google_vpc_access_connector" "connector" {
name = "step-ca-connector"
region = var.region
network = google_compute_network.step_ca.name
ip_cidr_range = "10.8.0.0/28"
depends_on = [google_service_networking_connection.private_vpc_connection]
}

View file

@ -1,33 +0,0 @@
# Secret Manager secrets for step-ca
# These will be populated by the bootstrap script
resource "google_secret_manager_secret" "secret" {
for_each = toset([
"step-root-ca",
"step-intermediate-crt",
"step-intermediate-key",
"step-ssh-host-ca-key",
"step-ssh-user-ca-key",
"step-ca-config",
"step-intermediate-password",
])
secret_id = each.key
replication {
auto {}
}
}
# Grant Cloud Run service account access to secrets
resource "google_secret_manager_secret_iam_member" "secret_access" {
for_each = google_secret_manager_secret.secret
secret_id = each.value.id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.step_ca.email}"
}
resource "google_secret_manager_secret_version" "secret_version" {
for_each = google_secret_manager_secret.secret
secret = each.value.id
secret_data = file("${path.module}/secrets/${each.key}")
}

View file

@ -1,39 +0,0 @@
{
"root": "/home/step/secrets/root_ca.crt",
"crt": "/home/step/secrets/intermediate_ca.crt",
"key": "/home/step/secrets/intermediate_ca_key",
"password": "",
"ssh": {
"hostKey": "/home/step/secrets/ssh_host_ca_key",
"userKey": "/home/step/secrets/ssh_user_ca_key"
},
"logger": {
"format": "json"
},
"db": {
"type": "badgerv2",
"dataSource": "/home/step/db"
},
"authority": {
"provisioners": [
{
"type": "JWK",
"name": "admin",
"key": {
"use": "sig",
"kty": "EC",
"kid": "zwEzziFr6rJGA3vaY1VJFiwXQJkqn32W0ITH1RQvYq8",
"crv": "P-256",
"alg": "ES256",
"x": "mFjtboERGK_noWLXhzg39SgcDbnYuly49Pu4C09ordY",
"y": "chyC1qvlTytNaT1pMKA92rVaMpKlA0PT6Qeprpiopd0",
"d": "wp9Xa1QwykS14NR_Mb5KBDASTnIbreZbxVQShpCwxFw"
},
"claims": {
"enableSSHCA": true
}
}
]
},
"insecureAddress": ":9000"
}

View file

@ -1,11 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBkjCCATegAwIBAgIRAKATfeljsbuJxJe4q9ZzLkEwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEAxMHUm9vdCBDQTAeFw0yNTEyMDExNTEyMDhaFw0zNTExMjkxNTEyMDha
MBoxGDAWBgNVBAMTD0ludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49
AwEHA0IABDq4vXEfZoSH1xTPuAD6lqa8gzNCljAFuju7kV/eRIMKx/WIjrK6ahRL
XXdkpdtnjYxR7No1ik57nPdodoodha6jZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNV
HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSz1cHkKVPx6J3UcVGj85mm3/QJtjAf
BgNVHSMEGDAWgBRMvRecgr2Q8mcGCptkaNClgHKGsTAKBggqhkjOPQQDAgNJADBG
AiEArB5SBybwUuVFZFc4jI6bBgDYaB3o5/6YtkCQ1TFcJ04CIQDgkTt63DgvzkjQ
aWVbnGBul2bBELLTyVgyRHJGPxRr1Q==
-----END CERTIFICATE-----

View file

@ -1,5 +0,0 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJrRGpZu7aBTJG7JQyqpRFcWKYCL545cB+0lRBfUDQwWoAoGCCqGSM49
AwEHoUQDQgAEOri9cR9mhIfXFM+4APqWpryDM0KWMAW6O7uRX95EgwrH9YiOsrpq
FEtdd2Sl22eNjFHs2jWKTnuc92h2ih2Frg==
-----END EC PRIVATE KEY-----

View file

@ -1 +0,0 @@

View file

@ -1,10 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIBZzCCAQ2gAwIBAgIQRGIn0/0TMi83phVNs5uy+zAKBggqhkjOPQQDAjASMRAw
DgYDVQQDEwdSb290IENBMB4XDTI1MTIwMTE1MTIwOFoXDTM1MTEyOTE1MTIwOFow
EjEQMA4GA1UEAxMHUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEzW
YoOejIcHa3hqw0anqD+F89N8CLodngeZmHnLybYK6A1B9JY/ThogZY9zEP4+520z
bR49ZrwN/sXY33mjCMajRTBDMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAG
AQH/AgEBMB0GA1UdDgQWBBRMvRecgr2Q8mcGCptkaNClgHKGsTAKBggqhkjOPQQD
AgNIADBFAiBJap2TiFTi8J2t+JPms9jqbKYQUV7rsmJYLXUV7Z0ZPQIhAKa3FUxj
CNKwWI3GvLrrc0exZzUo25wUYYh8XuXOJiY7
-----END CERTIFICATE-----

View file

@ -1,5 +0,0 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIDlBVdpQgUSlO4pauzYeCCW9DrEbjXEUnXkMGcpLnFQdoAoGCCqGSM49
AwEHoUQDQgAE8aDf+o3iAV9KpIpuknuqdIpj5eW9DOCwkQPV1vcBQE1UAa4LCVPJ
+RLkOvJC5moPMO7qQ1ylXfCGmKaK/lQzRw==
-----END EC PRIVATE KEY-----

View file

@ -1,5 +0,0 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIC84fie2f48K2NwPcyGF8RgjGY2mn9mTihe94hhL6FUHoAoGCCqGSM49
AwEHoUQDQgAEqcg3Fwk5sN1LyZTgePvce10Ub+IhJiKhTZmxBPQgf2NAd/l0rxKH
AAb7Cc+Mh7ki9U0+1KgZXOkaKTb0VJo6Pg==
-----END EC PRIVATE KEY-----

View file

@ -1,161 +0,0 @@
Deploying a PKI (Public Key Infrastructure) like `step-ca` on serverless platforms (Cloud Run) or managed K8s (GKE) is notoriously difficult because PKI is inherently **stateful** (it needs to keep track of keys, tokens, and certs), while those platforms prefer **stateless** workloads.
The easiest, most stable path to "just get it working" for SSH certificates is **Cloud Run backed by Cloud SQL**, with your keys stored in **Secret Manager**. This avoids the complexity of managing GKE persistent volumes and is cheaper/easier to maintain.
Here is a streamlined guide to deploying `step-ca` securely on GCP.
### The Architecture
* **Compute:** Cloud Run (Serverless, handles HTTPS automatically).
* **State/Database:** Cloud SQL (PostgreSQL). `step-ca` needs a DB to store used tokens and certificate revocation lists.
* **Secrets:** GCP Secret Manager. Stores your Root CA key, Intermediate CA Key, and provisioner passwords.
-----
### Phase 1: Local Bootstrap
Do not try to run `step ca init` inside Cloud Run. Run it locally to generate the keys and config, then upload them.
1. **Install `step` CLI locally** and initialize your PKI:
```bash
# Initialize PKI (choose 'stand-alone' -> 'PostgreSQL' when asked for DB)
step ca init --name="MyGCPCA" --dns="ca.example.com" --address=":9000" --provisioner="admin"
```
*Note: When asked for the DB address, put a placeholder like `postgresql://step:password@localhost:5432/step_db`. We will override this later.*
2. **Enable SSH Certificates:**
```bash
step ca provisioner add ssh-user --type=ssh --user
step ca provisioner add ssh-host --type=ssh --host
```
3. **Locate your files:** You should now have a `$(step path)/secrets` and `$(step path)/config` directory containing:
* `root_ca.crt`
* `intermediate_ca.crt`
* `intermediate_ca.key`
* `password` (files containing your key passwords)
* `ca.json` (the config file)
### Phase 2: Upload Secrets to GCP
Go to **GCP Secret Manager** and create secrets for the sensitive files you just generated. This allows Cloud Run to mount them as files.
Create the following secrets (names are suggestions):
* `step-ca-config`: Paste the content of your `ca.json`.
* `step-root-ca`: Paste content of `root_ca.crt`.
* `step-intermediate-crt`: Paste content of `intermediate_ca.crt`.
* `step-intermediate-key`: Paste content of `intermediate_ca.key`.
* `step-password`: Paste the password used for your intermediate key.
### Phase 3: Prepare the Database
1. Create a **Cloud SQL (PostgreSQL)** instance.
2. Create a database named `step_ca`.
3. Create a user `step` with a password.
4. **Crucial:** Note the "Connection Name" of the instance (e.g., `project-id:us-central1:instance-name`).
### Phase 4: The Dockerfile
You need a custom Docker wrapper to point `step-ca` to the mounted secrets.
Create a `Dockerfile`:
```dockerfile
FROM smallstep/step-ca:latest
USER root
# Create directories for the secret mounts
RUN mkdir -p /home/step/secrets /home/step/config
RUN chown -R step:step /home/step
USER step
# We will mount the secrets to these locations via Cloud Run config
ENV STEPPATH=/home/step
# The entrypoint command
CMD ["/usr/local/bin/step-ca", "/home/step/config/ca.json"]
```
Build and push this image to Google Artifact Registry:
```bash
gcloud builds submit --tag gcr.io/YOUR_PROJECT/step-ca-cloudrun
```
### Phase 5: Deploy to Cloud Run
This is where the magic happens. You will deploy the image and "mount" the secrets as files.
Run this command (replace placeholders):
```bash
gcloud run deploy step-ca \
--image gcr.io/YOUR_PROJECT/step-ca-cloudrun \
--region us-central1 \
--allow-unauthenticated \
--port 9000 \
--add-cloudsql-instances "PROJECT:REGION:INSTANCE" \
--set-env-vars "STEP_TLS_INSECURE=true" \
--set-secrets "/home/step/config/ca.json=step-ca-config:latest" \
--set-secrets "/home/step/secrets/root_ca.crt=step-root-ca:latest" \
--set-secrets "/home/step/secrets/intermediate_ca.crt=step-intermediate-crt:latest" \
--set-secrets "/home/step/secrets/intermediate_ca.key=step-intermediate-key:latest" \
--set-secrets "/home/step/secrets/password=step-password:latest"
```
**Key Configuration Details:**
* **`STEP_TLS_INSECURE=true`**: Cloud Run handles the TLS/SSL termination at the load balancer layer (the `https://...run.app` URL). The container itself receives HTTP. `step-ca` needs to know this is okay.
* **`--add-cloudsql-instances`**: This exposes the Cloud SQL socket to the container.
### Phase 6: Final Configuration Fix
Your `ca.json` (inside the secret) still points to localhost for the DB and has local paths for keys. You need to edit the `step-ca-config` secret in Secret Manager to match the Cloud Run environment.
Update the `ca.json` in Secret Manager to look like this:
```json
{
"address": ":9000",
"dnsNames": ["your-service-url.a.run.app"],
"db": {
"type": "postgresql",
"dataSource": "host=/cloudsql/PROJECT:REGION:INSTANCE user=step password=DB_PASSWORD dbname=step_ca sslmode=disable"
},
"crt": "/home/step/secrets/intermediate_ca.crt",
"key": "/home/step/secrets/intermediate_ca.key",
"root": "/home/step/secrets/root_ca.crt",
"password": "/home/step/secrets/password",
"authority": { ... }
}
```
*Redeploy the Cloud Run revision after updating the secret so it picks up the changes.*
### How to generate your SSH Certs now
1. **Bootstrap your local client** with the new remote CA:
```bash
step ca bootstrap --ca-url https://your-service.a.run.app --fingerprint <ROOT_CA_FINGERPRINT>
```
2. **Login/Generate:**
```bash
# Get a short-lived SSH cert
step ssh login user@example.com --provisioner ssh-user
```
### Summary of why this failed before
* **Filesystem:** You likely tried to generate keys *inside* the container. On Cloud Run/GKE, those vanish on restart.
* **TLS:** `step-ca` tries to do its own TLS by default. On Cloud Run, you must offload TLS to Google and run the container in "insecure" mode (checking the `STEP_TLS_INSECURE` env var).
**Would you like me to elaborate on how to configure the client-side `ssh` config to automatically use these certificates?**

View file

@ -1,9 +0,0 @@
variable "project_id" {
description = "GCP project ID"
type = string
}
variable "region" {
description = "GCP region"
type = string
}