agnes-the-ai-analyst/Caddyfile
Petr Simecek 4799119c81
feat(deploy): keboola-deploy tag-triggered workflow + Caddyfile LE/internal modes + dev_instances TLS support (#52)
* feat(deploy): keboola-deploy tag-triggered workflow + Caddyfile LE/internal modes + dev_instances TLS support

Three coordinated changes that together unblock Keboola's internal Agnes
deployment from the foot-gun where the dev VM tracks `:dev` (= last push
from anyone in the upstream repo).

1. .github/workflows/keboola-deploy.yml — new workflow

   Triggered ONLY on `keboola-deploy-*` git tag pushes (not on every branch
   push like release.yml). Builds an image and publishes two GHCR tags:

     ghcr.io/keboola/agnes-the-ai-analyst:keboola-deploy-<git-tag-suffix>
     ghcr.io/keboola/agnes-the-ai-analyst:keboola-deploy-latest

   The Keboola dev VM pins to `keboola-deploy-latest`; an operator deploys
   by `git tag keboola-deploy-foo && git push origin keboola-deploy-foo`.
   Audit trail lives in git tags (immutable, who-tagged-what-when), no
   PR-cycle needed for each deploy.

   Doesn't touch Vojta/Minas/David workflow — release.yml still builds
   `:dev-<slug>` for every branch push as before.

2. Caddyfile — parametrize TLS directive via $CADDY_TLS env var

   PR #51 hardcoded cert-file mode (`tls /certs/fullchain.pem ...`) for
   Groupon's corporate CA flow. That broke the Let's Encrypt path the
   module previously supported. Now:

     CADDY_TLS unset (default) → cert-file mode (Groupon corp PKI)
     CADDY_TLS="tls user@x.com"  → Let's Encrypt auto-issue
     CADDY_TLS="tls internal"     → Caddy-managed self-signed (lab/dev)

   Single Caddyfile, three regimes, no per-deployment fork. Validated with
   `caddy validate` in all three modes.

3. customer-instance module — dev_instances TLS + auto-set CADDY_TLS

   - variables.tf: dev_instances object schema gains optional tls_mode +
     domain (mirroring prod_instance). Defaults to "none" + "" so existing
     callers without those fields keep current behavior.
   - startup-script.sh.tpl: when tls_mode="caddy" and DOMAIN is set, write
     CADDY_TLS=tls <ACME_EMAIL> (or "tls internal" when ACME_EMAIL empty)
     into /opt/agnes/.env. Caddy then picks it up and the Caddyfile
     substitution flips the cert source.

   For an LE deploy: set tls_mode="caddy", domain="agnes-dev.example.com",
   ensure DNS A-record points at the VM, and acme_email is set on the
   module (or seed_admin_email is, since acme_email defaults to it).

After this lands, tag as infra-v1.6.0 so downstream infra repos can bump
their module ref without needing the upstream change tracking.

* feat(deploy): fetch optional Google OAuth credentials from Secret Manager

Mirrors the existing keboola-storage-token / agnes-<customer>-jwt-secret
pattern: VM SA reads google-oauth-client-{id,secret} secrets at boot
(if they exist + IAM is wired by caller via runtime_secrets) and writes
them into /opt/agnes/.env. Empty / missing / 403 → silent fallback
to "" so password and email auth keep working untouched.

Pairs with downstream change in agnes-infra-keboola which adds the two
secret names to runtime_secrets, granting the Keboola VM SA secretAccessor
on them. Operator pre-creates the SM containers via gcloud secrets create
google-oauth-client-{id,secret} (one-time, out of band) — values stay
in SM forever; rotation = `gcloud secrets versions add`.

This unblocks the Keboola agnes-dev deploy from PR #3 (infra) — without
GOOGLE_CLIENT_{ID,SECRET} in .env, app/auth/providers/google.is_available()
returns False and the Google sign-in button never even appears.
2026-04-25 23:19:00 +02:00

35 lines
1.6 KiB
Caddyfile

{$DOMAIN:localhost} {
# Cert provisioning. Driven by env var CADDY_TLS:
# - unset (default) → cert-file mode for corporate PKI (rotated by
# scripts/grpn/agnes-tls-rotate.sh into /data/state/certs/).
# - "tls <email>" → Let's Encrypt auto-issue, e.g. "tls ops@example.com"
# (used by public-internet deployments like Keboola dev).
# - "tls internal" → Caddy-managed self-signed cert (lab/dev only,
# browser warning on every visit).
#
# The {$VAR:default} substitution lets one Caddyfile serve all three
# regimes without per-deployment forks. Caddyfile parses the substituted
# string as a directive, so the value MUST start with `tls `.
{$CADDY_TLS:tls /certs/fullchain.pem /certs/privkey.pem} {
# Modern TLS only. Caddy default already excludes 1.0/1.1 in
# most builds, but pin explicitly so a future Caddy default
# change can't silently weaken our posture.
protocols tls1.2 tls1.3
}
# HSTS: tell compliant browsers to refuse plain-HTTP for this host
# for a year. Skipping `preload` so we keep an escape hatch (preload
# submission is hard-bound and blocks rollback). Skipping
# `includeSubDomains` because we don't control subdomains.
header Strict-Transport-Security "max-age=31536000"
reverse_proxy app:8000 {
# App's uvicorn runs with --proxy-headers, so stamping these
# ourselves makes OAuth callback URLs and Set-Cookie Secure
# flags resolve to https consistently. X-Forwarded-Host is
# also Caddy's default, but pinning it explicitly insures
# against future default changes.
header_up X-Forwarded-Proto https
header_up X-Forwarded-Host {host}
}
}