From 8dab5ca651eeeebe23cec2638f7edafe1896a11f Mon Sep 17 00:00:00 2001 From: Haiyan Meng Date: Thu, 17 Sep 2026 16:10:41 -0400 Subject: [PATCH] ateapi: require an egress gateway address --egress-gateway-address defaulted to empty, and empty meant ateapi sent no EgressGateway to atelet. An actor started that way installs no nftables redirect and leaves on the masquerade path, so it never reaches the PEP and its EgressPolicy is silently unenforced. A cluster could land there by omitting one flag, with nothing in the logs to say policy had stopped applying. Refuse to start when the flag is empty. Every in-tree deployment already sets it, so this only closes the misconfiguration. The downstream handling of an absent gateway is left in place: this makes the untunneled path unreachable by configuration, not impossible to express on the atelet and ateom APIs. --- cmd/ateapi/main.go | 16 +++++++++++++++- cmd/ateapi/main_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/cmd/ateapi/main.go b/cmd/ateapi/main.go index 340ea5109a..f8d2694be2 100644 --- a/cmd/ateapi/main.go +++ b/cmd/ateapi/main.go @@ -76,7 +76,7 @@ var ( postgresSchema = pflag.String("postgres-schema", "public", "PostgreSQL schema for Substrate tables. This overrides a search_path connection parameter.") actorIDJWTPoolFile = pflag.String("actor-id-jwt-pool", "", "The file that contains the serialized JWT authority pool for signing actor JWTs") - egressGatewayAddress = pflag.String("egress-gateway-address", "", "Address of the egress PEP. Empty disables tunneled egress.") + egressGatewayAddress = pflag.String("egress-gateway-address", "", "Address of the egress PEP. Required.") actorIDCAPoolFile = pflag.String("actor-id-ca-pool", "", "The file that contains the CA pool for signing actor JWTs") podIdentityCACerts = pflag.String("pod-identity-ca-certs", "", "The file that contains the pod-identity CA bundle, used both for verifying client certificates presented to the gRPC server and for verifying atelet serving certificates when dialing atelet. If empty, client-cert verification is disabled and atelet dials will fail.") @@ -106,6 +106,9 @@ func main() { if *templateResyncInterval < minResyncInterval { serverboot.Fatal(ctx, "Invalid --template-resync-interval", fmt.Errorf("must be at least %s", minResyncInterval)) } + if err := validateEgressGatewayAddress(); err != nil { + serverboot.Fatal(ctx, "Invalid --egress-gateway-address", err) + } // Kept separate from ctx so that in-progress work (clients, informers) is // not cancelled the moment SIGTERM arrives. The drainOnShutdown @@ -375,6 +378,17 @@ func newObjectStore(ctx context.Context) (objectstore.Store, error) { } } +// validateEgressGatewayAddress refuses a configuration with no egress gateway. +// An actor started without one is not tunneled through the PEP at all, so its +// EgressPolicy goes unenforced; failing at startup keeps a cluster from +// reaching that state by omitting a flag. +func validateEgressGatewayAddress() error { + if *egressGatewayAddress == "" { + return fmt.Errorf("--egress-gateway-address is required") + } + return nil +} + // connectStore builds the PostgreSQL-backed store.Interface. Startup fails if // its configuration is missing or the database can't be reached. func connectStore(ctx context.Context) (store.Interface, error) { diff --git a/cmd/ateapi/main_test.go b/cmd/ateapi/main_test.go index b5c19f1b88..211a417f02 100644 --- a/cmd/ateapi/main_test.go +++ b/cmd/ateapi/main_test.go @@ -32,3 +32,30 @@ func TestConnectStoreRequiresPostgresConnectionString(t *testing.T) { t.Fatalf("connectStore() error = %v, want missing-connection-string error", err) } } + +func TestValidateEgressGatewayAddress(t *testing.T) { + for _, tc := range []struct { + name string + address string + wantErr bool + }{ + {name: "empty", address: "", wantErr: true}, + {name: "set", address: "atenet-egress.ate-system.svc:443"}, + } { + t.Run(tc.name, func(t *testing.T) { + oldAddress := *egressGatewayAddress + t.Cleanup(func() { + *egressGatewayAddress = oldAddress + }) + *egressGatewayAddress = tc.address + + err := validateEgressGatewayAddress() + if gotErr := err != nil; gotErr != tc.wantErr { + t.Fatalf("validateEgressGatewayAddress() error = %v, wantErr %t", err, tc.wantErr) + } + if tc.wantErr && !strings.Contains(err.Error(), "--egress-gateway-address is required") { + t.Errorf("validateEgressGatewayAddress() error = %v, want missing-address error", err) + } + }) + } +}