feat: raw-TCP exposure with castle-managed TLS + reach model
Expose raw-TCP services (postgres) by name at <name>.<domain>:<port> and
cut the gateway's ACME wildcard cert onto them so they present a trusted
cert. Replaces the proxy/public booleans with a single `reach` enum
(off|internal|public); legacy input still parses via derived accessors.
Core:
- expose.tcp{port,tls} + TlsSpec(material: pair|combined|off, reload)
- tls.py: materialize cert files from Caddy's wildcard, reconcile on
renewal; `castle tls` CLI; optional cert_obtained events-exec hook
(gateway.cert_hook, gated so a plugin-less Caddy still parses)
- apply waits (bounded) for the wildcard to issue before materializing so
a fresh-node TLS service starts with its cert in place, then scopes
materialization to the deployments being applied
- reach: internal|public requires an expose block (no silent no-op);
public raw-TCP guarded until tunnel support lands
- chain.pem (${tls_ca}) is the issuer chain (leaf stripped), a real CA
bundle distinct from cert.pem
- one shared ${...} resolver (resolve_placeholders) for env and container
run fields; run.env now expands like volumes/args; $$ escapes a literal
- validate the generated Caddyfile before reloading the gateway so an
invalid config never degrades routing
Docs: docs/tcp-exposure.md. Tests cover reach/expose validation,
placeholder expansion + escape, issuer-chain material, TLS materialize.
This commit is contained in:
@@ -121,8 +121,12 @@ def run_info(args: argparse.Namespace) -> int:
|
||||
print(f" {BOLD}port{RESET}: {http.internal.port}")
|
||||
if http.health_path:
|
||||
print(f" {BOLD}health{RESET}: {http.health_path}")
|
||||
if service.proxy:
|
||||
if service.http_exposed:
|
||||
print(f" {BOLD}subdomain{RESET}: {name}.<gateway.domain>")
|
||||
if service.tcp_port is not None:
|
||||
# Raw-TCP reach is internal-only (public TCP is rejected at load, see
|
||||
# SystemdDeployment._validate_reach), so there's no "public" state here.
|
||||
print(f" {BOLD}tcp{RESET}: {name}.<gateway.domain>:{service.tcp_port} (internal)")
|
||||
if service.manage and service.manage.systemd:
|
||||
sd = service.manage.systemd
|
||||
print(f" {BOLD}systemd{RESET}: enabled={sd.enable}, restart={sd.restart.value}")
|
||||
|
||||
Reference in New Issue
Block a user