recruiter-responder: public /cb ingress for Telegram URL-button callbacks

- Add ingress_factory module (auth=none, HMAC + expiry are the gate);
  ingress_path=["/cb"] only — /api stays internal, /healthz cluster.
  dns_type=proxied. anti_ai_scraping=false.
- Drop setup_tls_secret module — Kyverno ClusterPolicy `sync-tls-secret`
  auto-clones the wildcard cert into every namespace.
- Bump image_tag to 7383b426 (callback endpoints + SMTP STARTTLS
  hostname relax).
- Wire CALLBACK_BASE_URL=https://recruiter-responder.viktorbarzin.me.
- Drop git-crypt-encrypted wildcard cert files into
  stacks/recruiter-responder/secrets/. Allowlist privkey.pem in a new
  .gitleaksignore — git-crypt encrypts at rest but the working-tree
  copy is plaintext, so gitleaks can't tell.

Smoke-tested end-to-end 2026-05-15 23:45:
  synthetic email -> Telegram with / buttons ->  tapped via curl
  -> 'Sent' HTML page -> thread.status=sent, decision row recorded
  with decided_via=telegram_button, outbound message threaded correctly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Viktor Barzin 2026-05-15 23:46:49 +00:00 committed by Viktor Barzin
parent 77010b769a
commit aa6e9b0242
5 changed files with 40 additions and 1 deletions

4
.gitleaksignore Normal file
View file

@ -0,0 +1,4 @@
# git-crypt encrypts these at rest; the working-tree plaintext is local-only.
# gitleaks scans the staged working-tree copy and can't see that they're
# encrypted on disk in git, so allowlist by fingerprint.
stacks/recruiter-responder/secrets/privkey.pem:private-key:1

View file

@ -6,6 +6,11 @@ variable "image_tag" {
variable "postgresql_host" { type = string }
variable "tls_secret_name" {
type = string
sensitive = true
}
locals {
namespace = "recruiter-responder"
image = "forgejo.viktorbarzin.me/viktor/recruiter-responder:${var.image_tag}"
@ -243,6 +248,12 @@ resource "kubernetes_deployment" "recruiter_responder" {
value = "http://claude-agent-service.claude-agent.svc.cluster.local:8080"
}
# Telegram bot (no URL env needed token in secret)
# Public callback base URL for inline-keyboard URL buttons.
# Must match the ingress host below (proxied via Cloudflare).
env {
name = "CALLBACK_BASE_URL"
value = "https://recruiter-responder.viktorbarzin.me"
}
readiness_probe {
http_get {
@ -298,3 +309,27 @@ resource "kubernetes_service" "recruiter_responder" {
}
}
}
# Kyverno ClusterPolicy `sync-tls-secret` auto-clones the wildcard TLS
# secret into every namespace, so we don't need a setup_tls_secret module.
# Public ingress for the /cb/* callback endpoints driven by Telegram URL
# buttons. /api/* and /healthz stay internal they're routed via cluster
# DNS from the OpenClaw plugin / kubelet probes respectively.
#
# auth = "none": the /cb endpoints are gated by HMAC-signed query params
# (sig + exp) generated from WEBHOOK_BEARER_TOKEN. Authentik would force
# a login flow before the GET could fire and break the one-tap flow.
module "ingress" {
source = "../../modules/kubernetes/ingress_factory"
# auth = "none": HMAC + expiry gate the /cb endpoints Authentik would
# force a login dance and break Telegram's one-tap UX. See callback_links.py.
auth = "none"
anti_ai_scraping = false
dns_type = "proxied"
namespace = kubernetes_namespace.recruiter_responder.metadata[0].name
name = "recruiter-responder"
port = 8080
ingress_path = ["/cb"]
tls_secret_name = var.tls_secret_name
}

Binary file not shown.

Binary file not shown.

View file

@ -19,5 +19,5 @@ dependency "external-secrets" {
inputs = {
# Override per-deploy in CI / commit.
image_tag = "0500c3d3"
image_tag = "7383b426"
}