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:
parent
b99c8adb26
commit
622d10317b
18 changed files with 0 additions and 1041 deletions
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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/
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
|
@ -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}")
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIBkjCCATegAwIBAgIRAKATfeljsbuJxJe4q9ZzLkEwCgYIKoZIzj0EAwIwEjEQ
|
||||
MA4GA1UEAxMHUm9vdCBDQTAeFw0yNTEyMDExNTEyMDhaFw0zNTExMjkxNTEyMDha
|
||||
MBoxGDAWBgNVBAMTD0ludGVybWVkaWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49
|
||||
AwEHA0IABDq4vXEfZoSH1xTPuAD6lqa8gzNCljAFuju7kV/eRIMKx/WIjrK6ahRL
|
||||
XXdkpdtnjYxR7No1ik57nPdodoodha6jZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNV
|
||||
HRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSz1cHkKVPx6J3UcVGj85mm3/QJtjAf
|
||||
BgNVHSMEGDAWgBRMvRecgr2Q8mcGCptkaNClgHKGsTAKBggqhkjOPQQDAgNJADBG
|
||||
AiEArB5SBybwUuVFZFc4jI6bBgDYaB3o5/6YtkCQ1TFcJ04CIQDgkTt63DgvzkjQ
|
||||
aWVbnGBul2bBELLTyVgyRHJGPxRr1Q==
|
||||
-----END CERTIFICATE-----
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIJrRGpZu7aBTJG7JQyqpRFcWKYCL545cB+0lRBfUDQwWoAoGCCqGSM49
|
||||
AwEHoUQDQgAEOri9cR9mhIfXFM+4APqWpryDM0KWMAW6O7uRX95EgwrH9YiOsrpq
|
||||
FEtdd2Sl22eNjFHs2jWKTnuc92h2ih2Frg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -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-----
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIDlBVdpQgUSlO4pauzYeCCW9DrEbjXEUnXkMGcpLnFQdoAoGCCqGSM49
|
||||
AwEHoUQDQgAE8aDf+o3iAV9KpIpuknuqdIpj5eW9DOCwkQPV1vcBQE1UAa4LCVPJ
|
||||
+RLkOvJC5moPMO7qQ1ylXfCGmKaK/lQzRw==
|
||||
-----END EC PRIVATE KEY-----
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
-----BEGIN EC PRIVATE KEY-----
|
||||
MHcCAQEEIC84fie2f48K2NwPcyGF8RgjGY2mn9mTihe94hhL6FUHoAoGCCqGSM49
|
||||
AwEHoUQDQgAEqcg3Fwk5sN1LyZTgePvce10Ub+IhJiKhTZmxBPQgf2NAd/l0rxKH
|
||||
AAb7Cc+Mh7ki9U0+1KgZXOkaKTb0VJo6Pg==
|
||||
-----END EC PRIVATE KEY-----
|
||||
|
|
@ -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?**
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
variable "project_id" {
|
||||
description = "GCP project ID"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "GCP region"
|
||||
type = string
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue