Phase C: Automated TLS cert provisioning for L7 routes
Add CertProvisioner that requests TLS certificates from Metacrypt's CA API during deploy. When a service has L7 routes, the agent checks for an existing cert, re-issues if missing or within 30 days of expiry, and writes chain+key to mc-proxy's cert directory before registering routes. - Add MetacryptConfig to agent config (server_url, ca_cert, mount, issuer, token_path) with defaults and env overrides - Add CertProvisioner (internal/agent/certs.go): REST client for Metacrypt IssueCert, atomic file writes, cert expiry checking - Wire into Agent struct and deploy flow (before route registration) - Add hasL7Routes/l7Hostnames helpers in deploy.go - Fix pre-existing lint issues: unreachable code in portalloc.go, gofmt in servicedef.go, gosec suppressions, golangci v2 config - Update vendored mc-proxy to fix protobuf init panic - 10 new tests, make all passes with 0 issues Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,14 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp
|
||||
}
|
||||
}
|
||||
|
||||
// Provision TLS certs for L7 routes before registering with mc-proxy.
|
||||
if a.Certs != nil && hasL7Routes(regRoutes) {
|
||||
hostnames := l7Hostnames(serviceName, regRoutes)
|
||||
if err := a.Certs.EnsureCert(ctx, serviceName, hostnames); err != nil {
|
||||
a.Logger.Warn("failed to provision TLS cert", "service", serviceName, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Register routes with mc-proxy after the container is running.
|
||||
if len(regRoutes) > 0 && a.Proxy != nil {
|
||||
hostPorts, err := registry.GetRouteHostPorts(a.DB, serviceName, compName)
|
||||
@@ -209,6 +217,37 @@ func ensureService(db *sql.DB, name string, active bool) error {
|
||||
return registry.UpdateServiceActive(db, name, active)
|
||||
}
|
||||
|
||||
// hasL7Routes reports whether any route uses L7 (TLS-terminating) mode.
|
||||
func hasL7Routes(routes []registry.Route) bool {
|
||||
for _, r := range routes {
|
||||
if r.Mode == "l7" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// l7Hostnames returns the unique hostnames from L7 routes, applying
|
||||
// the default hostname convention when a route has no explicit hostname.
|
||||
func l7Hostnames(serviceName string, routes []registry.Route) []string {
|
||||
seen := make(map[string]bool)
|
||||
var hostnames []string
|
||||
for _, r := range routes {
|
||||
if r.Mode != "l7" {
|
||||
continue
|
||||
}
|
||||
h := r.Hostname
|
||||
if h == "" {
|
||||
h = serviceName + ".svc.mcp.metacircular.net"
|
||||
}
|
||||
if !seen[h] {
|
||||
seen[h] = true
|
||||
hostnames = append(hostnames, h)
|
||||
}
|
||||
}
|
||||
return hostnames
|
||||
}
|
||||
|
||||
// ensureComponent creates the component if it does not exist, or updates its
|
||||
// spec if it does.
|
||||
func ensureComponent(db *sql.DB, c *registry.Component) error {
|
||||
|
||||
Reference in New Issue
Block a user