From aa6e9b024269bfd674e85536e20ca40c76fda6ea Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Fri, 15 May 2026 23:46:49 +0000 Subject: [PATCH] recruiter-responder: public /cb ingress for Telegram URL-button callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .gitleaksignore | 4 ++ stacks/recruiter-responder/main.tf | 35 ++++++++++++++++++ .../recruiter-responder/secrets/fullchain.pem | Bin 0 -> 2902 bytes .../recruiter-responder/secrets/privkey.pem | Bin 0 -> 263 bytes stacks/recruiter-responder/terragrunt.hcl | 2 +- 5 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 .gitleaksignore create mode 100644 stacks/recruiter-responder/secrets/fullchain.pem create mode 100644 stacks/recruiter-responder/secrets/privkey.pem diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..dfe626cd --- /dev/null +++ b/.gitleaksignore @@ -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 diff --git a/stacks/recruiter-responder/main.tf b/stacks/recruiter-responder/main.tf index cbc74a7f..6958eb50 100644 --- a/stacks/recruiter-responder/main.tf +++ b/stacks/recruiter-responder/main.tf @@ -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 +} diff --git a/stacks/recruiter-responder/secrets/fullchain.pem b/stacks/recruiter-responder/secrets/fullchain.pem new file mode 100644 index 0000000000000000000000000000000000000000..b0dcf47082eae45dce647632843af843b8845a8e GIT binary patch literal 2902 zcmV-c3#s$~M@dveQdv+`0G%D1JtO}sV}>^}O<6lBSm^f)T2gJV{E-XCV7x#!%CE{a zH<<}ffB<$mfV}j=h?HANHhXkg8KzF@R0w0WIfPLt9{DQxNa4Mt)OF^I6lM^DN#AJ< zb}qc&CUv*JF)={QvO1cpZ~wrC96SQ8lh8HMC#KCR8d-b#)6ABfiVa)UM4JiK(J+Av04aUB*{d8oFUc*PoCI_0DyM zW@xtq^)LKN)nx+ri}6IBJx+-c_Pto7P)T16RJ0rG>@zoi4WZ&{D9}PEYP6zQ`C_(n zkh2Vo0@%1G-3Ws`n*3U!C)l@M;3btv$b|gV!fSuwE2K&&~;0q zmEDq~OyIpavEw4@MNg{&@GR>mj65W8@Dwzo9VZ&aGXmEcw%{}9fDNshO}w!-Ib*1Y zQP2r~GCkpw#B=BPrp9|aEnczrHDw^|Ab~$x3RI%*9RPUII*rf5qzC%L0^D*E^mhSa z&yugG7x_Y6Qfia-T5PCk#DoSXIp1!^ zWfR36hflh@mINSh;|yu-x#%erMI7rbQWe)sN8UrMJSLj{khmA%9+&Vgn<{YUydG3s zhZY;bl5F4ZZ+G}BRCl`k;qbMbk>6%VDI9bqCp;*6hTv}SDdF`lHU!+%L&L4+c=0x0 z0X@_I&T{?O<;gjq9x*qgF|rhR7Zzyy?2e8VYR-+D$M3G<_VW^QIOAJKwxQor24f*$ z%)N_IB;x~wG|1fwGDj^rA|4oZUzjEXSw%HE#?A;PgWn$i~wn(?Ff)@`{9ME2|5KRj8Nm$-T>7SB_Q%<$ay z%{}Md;zbvOFU7r8=ty`2j$4uS>wtzbX-W?ji^zYnrwn{qbXBQdgt*lDN$rIIIm%pw zVICtPuZ8z{Hv9e*dO%P-9+>T3h;uqt6^i$|-J@@(a8i9+OWk#q+Vi5gx#rSGv4*%q#mv3A{C$ zTv^k26O>EZn;f0vlEW;mnO391Or|cTa5jc>sX2vID)4yXiN5y=f!(9h!@VixU-2`! z4k;vR-|!{eFlZ^XBfDiWU=nOg@aLKsgG0D>PH9P1PGBe0-%iErRcE|@jG=)B(H`o! zqlCRxg(y1^(HD~6HrxA5y-y}$&|4pBf(5DOk~7>lOx)s}k_h1u#ZSiF4MY#H zi3Tg|uI0$kv}uq`Y`pJFXX?!WP%*C0cM50M%$$8j+(0*2#;={-nSMOQPBQl@SLHvX zn13Vzp?|j^Bj0;AYp*t>LAFgBmr48#3pf<8er>cHF#V1A_W-7>!E{sQ7$$X7e~>VJ zeI!ToR)#OBqFQvK=O_Z6M)8NUF&J|j5(g?d7pIYPB)gb@h@S(=|`5L|+c0a%Kw;G*kk;raXycN@P$U5r76w!DLI3Y8*MlY~)P>!gEQwue+UO#v znRi5_>zcvE!k!Fj*}i6*xmX{(QwaygWc4_7y%%f0g*N)+6k!6U}IJNrVOrlJhFI4ZIO0ESnT?NbvP22j+1 zvTLEi+NnjKxw9Du@1ZM|f7KZb29J>9ecH>~0rS1NE4-g4OcoUVYac(oMP!RS-LAglv<(SGr_)Feycf~n9k6aU_mVi_m9s$Y73<9G)J*Z3dID~ zUoDGLksQTM99v*ErVrL7{wX```PAqeYLd|$x_De20|6QZH&)_Pyx9TENp4$`6F#Qk zn<{utUamKA>g}k@r7Rtgi&HBTuPkPGO3gAyO?Awh3^OkDQ}9_x+d`m3j6Hp`#bA&oCunpsXv4}he}2@NtNZtNdd_@?X| zmG(et7OQ)DP;@`HnbSor4nu{yp-G4hpkh z1oawU1`yx~cr^g1qxc02@r=ahK~ z-1*@`={gsi5qf9B&|rt+#l)(P3*aTfYv{DKB4XCwIb}n1Jmn(5ko;({1 z0-E-y76zAmwr;rfXeq#eS3~kAuJKTVB(r(^Kq-FDfaWN+&cf7X}8DiA|kk!7G_#m9KX~3IGF1kNGq0? AumAu6 literal 0 HcmV?d00001 diff --git a/stacks/recruiter-responder/secrets/privkey.pem b/stacks/recruiter-responder/secrets/privkey.pem new file mode 100644 index 0000000000000000000000000000000000000000..444b348e976e4f25685d18a5a1cde1e54e6eb01b GIT binary patch literal 263 zcmV+i0r>s^M@dveQdv+`0PXXi-SqgNpETk{-IelvL$q+K_TS@w1R&A=O$(TtZdhkU z!W}hXc?DHdwU+K^-8;$27-*~jIXYe@z>Ov;C6ctuI?Bx@d=%#r(reK`V9oeHcGdqr znGc}%I6rAwQIZ-)+H*F;h(c`vR}M|rQ<#SlYCkrtH=}}+DDqJog8&>_0lHE#X_)T! zQWr^8OH+S^s`#w0Z}E0fXram%FXJjj?n28*AAm5W>((@V4Q_A;U47tv-|V=>vU;#M zHQif^mBOu=tmblLdf-Qp0u5PRN0}Csl;6~)o+=U?8e;qgmRW*I)u3}*fK@;q4tZZO NfdIkWAC>OOCue??dx`)6 literal 0 HcmV?d00001 diff --git a/stacks/recruiter-responder/terragrunt.hcl b/stacks/recruiter-responder/terragrunt.hcl index 6a27cb7a..182796e7 100644 --- a/stacks/recruiter-responder/terragrunt.hcl +++ b/stacks/recruiter-responder/terragrunt.hcl @@ -19,5 +19,5 @@ dependency "external-secrets" { inputs = { # Override per-deploy in CI / commit. - image_tag = "0500c3d3" + image_tag = "7383b426" }