Add edge routing and health check RPCs (Phase 2)
New agent RPCs for v2 multi-node orchestration: - SetupEdgeRoute: provisions TLS cert from Metacrypt, resolves backend hostname to Tailnet IP, validates it's in 100.64.0.0/10, registers L7 route in mc-proxy. Rejects backend_tls=false. - RemoveEdgeRoute: removes mc-proxy route, cleans up TLS cert, removes registry entry. - ListEdgeRoutes: returns all edge routes with cert serial/expiry. - HealthCheck: returns agent health and container count. New database table (migration 4): edge_routes stores hostname, backend info, and cert paths for persistence across agent restarts. ProxyRouter gains CertPath/KeyPath helpers for consistent cert path construction. Security: - Backend hostname must resolve to a Tailnet IP (100.64.0.0/10) - backend_tls=false is rejected (no cleartext to backends) - Cert provisioning failure fails the setup (no route to missing cert) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
93
internal/registry/edge_routes.go
Normal file
93
internal/registry/edge_routes.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EdgeRoute represents a public edge route managed by the master.
|
||||
type EdgeRoute struct {
|
||||
Hostname string
|
||||
BackendHostname string
|
||||
BackendPort int
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// CreateEdgeRoute inserts or replaces an edge route.
|
||||
func CreateEdgeRoute(db *sql.DB, hostname, backendHostname string, backendPort int, tlsCert, tlsKey string) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO edge_routes (hostname, backend_hostname, backend_port, tls_cert, tls_key, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
ON CONFLICT(hostname) DO UPDATE SET
|
||||
backend_hostname = excluded.backend_hostname,
|
||||
backend_port = excluded.backend_port,
|
||||
tls_cert = excluded.tls_cert,
|
||||
tls_key = excluded.tls_key,
|
||||
updated_at = datetime('now')
|
||||
`, hostname, backendHostname, backendPort, tlsCert, tlsKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create edge route %s: %w", hostname, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEdgeRoute returns a single edge route by hostname.
|
||||
func GetEdgeRoute(db *sql.DB, hostname string) (*EdgeRoute, error) {
|
||||
var r EdgeRoute
|
||||
var createdAt, updatedAt string
|
||||
err := db.QueryRow(`
|
||||
SELECT hostname, backend_hostname, backend_port, tls_cert, tls_key, created_at, updated_at
|
||||
FROM edge_routes WHERE hostname = ?
|
||||
`, hostname).Scan(&r.Hostname, &r.BackendHostname, &r.BackendPort, &r.TLSCert, &r.TLSKey, &createdAt, &updatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get edge route %s: %w", hostname, err)
|
||||
}
|
||||
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||
r.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt)
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
// ListEdgeRoutes returns all edge routes.
|
||||
func ListEdgeRoutes(db *sql.DB) ([]*EdgeRoute, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT hostname, backend_hostname, backend_port, tls_cert, tls_key, created_at, updated_at
|
||||
FROM edge_routes ORDER BY hostname
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list edge routes: %w", err)
|
||||
}
|
||||
defer func() { _ = rows.Close() }()
|
||||
|
||||
var routes []*EdgeRoute
|
||||
for rows.Next() {
|
||||
var r EdgeRoute
|
||||
var createdAt, updatedAt string
|
||||
if err := rows.Scan(&r.Hostname, &r.BackendHostname, &r.BackendPort, &r.TLSCert, &r.TLSKey, &createdAt, &updatedAt); err != nil {
|
||||
return nil, fmt.Errorf("scan edge route: %w", err)
|
||||
}
|
||||
r.CreatedAt, _ = time.Parse("2006-01-02 15:04:05", createdAt)
|
||||
r.UpdatedAt, _ = time.Parse("2006-01-02 15:04:05", updatedAt)
|
||||
routes = append(routes, &r)
|
||||
}
|
||||
return routes, rows.Err()
|
||||
}
|
||||
|
||||
// DeleteEdgeRoute removes an edge route by hostname.
|
||||
func DeleteEdgeRoute(db *sql.DB, hostname string) error {
|
||||
result, err := db.Exec(`DELETE FROM edge_routes WHERE hostname = ?`, hostname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("delete edge route %s: %w", hostname, err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
if n == 0 {
|
||||
return fmt.Errorf("edge route %s not found", hostname)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user