From 50a5336475c362d88c382e938c37ae82660a208c Mon Sep 17 00:00:00 2001 From: Lior Lieberman Date: Mon, 27 Jul 2026 14:48:29 -0700 Subject: [PATCH 1/5] atunnel: add mTLS tunnel component for actor traffic atunnel is the in-worker component that carries Actor traffic in both directions over authenticated channels. * Server: an mTLS HTTPS listener that terminates connections from the ingress gateway, authorizes the peer by its SPIFFE identity, checks that the request targets the Actor currently activated on this worker, and reverse-proxies to the Actor over the private veth. * Egress: an activation-aware TCP proxy for transparently intercepted Actor connections. It resolves the original destination via SO_ORIGINAL_DST and forwards through an EgressDialer, asserting the Actor identity to the far end. Dormant until an egress gateway is configured; the client and dialer land here so the package is reviewed as one unit. Activate/Deactivate bracket an Actor activation so traffic is only carried while an Actor is assigned to the worker, and Deactivate drains in-flight streams before the Actor network is torn down. --- internal/atunnel/client.go | 213 +++++++++++++ internal/atunnel/client_test.go | 251 +++++++++++++++ internal/atunnel/egress.go | 207 ++++++++++++ internal/atunnel/egress_test.go | 114 +++++++ internal/atunnel/original_dst_linux.go | 72 +++++ internal/atunnel/server.go | 286 +++++++++++++++++ internal/atunnel/server_test.go | 424 +++++++++++++++++++++++++ 7 files changed, 1567 insertions(+) create mode 100644 internal/atunnel/client.go create mode 100644 internal/atunnel/client_test.go create mode 100644 internal/atunnel/egress.go create mode 100644 internal/atunnel/egress_test.go create mode 100644 internal/atunnel/original_dst_linux.go create mode 100644 internal/atunnel/server.go create mode 100644 internal/atunnel/server_test.go diff --git a/internal/atunnel/client.go b/internal/atunnel/client.go new file mode 100644 index 000000000..13692208c --- /dev/null +++ b/internal/atunnel/client.go @@ -0,0 +1,213 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package atunnel + +import ( + "bufio" + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "strconv" + "strings" + + "github.com/agent-substrate/substrate/internal/resources" +) + +const ( + // ActorAtespaceHeader identifies the atespace whose actor opened an egress + // tunnel. The egress gateway must authenticate this metadata before using it + // for policy decisions. + ActorAtespaceHeader = "X-Ate-Atespace" + // ActorNameHeader identifies the actor that opened an egress tunnel. + ActorNameHeader = "X-Ate-Actor" + // ActorVersionHeader is the Actor resource version observed when the worker + // was assigned. Gateways use it as a lower bound on cached Actor metadata. + ActorVersionHeader = "X-Ate-Actor-Version" +) + +// ClientConfig configures an egress CONNECT client. +type ClientConfig struct { + GatewayAddress string + ServerName string + CredentialBundlePath string + TrustBundlePath string + + // DialContext is injectable for tests. When nil, a net.Dialer is used. + DialContext func(context.Context, string, string) (net.Conn, error) +} + +// EgressMetadata is attached to an egress CONNECT request. BearerToken is +// optional until actor JWT issuance is wired into ateom. +type EgressMetadata struct { + Atespace string + ActorName string + ActorVersion int64 + BearerToken string +} + +// Client opens actor egress streams through an mTLS-authenticated gateway. +type Client struct { + gatewayAddress string + tlsConfig *tls.Config + dialContext func(context.Context, string, string) (net.Conn, error) +} + +var _ EgressDialer = (*Client)(nil) + +// NewClient creates an egress CONNECT client and validates its TLS material. +func NewClient(cfg ClientConfig) (*Client, error) { + if _, _, err := net.SplitHostPort(cfg.GatewayAddress); err != nil { + return nil, fmt.Errorf("atunnel: invalid egress gateway address %q: %w", cfg.GatewayAddress, err) + } + if cfg.ServerName == "" { + return nil, fmt.Errorf("atunnel: egress gateway server name is required") + } + if cfg.CredentialBundlePath == "" { + return nil, fmt.Errorf("atunnel: credential bundle path is required") + } + if cfg.TrustBundlePath == "" { + return nil, fmt.Errorf("atunnel: trust bundle path is required") + } + if _, err := loadCredentialBundle(cfg.CredentialBundlePath); err != nil { + return nil, err + } + trustPEM, err := os.ReadFile(cfg.TrustBundlePath) + if err != nil { + return nil, fmt.Errorf("atunnel: reading trust bundle: %w", err) + } + rootCAs := x509.NewCertPool() + if !rootCAs.AppendCertsFromPEM(trustPEM) { + return nil, fmt.Errorf("atunnel: trust bundle %q contains no certificates", cfg.TrustBundlePath) + } + + dialContext := cfg.DialContext + if dialContext == nil { + dialContext = (&net.Dialer{}).DialContext + } + credentialBundlePath := cfg.CredentialBundlePath + return &Client{ + gatewayAddress: cfg.GatewayAddress, + dialContext: dialContext, + tlsConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: rootCAs, + ServerName: cfg.ServerName, + GetClientCertificate: func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return loadCredentialBundle(credentialBundlePath) + }, + }, + }, nil +} + +// DialContext opens a CONNECT tunnel to destination. destination becomes the +// request authority, so it must include an explicit port. +func (c *Client) DialContext(ctx context.Context, destination string, metadata EgressMetadata) (net.Conn, error) { + if err := validateDestination(destination); err != nil { + return nil, err + } + if !resources.IsValidResourceName(metadata.Atespace) || !resources.IsValidResourceName(metadata.ActorName) { + return nil, fmt.Errorf("atunnel: invalid actor identity %q/%q", metadata.Atespace, metadata.ActorName) + } + if metadata.ActorVersion < 1 { + return nil, fmt.Errorf("atunnel: actor version must be positive") + } + + rawConn, err := c.dialContext(ctx, "tcp", c.gatewayAddress) + if err != nil { + return nil, fmt.Errorf("atunnel: connecting to egress gateway: %w", err) + } + tlsConn := tls.Client(rawConn, c.tlsConfig.Clone()) + if err := tlsConn.HandshakeContext(ctx); err != nil { + _ = rawConn.Close() + return nil, fmt.Errorf("atunnel: egress gateway TLS handshake: %w", err) + } + + req := &http.Request{ + Method: http.MethodConnect, + URL: &url.URL{Host: destination}, + Host: destination, + Header: http.Header{ + ActorAtespaceHeader: []string{metadata.Atespace}, + ActorNameHeader: []string{metadata.ActorName}, + ActorVersionHeader: []string{strconv.FormatInt(metadata.ActorVersion, 10)}, + }, + } + if metadata.BearerToken != "" { + req.Header.Set("Authorization", "Bearer "+metadata.BearerToken) + } + if err := req.Write(tlsConn); err != nil { + _ = tlsConn.Close() + return nil, fmt.Errorf("atunnel: writing CONNECT request: %w", err) + } + + reader := bufio.NewReader(tlsConn) + resp, err := http.ReadResponse(reader, req) + if err != nil { + _ = tlsConn.Close() + return nil, fmt.Errorf("atunnel: reading CONNECT response: %w", err) + } + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + body, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10)) + _ = resp.Body.Close() + _ = tlsConn.Close() + message := strings.TrimSpace(string(body)) + if message == "" { + message = http.StatusText(resp.StatusCode) + } + return nil, fmt.Errorf("atunnel: egress gateway rejected CONNECT with %s: %s", resp.Status, message) + } + + return &bufferedConn{Conn: tlsConn, reader: reader}, nil +} + +func validateDestination(destination string) error { + host, port, err := net.SplitHostPort(destination) + if err != nil { + return fmt.Errorf("atunnel: invalid egress destination %q: %w", destination, err) + } + if host == "" { + return fmt.Errorf("atunnel: invalid egress destination %q: host is empty", destination) + } + if net.ParseIP(host) == nil { + return fmt.Errorf("atunnel: invalid egress destination %q: host must be an IP address", destination) + } + portNumber, err := strconv.Atoi(port) + if err != nil || portNumber < 1 || portNumber > 65535 { + return fmt.Errorf("atunnel: invalid egress destination %q: port must be between 1 and 65535", destination) + } + return nil +} + +type bufferedConn struct { + net.Conn + reader *bufio.Reader +} + +func (c *bufferedConn) Read(p []byte) (int, error) { + return c.reader.Read(p) +} + +func (c *bufferedConn) CloseWrite() error { + if conn, ok := c.Conn.(interface{ CloseWrite() error }); ok { + return conn.CloseWrite() + } + return nil +} diff --git a/internal/atunnel/client_test.go b/internal/atunnel/client_test.go new file mode 100644 index 000000000..f52629f19 --- /dev/null +++ b/internal/atunnel/client_test.go @@ -0,0 +1,251 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package atunnel + +import ( + "bufio" + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "fmt" + "io" + "math/big" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestClientDialContext(t *testing.T) { + ca := newTestCA(t) + client := newTestClient(t, ca) + request := make(chan *http.Request, 1) + gatewayAddress := serveTestConnectGateway(t, ca, func(conn net.Conn, req *http.Request) { + request <- req + if _, err := io.WriteString(conn, "HTTP/1.1 200 Connection Established\r\n\r\nhello"); err != nil { + t.Errorf("writing CONNECT response: %v", err) + return + } + payload := make([]byte, len("ping")) + if _, err := io.ReadFull(conn, payload); err != nil { + t.Errorf("reading tunneled payload: %v", err) + return + } + if string(payload) != "ping" { + t.Errorf("tunneled payload = %q, want ping", payload) + } + }) + client.gatewayAddress = gatewayAddress + + conn, err := client.DialContext(context.Background(), "192.0.2.10:443", EgressMetadata{ + Atespace: "team-a", + ActorName: "actor-1", + ActorVersion: 7, + BearerToken: "actor-token", + }) + if err != nil { + t.Fatal(err) + } + defer conn.Close() + + gotRequest := <-request + if gotRequest.Method != http.MethodConnect { + t.Errorf("method = %q, want CONNECT", gotRequest.Method) + } + if gotRequest.Host != "192.0.2.10:443" { + t.Errorf("authority = %q, want 192.0.2.10:443", gotRequest.Host) + } + if got := gotRequest.Header.Get(ActorAtespaceHeader); got != "team-a" { + t.Errorf("%s = %q, want team-a", ActorAtespaceHeader, got) + } + if got := gotRequest.Header.Get(ActorNameHeader); got != "actor-1" { + t.Errorf("%s = %q, want actor-1", ActorNameHeader, got) + } + if got := gotRequest.Header.Get(ActorVersionHeader); got != "7" { + t.Errorf("%s = %q, want 7", ActorVersionHeader, got) + } + if got := gotRequest.Header.Get("Authorization"); got != "Bearer actor-token" { + t.Errorf("Authorization = %q, want Bearer actor-token", got) + } + + buffered := make([]byte, len("hello")) + if _, err := io.ReadFull(conn, buffered); err != nil { + t.Fatal(err) + } + if string(buffered) != "hello" { + t.Errorf("buffered payload = %q, want hello", buffered) + } + if _, err := io.WriteString(conn, "ping"); err != nil { + t.Fatal(err) + } +} + +func TestClientDialContextRejected(t *testing.T) { + ca := newTestCA(t) + client := newTestClient(t, ca) + client.gatewayAddress = serveTestConnectGateway(t, ca, func(conn net.Conn, _ *http.Request) { + body := "denied by policy" + _, _ = fmt.Fprintf(conn, "HTTP/1.1 403 Forbidden\r\nContent-Length: %d\r\n\r\n%s", len(body), body) + }) + + _, err := client.DialContext(context.Background(), "192.0.2.10:443", EgressMetadata{ + Atespace: "team-a", + ActorName: "actor-1", + ActorVersion: 7, + }) + if err == nil || !strings.Contains(err.Error(), "denied by policy") { + t.Fatalf("DialContext error = %v, want policy rejection", err) + } +} + +func TestClientDialContextValidatesInput(t *testing.T) { + ca := newTestCA(t) + client := newTestClient(t, ca) + tests := []struct { + name string + destination string + metadata EgressMetadata + }{ + { + name: "destination has no port", + destination: "192.0.2.10", + metadata: EgressMetadata{Atespace: "team-a", ActorName: "actor-1", ActorVersion: 7}, + }, + { + name: "destination is a hostname", + destination: "example.com:443", + metadata: EgressMetadata{Atespace: "team-a", ActorName: "actor-1", ActorVersion: 7}, + }, + { + name: "invalid atespace", + destination: "192.0.2.10:443", + metadata: EgressMetadata{Atespace: "TEAM A", ActorName: "actor-1", ActorVersion: 7}, + }, + { + name: "invalid actor", + destination: "192.0.2.10:443", + metadata: EgressMetadata{Atespace: "team-a", ActorName: "actor/1", ActorVersion: 7}, + }, + { + name: "invalid actor version", + destination: "192.0.2.10:443", + metadata: EgressMetadata{Atespace: "team-a", ActorName: "actor-1"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if _, err := client.DialContext(context.Background(), tt.destination, tt.metadata); err == nil { + t.Fatal("DialContext unexpectedly succeeded") + } + }) + } +} + +func newTestClient(t *testing.T, ca *testCA) *Client { + t.Helper() + dir := t.TempDir() + bundlePath := filepath.Join(dir, "client.pem") + trustPath := filepath.Join(dir, "trust.pem") + writeCredentialBundle(t, bundlePath, ca.issue(t, + "spiffe://cluster.local/ns/ate-demo/sa/ateom", + []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + )) + if err := os.WriteFile(trustPath, ca.certPEM, 0o600); err != nil { + t.Fatal(err) + } + client, err := NewClient(ClientConfig{ + GatewayAddress: "127.0.0.1:1", + ServerName: "egress.test", + CredentialBundlePath: bundlePath, + TrustBundlePath: trustPath, + }) + if err != nil { + t.Fatal(err) + } + return client +} + +func serveTestConnectGateway(t *testing.T, ca *testCA, handle func(net.Conn, *http.Request)) string { + t.Helper() + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + serverCert := issueDNSCertificate(t, ca, "egress.test") + clientCAs := x509.NewCertPool() + clientCAs.AppendCertsFromPEM(ca.certPEM) + tlsListener := tls.NewListener(listener, &tls.Config{ + MinVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{serverCert}, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCAs, + }) + t.Cleanup(func() { _ = tlsListener.Close() }) + + go func() { + conn, err := tlsListener.Accept() + if err != nil { + return + } + defer conn.Close() + req, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + t.Errorf("reading CONNECT request: %v", err) + return + } + handle(conn, req) + }() + return listener.Addr().String() +} + +func issueDNSCertificate(t *testing.T, ca *testCA, dnsName string) tls.Certificate { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + now := time.Now() + template := &x509.Certificate{ + SerialNumber: big.NewInt(now.UnixNano()), + NotBefore: now.Add(-time.Minute), + NotAfter: now.Add(time.Hour), + DNSNames: []string{dnsName}, + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + der, err := x509.CreateCertificate(rand.Reader, template, ca.cert, &key.PublicKey, ca.key) + if err != nil { + t.Fatal(err) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + t.Fatal(err) + } + cert, err := tls.X509KeyPair( + pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), + pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), + ) + if err != nil { + t.Fatal(err) + } + return cert +} diff --git a/internal/atunnel/egress.go b/internal/atunnel/egress.go new file mode 100644 index 000000000..5b1d63b08 --- /dev/null +++ b/internal/atunnel/egress.go @@ -0,0 +1,207 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package atunnel + +import ( + "context" + "errors" + "fmt" + "io" + "log/slog" + "net" + "sync" + + "github.com/agent-substrate/substrate/internal/resources" +) + +// EgressDialer opens an authenticated tunnel to an original destination. +type EgressDialer interface { + DialContext(context.Context, string, EgressMetadata) (net.Conn, error) +} + +// OriginalDestination returns the address that a transparently intercepted +// connection originally targeted. +type OriginalDestination func(net.Conn) (string, error) + +// Egress proxies actor TCP connections through an egress CONNECT dialer. It is +// long-lived across actor activations, but only carries traffic while an actor +// is assigned to its worker. +type Egress struct { + originalDestination OriginalDestination + + mu sync.Mutex + active *egressActivation +} + +type egressActivation struct { + metadata EgressMetadata + dialer EgressDialer + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup +} + +// NewEgress creates an activation-aware egress proxy. +func NewEgress(originalDestination OriginalDestination) (*Egress, error) { + if originalDestination == nil { + return nil, fmt.Errorf("atunnel: original destination resolver is required") + } + return &Egress{ + originalDestination: originalDestination, + }, nil +} + +// Serve accepts intercepted actor connections until ctx is canceled or the +// listener fails. +func (e *Egress) Serve(ctx context.Context, listener net.Listener) error { + done := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + _ = listener.Close() + case <-done: + } + }() + defer close(done) + + for { + conn, err := listener.Accept() + if err != nil { + if ctx.Err() != nil || errors.Is(err, net.ErrClosed) { + return nil + } + return fmt.Errorf("atunnel: accepting actor egress connection: %w", err) + } + e.handle(conn) + } +} + +// Activate allows egress for one actor. There can be only one active actor per +// worker. bearerToken may be empty until actor JWT issuance is available. +func (e *Egress) Activate(dialer EgressDialer, atespace, actorName string, actorVersion int64, bearerToken string) error { + if dialer == nil { + return fmt.Errorf("atunnel: egress dialer is required") + } + if !resources.IsValidResourceName(atespace) || !resources.IsValidResourceName(actorName) { + return fmt.Errorf("atunnel: invalid actor identity %q/%q", atespace, actorName) + } + if actorVersion < 1 { + return fmt.Errorf("atunnel: actor version must be positive") + } + e.mu.Lock() + defer e.mu.Unlock() + if e.active != nil { + return fmt.Errorf("atunnel: actor %s/%s already has active egress", e.active.metadata.Atespace, e.active.metadata.ActorName) + } + ctx, cancel := context.WithCancel(context.Background()) + e.active = &egressActivation{ + metadata: EgressMetadata{ + Atespace: atespace, + ActorName: actorName, + ActorVersion: actorVersion, + BearerToken: bearerToken, + }, + dialer: dialer, + ctx: ctx, + cancel: cancel, + } + return nil +} + +// Deactivate rejects new egress, closes active streams, and waits for their +// forwarding goroutines to exit. +func (e *Egress) Deactivate(ctx context.Context) error { + e.mu.Lock() + active := e.active + e.active = nil + if active != nil { + active.cancel() + } + e.mu.Unlock() + if active == nil { + return nil + } + + done := make(chan struct{}) + go func() { + active.wg.Wait() + close(done) + }() + select { + case <-done: + return nil + case <-ctx.Done(): + return fmt.Errorf("atunnel: waiting for active egress streams to stop: %w", ctx.Err()) + } +} + +func (e *Egress) handle(downstream net.Conn) { + e.mu.Lock() + active := e.active + if active == nil { + e.mu.Unlock() + _ = downstream.Close() + return + } + active.wg.Add(1) + e.mu.Unlock() + + go func() { + defer active.wg.Done() + defer downstream.Close() + + destination, err := e.originalDestination(downstream) + if err != nil { + slog.WarnContext(active.ctx, "atunnel failed to resolve original egress destination", slog.Any("err", err)) + return + } + upstream, err := active.dialer.DialContext(active.ctx, destination, active.metadata) + if err != nil { + slog.WarnContext(active.ctx, "atunnel failed to open egress tunnel", slog.String("destination", destination), slog.Any("err", err)) + return + } + defer upstream.Close() + + stop := context.AfterFunc(active.ctx, func() { + _ = downstream.Close() + _ = upstream.Close() + }) + defer stop() + + copyBothWays(downstream, upstream) + }() +} + +func copyBothWays(a, b net.Conn) { + done := make(chan struct{}, 2) + go func() { + _, _ = io.Copy(a, b) + closeWrite(a) + done <- struct{}{} + }() + go func() { + _, _ = io.Copy(b, a) + closeWrite(b) + done <- struct{}{} + }() + <-done + <-done +} + +func closeWrite(conn net.Conn) { + if conn, ok := conn.(interface{ CloseWrite() error }); ok { + _ = conn.CloseWrite() + } +} diff --git a/internal/atunnel/egress_test.go b/internal/atunnel/egress_test.go new file mode 100644 index 000000000..17828fbd5 --- /dev/null +++ b/internal/atunnel/egress_test.go @@ -0,0 +1,114 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package atunnel + +import ( + "context" + "io" + "net" + "testing" + "time" +) + +func TestEgressForwardsActiveActor(t *testing.T) { + dialed := make(chan egressDial, 1) + upstreamProxy, upstreamGateway := net.Pipe() + t.Cleanup(func() { + _ = upstreamProxy.Close() + _ = upstreamGateway.Close() + }) + dialer := egressDialerFunc(func(_ context.Context, destination string, metadata EgressMetadata) (net.Conn, error) { + dialed <- egressDial{destination: destination, metadata: metadata} + return upstreamProxy, nil + }) + egress, err := NewEgress(func(net.Conn) (string, error) { + return "192.0.2.10:443", nil + }) + if err != nil { + t.Fatal(err) + } + if err := egress.Activate(dialer, "team-a", "actor-1", 7, "actor-token"); err != nil { + t.Fatal(err) + } + + downstreamActor, downstreamProxy := net.Pipe() + t.Cleanup(func() { + _ = downstreamActor.Close() + _ = downstreamProxy.Close() + }) + egress.handle(downstreamProxy) + + gotDial := <-dialed + if gotDial.destination != "192.0.2.10:443" { + t.Errorf("destination = %q, want 192.0.2.10:443", gotDial.destination) + } + if gotDial.metadata != (EgressMetadata{Atespace: "team-a", ActorName: "actor-1", ActorVersion: 7, BearerToken: "actor-token"}) { + t.Errorf("metadata = %+v", gotDial.metadata) + } + + actorPayload := []byte("from actor") + go func() { _, _ = downstreamActor.Write(actorPayload) }() + gotAtGateway := make([]byte, len(actorPayload)) + if _, err := io.ReadFull(upstreamGateway, gotAtGateway); err != nil { + t.Fatal(err) + } + if string(gotAtGateway) != string(actorPayload) { + t.Errorf("gateway payload = %q, want %q", gotAtGateway, actorPayload) + } + + gatewayPayload := []byte("from gateway") + go func() { _, _ = upstreamGateway.Write(gatewayPayload) }() + gotAtActor := make([]byte, len(gatewayPayload)) + if _, err := io.ReadFull(downstreamActor, gotAtActor); err != nil { + t.Fatal(err) + } + if string(gotAtActor) != string(gatewayPayload) { + t.Errorf("actor payload = %q, want %q", gotAtActor, gatewayPayload) + } + + if err := egress.Deactivate(context.Background()); err != nil { + t.Fatal(err) + } +} + +func TestEgressRejectsInactiveConnection(t *testing.T) { + egress, err := NewEgress(func(net.Conn) (string, error) { + t.Fatal("inactive egress resolved destination") + return "", nil + }) + if err != nil { + t.Fatal(err) + } + actor, proxy := net.Pipe() + defer actor.Close() + if err := actor.SetReadDeadline(time.Now().Add(time.Second)); err != nil { + t.Fatal(err) + } + egress.handle(proxy) + if _, err := actor.Read(make([]byte, 1)); err == nil { + t.Fatal("inactive connection remained open") + } +} + +type egressDial struct { + destination string + metadata EgressMetadata +} + +type egressDialerFunc func(context.Context, string, EgressMetadata) (net.Conn, error) + +func (f egressDialerFunc) DialContext(ctx context.Context, destination string, metadata EgressMetadata) (net.Conn, error) { + return f(ctx, destination, metadata) +} diff --git a/internal/atunnel/original_dst_linux.go b/internal/atunnel/original_dst_linux.go new file mode 100644 index 000000000..07dd0f934 --- /dev/null +++ b/internal/atunnel/original_dst_linux.go @@ -0,0 +1,72 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build linux + +package atunnel + +import ( + "encoding/binary" + "fmt" + "net" + "strconv" + "unsafe" + + "golang.org/x/sys/unix" +) + +// TCPOriginalDestination reads the IPv4 destination preserved by a Linux +// REDIRECT rule. Actor networking is currently IPv4-only. +// TODO(liorlieberman) add the IPv6 IP6T_SO_ORIGINAL_DST variant +// when actor veth setup gains dual-stack support. +func TCPOriginalDestination(conn net.Conn) (string, error) { + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + return "", fmt.Errorf("atunnel: original destination requires a TCP connection, got %T", conn) + } + rawConn, err := tcpConn.SyscallConn() + if err != nil { + return "", fmt.Errorf("atunnel: acquiring TCP syscall connection: %w", err) + } + + var addr unix.RawSockaddrInet4 + var sockoptErr error + if err := rawConn.Control(func(fd uintptr) { + size := uint32(unsafe.Sizeof(addr)) + _, _, errno := unix.Syscall6( + unix.SYS_GETSOCKOPT, + fd, + unix.SOL_IP, + unix.SO_ORIGINAL_DST, + uintptr(unsafe.Pointer(&addr)), + uintptr(unsafe.Pointer(&size)), + 0, + ) + if errno != 0 { + sockoptErr = errno + } + }); err != nil { + return "", fmt.Errorf("atunnel: accessing TCP socket: %w", err) + } + if sockoptErr != nil { + return "", fmt.Errorf("atunnel: reading original TCP destination: %w", sockoptErr) + } + + portBytes := (*[2]byte)(unsafe.Pointer(&addr.Port)) + port := binary.BigEndian.Uint16(portBytes[:]) + if port == 0 { + return "", fmt.Errorf("atunnel: original TCP destination has port zero") + } + return net.JoinHostPort(net.IP(addr.Addr[:]).String(), strconv.Itoa(int(port))), nil +} diff --git a/internal/atunnel/server.go b/internal/atunnel/server.go new file mode 100644 index 000000000..b8dca5e8b --- /dev/null +++ b/internal/atunnel/server.go @@ -0,0 +1,286 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package atunnel carries actor ingress and egress through an ateom worker pod. +package atunnel + +import ( + "context" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "log/slog" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/agent-substrate/substrate/internal/resources" +) + +const ( + // StaleAssignmentHeader distinguishes an atunnel routing rejection from a + // 421 returned by the actor application itself. + StaleAssignmentHeader = "X-Ate-Assignment-Stale" +) + +// Config configures an ingress Server. +type Config struct { + CredentialBundlePath string + TrustBundlePath string + AllowedClientID string + Upstream *url.URL +} + +// Server is an activation-aware HTTPS reverse proxy. It is long-lived across +// actor activations, but only routes requests for the actor currently assigned +// to its worker. +type Server struct { + credentialBundlePath string + tlsConfig *tls.Config + proxy *httputil.ReverseProxy + + mu sync.Mutex + active *activation +} + +type activation struct { + atespace string + actorName string + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup +} + +// NewServer creates a Server and validates its TLS material. +func NewServer(cfg Config) (*Server, error) { + if cfg.CredentialBundlePath == "" { + return nil, fmt.Errorf("atunnel: credential bundle path is required") + } + if cfg.TrustBundlePath == "" { + return nil, fmt.Errorf("atunnel: trust bundle path is required") + } + if cfg.AllowedClientID == "" { + return nil, fmt.Errorf("atunnel: allowed client identity is required") + } + if cfg.Upstream == nil || cfg.Upstream.Scheme == "" || cfg.Upstream.Host == "" { + return nil, fmt.Errorf("atunnel: upstream URL is required") + } + + // Load once at startup so a malformed or missing projection fails the pod + // promptly. GetCertificate reloads the bundle for every new TLS connection, + // allowing kubelet's projected certificate rotation to take effect. + if _, err := loadCredentialBundle(cfg.CredentialBundlePath); err != nil { + return nil, err + } + trustPEM, err := os.ReadFile(cfg.TrustBundlePath) + if err != nil { + return nil, fmt.Errorf("atunnel: reading trust bundle: %w", err) + } + clientCAs := x509.NewCertPool() + if !clientCAs.AppendCertsFromPEM(trustPEM) { + return nil, fmt.Errorf("atunnel: trust bundle %q contains no certificates", cfg.TrustBundlePath) + } + + proxy := httputil.NewSingleHostReverseProxy(cfg.Upstream) + transport := http.DefaultTransport.(*http.Transport).Clone() + proxy.Transport = transport + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { + slog.WarnContext(r.Context(), "atunnel upstream request failed", slog.Any("err", err)) + http.Error(w, "bad gateway", http.StatusBadGateway) + } + + s := &Server{ + credentialBundlePath: cfg.CredentialBundlePath, + proxy: proxy, + } + s.tlsConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { + return loadCredentialBundle(s.credentialBundlePath) + }, + ClientAuth: tls.RequireAndVerifyClientCert, + ClientCAs: clientCAs, + VerifyConnection: func(cs tls.ConnectionState) error { + if len(cs.PeerCertificates) == 0 { + return fmt.Errorf("atunnel: client certificate is required") + } + for _, uri := range cs.PeerCertificates[0].URIs { + if uri.String() == cfg.AllowedClientID { + return nil + } + } + return fmt.Errorf("atunnel: client is not %q", cfg.AllowedClientID) + }, + } + return s, nil +} + +func loadCredentialBundle(path string) (*tls.Certificate, error) { + pemBytes, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("atunnel: reading credential bundle: %w", err) + } + cert, err := tls.X509KeyPair(pemBytes, pemBytes) + if err != nil { + return nil, fmt.Errorf("atunnel: parsing credential bundle: %w", err) + } + return &cert, nil +} + +// Serve serves HTTPS on lis until ctx is canceled or the server fails. +func (s *Server) Serve(ctx context.Context, lis net.Listener) error { + httpServer := &http.Server{ + Handler: s, + TLSConfig: s.tlsConfig, + ReadHeaderTimeout: 10 * time.Second, + } + done := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + _ = httpServer.Close() + case <-done: + } + }() + err := httpServer.ServeTLS(lis, "", "") + close(done) + if errors.Is(err, http.ErrServerClosed) && ctx.Err() != nil { + return nil + } + return err +} + +// Activate allows requests for actorName in atespace. There can be only one +// active actor per worker. +func (s *Server) Activate(atespace, actorName string) error { + if !resources.IsValidResourceName(atespace) || !resources.IsValidResourceName(actorName) { + return fmt.Errorf("atunnel: invalid actor identity %q/%q", atespace, actorName) + } + s.mu.Lock() + defer s.mu.Unlock() + if s.active != nil { + return fmt.Errorf("atunnel: actor %s/%s is already active", s.active.atespace, s.active.actorName) + } + ctx, cancel := context.WithCancel(context.Background()) + s.active = &activation{ + atespace: atespace, + actorName: actorName, + ctx: ctx, + cancel: cancel, + } + return nil +} + +// Deactivate rejects new requests, cancels requests for the active actor, and +// waits for their handlers to exit before returning. +func (s *Server) Deactivate(ctx context.Context) error { + s.mu.Lock() + active := s.active + s.active = nil + if active != nil { + active.cancel() + } + s.mu.Unlock() + if active == nil { + return nil + } + + done := make(chan struct{}) + go func() { + active.wg.Wait() + close(done) + }() + select { + case <-done: + s.closeIdleUpstreamConnections() + return nil + case <-ctx.Done(): + s.closeIdleUpstreamConnections() + return fmt.Errorf("atunnel: waiting for active requests to stop: %w", ctx.Err()) + } +} + +func (s *Server) closeIdleUpstreamConnections() { + if transport, ok := s.proxy.Transport.(interface{ CloseIdleConnections() }); ok { + transport.CloseIdleConnections() + } +} + +// ServeHTTP validates the actor hostname on every request before proxying it. +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + host, err := requestHostname(r.Host) + if err != nil { + s.reject(w) + return + } + atespace, actorName, err := resources.ParseActorDNSName(host) + if err != nil { + s.reject(w) + return + } + + s.mu.Lock() + active := s.active + if active == nil || active.atespace != atespace || active.actorName != actorName { + s.mu.Unlock() + s.reject(w) + return + } + active.wg.Add(1) + s.mu.Unlock() + defer active.wg.Done() + + requestCtx, cancel := context.WithCancel(r.Context()) + stop := context.AfterFunc(active.ctx, cancel) + defer func() { + stop() + cancel() + }() + + // ReverseProxy changes the URL destination but intentionally retains Host, + // allowing the actor application to observe its stable mesh hostname. + s.proxy.ServeHTTP(w, r.WithContext(requestCtx)) +} + +func (s *Server) reject(w http.ResponseWriter) { + w.Header().Set(StaleAssignmentHeader, "true") + http.Error(w, "misdirected request", http.StatusMisdirectedRequest) +} + +func requestHostname(hostport string) (string, error) { + if hostport == "" { + return "", fmt.Errorf("empty host") + } + host := hostport + if strings.Contains(hostport, ":") { + var port string + var err error + host, port, err = net.SplitHostPort(hostport) + if err != nil { + return "", fmt.Errorf("invalid host %q: %w", hostport, err) + } + portNumber, err := strconv.Atoi(port) + if err != nil || portNumber < 1 || portNumber > 65535 { + return "", fmt.Errorf("invalid port in host %q", hostport) + } + } + return strings.ToLower(host), nil +} diff --git a/internal/atunnel/server_test.go b/internal/atunnel/server_test.go new file mode 100644 index 000000000..1b75a836a --- /dev/null +++ b/internal/atunnel/server_test.go @@ -0,0 +1,424 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package atunnel + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "net" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + "time" +) + +func TestServeHTTP(t *testing.T) { + upstreamHost := make(chan string, 3) + upstreamURL, err := url.Parse("http://actor.internal:80") + if err != nil { + t.Fatal(err) + } + + s := newTestServer(t, upstreamURL) + s.proxy.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) { + upstreamHost <- r.Host + return &http.Response{ + StatusCode: http.StatusNoContent, + Header: make(http.Header), + Body: http.NoBody, + }, nil + }) + if err := s.Activate("team-a", "actor-1"); err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + host string + wantStatus int + }{ + {"active actor", "actor-1.team-a.actors.resources.substrate.ate.dev", http.StatusNoContent}, + {"active actor with port", "actor-1.team-a.actors.resources.substrate.ate.dev:443", http.StatusNoContent}, + {"DNS case insensitive", "ACTOR-1.TEAM-A.ACTORS.RESOURCES.SUBSTRATE.ATE.DEV", http.StatusNoContent}, + {"wrong actor", "actor-2.team-a.actors.resources.substrate.ate.dev", http.StatusMisdirectedRequest}, + {"wrong atespace", "actor-1.team-b.actors.resources.substrate.ate.dev", http.StatusMisdirectedRequest}, + {"suffix confusion", "actor-1.team-a.actors.resources.substrate.ate.dev.example.com", http.StatusMisdirectedRequest}, + {"malformed port", "actor-1.team-a.actors.resources.substrate.ate.dev:nope", http.StatusMisdirectedRequest}, + {"empty", "", http.StatusMisdirectedRequest}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "https://worker/hello", nil) + req.Host = tt.host + rec := httptest.NewRecorder() + s.ServeHTTP(rec, req) + if rec.Code != tt.wantStatus { + t.Fatalf("status = %d, want %d", rec.Code, tt.wantStatus) + } + if tt.wantStatus == http.StatusMisdirectedRequest && rec.Header().Get(StaleAssignmentHeader) != "true" { + t.Errorf("missing %s response header", StaleAssignmentHeader) + } + }) + } + + for range 3 { + if got := <-upstreamHost; got != "actor-1.team-a.actors.resources.substrate.ate.dev" && got != "actor-1.team-a.actors.resources.substrate.ate.dev:443" && got != "ACTOR-1.TEAM-A.ACTORS.RESOURCES.SUBSTRATE.ATE.DEV" { + t.Errorf("upstream Host = %q", got) + } + } +} + +type roundTripFunc func(*http.Request) (*http.Response, error) + +func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) { + return f(r) +} + +type idleClosingRoundTripper struct { + closed bool +} + +func (t *idleClosingRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusNoContent, + Header: make(http.Header), + Body: http.NoBody, + }, nil +} + +func (t *idleClosingRoundTripper) CloseIdleConnections() { + t.closed = true +} + +func TestDeactivateClosesIdleUpstreamConnections(t *testing.T) { + upstream, err := url.Parse("http://actor.internal:80") + if err != nil { + t.Fatal(err) + } + s := newTestServer(t, upstream) + transport := &idleClosingRoundTripper{} + s.proxy.Transport = transport + if err := s.Activate("team-a", "actor-1"); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest(http.MethodGet, "https://worker/", nil) + req.Host = "actor-1.team-a.actors.resources.substrate.ate.dev" + rec := httptest.NewRecorder() + s.ServeHTTP(rec, req) + if rec.Code != http.StatusNoContent { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusNoContent) + } + if err := s.Deactivate(context.Background()); err != nil { + t.Fatal(err) + } + if !transport.closed { + t.Fatal("Deactivate did not close idle upstream connections") + } +} + +func TestInactive(t *testing.T) { + upstream, err := url.Parse("http://127.0.0.1:1") + if err != nil { + t.Fatal(err) + } + s := newTestServer(t, upstream) + req := httptest.NewRequest(http.MethodGet, "https://worker/", nil) + req.Host = "actor-1.team-a.actors.resources.substrate.ate.dev" + + for _, phase := range []string{"before activation", "after deactivation"} { + t.Run(phase, func(t *testing.T) { + rec := httptest.NewRecorder() + s.ServeHTTP(rec, req) + if rec.Code != http.StatusMisdirectedRequest { + t.Fatalf("status = %d, want %d", rec.Code, http.StatusMisdirectedRequest) + } + }) + if phase == "before activation" { + if err := s.Activate("team-a", "actor-1"); err != nil { + t.Fatal(err) + } + if err := s.Deactivate(context.Background()); err != nil { + t.Fatal(err) + } + } + } +} + +func TestMutualTLSClientIdentity(t *testing.T) { + dir := t.TempDir() + ca := newTestCA(t) + serverCert := ca.issue(t, "", []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}) + bundlePath := filepath.Join(dir, "server.pem") + trustPath := filepath.Join(dir, "trust.pem") + writeCredentialBundle(t, bundlePath, serverCert) + if err := os.WriteFile(trustPath, ca.certPEM, 0o600); err != nil { + t.Fatal(err) + } + upstream, err := url.Parse("http://actor.internal:80") + if err != nil { + t.Fatal(err) + } + s, err := NewServer(Config{ + CredentialBundlePath: bundlePath, + TrustBundlePath: trustPath, + AllowedClientID: "spiffe://cluster.local/ns/ate-system/sa/atenet-router", + Upstream: upstream, + }) + if err != nil { + t.Fatal(err) + } + + untrustedCA := newTestCA(t) + tests := []struct { + name string + cert tls.Certificate + wantErr bool + }{ + { + name: "allowed identity", + cert: ca.issue(t, "spiffe://cluster.local/ns/ate-system/sa/atenet-router", []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), + }, + { + name: "wrong identity", + cert: ca.issue(t, "spiffe://cluster.local/ns/ate-system/sa/not-the-gateway", []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), + wantErr: true, + }, + { + name: "untrusted identity", + cert: untrustedCA.issue(t, "spiffe://cluster.local/ns/ate-system/sa/atenet-router", []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}), + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + serverErr, clientErr := tlsHandshake(s.tlsConfig, &tls.Config{ + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: true, // Test only the server's client authentication here. + Certificates: []tls.Certificate{tt.cert}, + }) + gotErr := serverErr != nil || clientErr != nil + if gotErr != tt.wantErr { + t.Fatalf("server error = %v, client error = %v, want error %v", serverErr, clientErr, tt.wantErr) + } + }) + } +} + +func TestDeactivateCancelsInflightRequest(t *testing.T) { + upstream, err := url.Parse("http://actor.internal:80") + if err != nil { + t.Fatal(err) + } + s := newTestServer(t, upstream) + if err := s.Activate("team-a", "actor-1"); err != nil { + t.Fatal(err) + } + started := make(chan struct{}) + s.proxy.Transport = roundTripFunc(func(r *http.Request) (*http.Response, error) { + close(started) + <-r.Context().Done() + return nil, r.Context().Err() + }) + + done := make(chan struct{}) + go func() { + defer close(done) + req := httptest.NewRequest(http.MethodGet, "https://worker/", nil) + req.Host = "actor-1.team-a.actors.resources.substrate.ate.dev" + s.ServeHTTP(httptest.NewRecorder(), req) + }() + <-started + if err := s.Deactivate(context.Background()); err != nil { + t.Fatal(err) + } + <-done +} + +func newTestServer(t *testing.T, upstream *url.URL) *Server { + t.Helper() + dir := t.TempDir() + bundle, trust := makeCertFiles(t, dir) + s, err := NewServer(Config{ + CredentialBundlePath: bundle, + TrustBundlePath: trust, + AllowedClientID: "spiffe://cluster.local/ns/ate-system/sa/atenet-router", + Upstream: upstream, + }) + if err != nil { + t.Fatal(err) + } + return s +} + +func makeCertFiles(t *testing.T, dir string) (bundlePath, trustPath string) { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + now := time.Now() + template := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "test"}, + NotBefore: now.Add(-time.Minute), + NotAfter: now.Add(time.Hour), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) + if err != nil { + t.Fatal(err) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + t.Fatal(err) + } + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}) + keyPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}) + bundlePath = filepath.Join(dir, "bundle.pem") + trustPath = filepath.Join(dir, "trust.pem") + if err := os.WriteFile(bundlePath, append(certPEM, keyPEM...), 0o600); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(trustPath, certPEM, 0o600); err != nil { + t.Fatal(err) + } + return bundlePath, trustPath +} + +type testCA struct { + cert *x509.Certificate + key *ecdsa.PrivateKey + certPEM []byte +} + +func newTestCA(t *testing.T) *testCA { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + now := time.Now() + template := &x509.Certificate{ + SerialNumber: big.NewInt(now.UnixNano()), + Subject: pkix.Name{CommonName: "test CA"}, + NotBefore: now.Add(-time.Minute), + NotAfter: now.Add(time.Hour), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key) + if err != nil { + t.Fatal(err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + t.Fatal(err) + } + return &testCA{ + cert: cert, + key: key, + certPEM: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), + } +} + +func (ca *testCA) issue(t *testing.T, spiffeID string, usages []x509.ExtKeyUsage) tls.Certificate { + t.Helper() + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + now := time.Now() + template := &x509.Certificate{ + SerialNumber: big.NewInt(now.UnixNano()), + NotBefore: now.Add(-time.Minute), + NotAfter: now.Add(time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: usages, + } + if spiffeID != "" { + uri, err := url.Parse(spiffeID) + if err != nil { + t.Fatal(err) + } + template.URIs = []*url.URL{uri} + } + der, err := x509.CreateCertificate(rand.Reader, template, ca.cert, &key.PublicKey, ca.key) + if err != nil { + t.Fatal(err) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + t.Fatal(err) + } + cert, err := tls.X509KeyPair( + append(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), ca.certPEM...), + pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER}), + ) + if err != nil { + t.Fatal(err) + } + return cert +} + +func writeCredentialBundle(t *testing.T, path string, cert tls.Certificate) { + t.Helper() + key, ok := cert.PrivateKey.(*ecdsa.PrivateKey) + if !ok { + t.Fatalf("private key has type %T", cert.PrivateKey) + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + t.Fatal(err) + } + var bundle []byte + for _, der := range cert.Certificate { + bundle = append(bundle, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})...) + } + bundle = append(bundle, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})...) + if err := os.WriteFile(path, bundle, 0o600); err != nil { + t.Fatal(err) + } +} + +func tlsHandshake(serverConfig, clientConfig *tls.Config) (serverErr, clientErr error) { + serverConn, clientConn := net.Pipe() + serverTLS := tls.Server(serverConn, serverConfig) + clientTLS := tls.Client(clientConn, clientConfig) + done := make(chan error, 1) + go func() { + err := serverTLS.Handshake() + _ = serverConn.Close() + done <- err + }() + clientErr = clientTLS.Handshake() + _ = clientConn.Close() + serverErr = <-done + return serverErr, clientErr +} From 0df558454a101a7c776861e8477a4cfbc1ffcb11 Mon Sep 17 00:00:00 2001 From: Lior Lieberman Date: Mon, 27 Jul 2026 14:49:38 -0700 Subject: [PATCH 2/5] worker: serve actor ingress through atunnel Every worker pod now hosts an atunnel ingress server on :443 and an atunnel egress listener, both long-lived, with per-activation Activate/Deactivate bracketing every Run, Restore and Checkpoint. The nftables rules ateom installs change accordingly: * The pod-IP:80 -> actor-veth:80 DNAT is gone. Worker port 80 is no longer an Actor ingress path; the only way in is the mTLS listener on :443, which authorizes the caller and checks the Actor is the one currently assigned to this worker. * A prerouting REDIRECT sends Actor TCP egress to the local atunnel egress listener, preserving SO_ORIGINAL_DST. It is only installed when the activation carries an egress gateway address, which nothing populates yet, so the masquerade path is unchanged and Actor egress behaves exactly as before. The ateom Run/Restore protos gain the two fields that arm that path: egress_gateway_address, which decides whether the redirect is installed at all, and actor_version, the Actor resource version ate-api observed when assigning the worker, which atunnel asserts to the egress gateway as a lower bound on trustworthy Actor metadata. Both are consumed here and left unset. atelet and ate-api start populating them in the egress gateway change, which is the point at which they mean anything, so this change adds no new requirement to the atelet wire contract. atecontroller gives worker pods the podidentity credential + trust bundles (the atunnel server identity), the servicedns trust bundle, and container port 443. podidentitysigner now issues certs with ExtKeyUsageServerAuth as well as ClientAuth, without which the worker cannot present its podidentity cert as a TLS server cert and the gateway handshake fails. --- .../internal/controllers/workerpool_apply.go | 75 +++++++- .../controllers/workerpool_apply_test.go | 72 ++++++- .../controllers/workerpool_controller_test.go | 6 +- cmd/ateom-gvisor/main.go | 176 +++++++++++++++++- cmd/ateom-microvm/checkpoint.go | 3 + cmd/ateom-microvm/main.go | 144 +++++++++++++- cmd/ateom-microvm/restore.go | 11 ++ cmd/ateom-microvm/run.go | 16 ++ .../podidentitysigner/podidentitysigner.go | 16 +- .../podidentitysigner_test.go | 24 +++ internal/ateomnet/net.go | 104 +++++------ internal/atunnel/server.go | 4 +- internal/proto/ateompb/ateom.pb.go | 105 ++++++++--- internal/proto/ateompb/ateom.proto | 12 ++ 14 files changed, 633 insertions(+), 135 deletions(-) diff --git a/cmd/atecontroller/internal/controllers/workerpool_apply.go b/cmd/atecontroller/internal/controllers/workerpool_apply.go index 913e2a168..7cded15b8 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_apply.go +++ b/cmd/atecontroller/internal/controllers/workerpool_apply.go @@ -28,6 +28,13 @@ import ( // uid so each worker pod is a distinct telemetry source. const ateomOTelResourceAttributes = "k8s.namespace.name=$(POD_NAMESPACE),k8s.pod.name=$(POD_NAME),k8s.pod.uid=$(POD_UID),service.instance.id=$(POD_UID)" +const ( + atunnelIdentityVolume = "atunnel-identity" + atunnelIdentityMountPath = "/run/podidentity.podcert.ate.dev" + atunnelEgressTrustVolume = "atunnel-egress-trust" + atunnelEgressTrustMountPath = "/run/servicedns.podcert.ate.dev" +) + // buildDeploymentApplyConfig constructs the SSA apply configuration for the // Deployment managed by a WorkerPool. Only fields owned by this controller // are declared here. otelEndpoint, when non-empty, is propagated to the ateom @@ -38,23 +45,73 @@ func buildDeploymentApplyConfig(wp *atev1alpha1.WorkerPool, otelEndpoint string) WithImage(wp.Spec.AteomImage). WithArgs( "--pod-uid=$(POD_UID)", + "--atunnel-listen-address=0.0.0.0:443", + "--atunnel-credential-bundle="+atunnelIdentityMountPath+"/credential-bundle.pem", + "--atunnel-trust-bundle="+atunnelIdentityMountPath+"/trust-bundle.pem", + "--atunnel-egress-listen-address=0.0.0.0:15001", + "--atunnel-egress-trust-bundle="+atunnelEgressTrustMountPath+"/trust-bundle.pem", ). + WithPorts(corev1ac.ContainerPort(). + WithName("https"). + WithContainerPort(443). + WithProtocol(corev1.ProtocolTCP)). WithSecurityContext(ateomSecurityContext(wp.Spec.SandboxClass)). WithEnv(ateomContainerEnv(otelEndpoint)...). - WithVolumeMounts(corev1ac.VolumeMount(). - WithName("run-ateom"). - WithMountPath(ateompath.BasePath). - WithMountPropagation(corev1.MountPropagationHostToContainer)) + WithVolumeMounts( + corev1ac.VolumeMount(). + WithName("run-ateom"). + WithMountPath(ateompath.BasePath). + WithMountPropagation(corev1.MountPropagationHostToContainer), + corev1ac.VolumeMount(). + WithName(atunnelIdentityVolume). + WithMountPath(atunnelIdentityMountPath). + WithReadOnly(true), + corev1ac.VolumeMount(). + WithName(atunnelEgressTrustVolume). + WithMountPath(atunnelEgressTrustMountPath). + WithReadOnly(true), + ) podSpecAC := corev1ac.PodSpec(). WithSecurityContext(corev1ac.PodSecurityContext(). WithRunAsUser(0). WithRunAsGroup(0)). - WithVolumes(corev1ac.Volume(). - WithName("run-ateom"). - WithHostPath(corev1ac.HostPathVolumeSource(). - WithPath(ateompath.BasePath). - WithType(corev1.HostPathDirectoryOrCreate))) + WithVolumes( + corev1ac.Volume(). + WithName("run-ateom"). + WithHostPath(corev1ac.HostPathVolumeSource(). + WithPath(ateompath.BasePath). + WithType(corev1.HostPathDirectoryOrCreate)), + corev1ac.Volume(). + WithName(atunnelIdentityVolume). + WithProjected(corev1ac.ProjectedVolumeSource(). + WithSources( + corev1ac.VolumeProjection(). + WithPodCertificate(corev1ac.PodCertificateProjection(). + WithSignerName("podidentity.podcert.ate.dev/identity"). + WithKeyType("ECDSAP256"). + WithCredentialBundlePath("credential-bundle.pem")), + corev1ac.VolumeProjection(). + WithClusterTrustBundle(corev1ac.ClusterTrustBundleProjection(). + WithSignerName("podidentity.podcert.ate.dev/identity"). + WithLabelSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"podcert.ate.dev/canarying": "live"})). + WithPath("trust-bundle.pem")), + ), + ), + corev1ac.Volume(). + WithName(atunnelEgressTrustVolume). + WithProjected(corev1ac.ProjectedVolumeSource(). + WithSources( + corev1ac.VolumeProjection(). + WithClusterTrustBundle(corev1ac.ClusterTrustBundleProjection(). + WithSignerName("servicedns.podcert.ate.dev/identity"). + WithLabelSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"podcert.ate.dev/canarying": "live"})). + WithPath("trust-bundle.pem")), + ), + ), + ) applyWorkerPoolPodTemplate(podSpecAC, containerAC, wp.Spec.Template) maybeApplyMicroVMPodShape(podSpecAC, containerAC, wp.Spec.SandboxClass) diff --git a/cmd/atecontroller/internal/controllers/workerpool_apply_test.go b/cmd/atecontroller/internal/controllers/workerpool_apply_test.go index 586f0d26b..97029a6cc 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_apply_test.go +++ b/cmd/atecontroller/internal/controllers/workerpool_apply_test.go @@ -426,15 +426,57 @@ func expectedDeploymentApplyConfig(mutatePodSpec func(*corev1ac.PodSpecApplyConf WithSecurityContext(corev1ac.PodSecurityContext(). WithRunAsUser(0). WithRunAsGroup(0)). - WithVolumes(corev1ac.Volume(). - WithName("run-ateom"). - WithHostPath(corev1ac.HostPathVolumeSource(). - WithPath(ateompath.BasePath). - WithType(corev1.HostPathDirectoryOrCreate))). + WithVolumes( + corev1ac.Volume(). + WithName("run-ateom"). + WithHostPath(corev1ac.HostPathVolumeSource(). + WithPath(ateompath.BasePath). + WithType(corev1.HostPathDirectoryOrCreate)), + corev1ac.Volume(). + WithName(atunnelIdentityVolume). + WithProjected(corev1ac.ProjectedVolumeSource(). + WithSources( + corev1ac.VolumeProjection(). + WithPodCertificate(corev1ac.PodCertificateProjection(). + WithSignerName("podidentity.podcert.ate.dev/identity"). + WithKeyType("ECDSAP256"). + WithCredentialBundlePath("credential-bundle.pem")), + corev1ac.VolumeProjection(). + WithClusterTrustBundle(corev1ac.ClusterTrustBundleProjection(). + WithSignerName("podidentity.podcert.ate.dev/identity"). + WithLabelSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"podcert.ate.dev/canarying": "live"})). + WithPath("trust-bundle.pem")), + ), + ), + corev1ac.Volume(). + WithName(atunnelEgressTrustVolume). + WithProjected(corev1ac.ProjectedVolumeSource(). + WithSources( + corev1ac.VolumeProjection(). + WithClusterTrustBundle(corev1ac.ClusterTrustBundleProjection(). + WithSignerName("servicedns.podcert.ate.dev/identity"). + WithLabelSelector(metav1ac.LabelSelector(). + WithMatchLabels(map[string]string{"podcert.ate.dev/canarying": "live"})). + WithPath("trust-bundle.pem")), + ), + ), + ). WithContainers(corev1ac.Container(). WithName("ateom"). WithImage(wp.Spec.AteomImage). - WithArgs("--pod-uid=$(POD_UID)"). + WithArgs( + "--pod-uid=$(POD_UID)", + "--atunnel-listen-address=0.0.0.0:443", + "--atunnel-credential-bundle="+atunnelIdentityMountPath+"/credential-bundle.pem", + "--atunnel-trust-bundle="+atunnelIdentityMountPath+"/trust-bundle.pem", + "--atunnel-egress-listen-address=0.0.0.0:15001", + "--atunnel-egress-trust-bundle="+atunnelEgressTrustMountPath+"/trust-bundle.pem", + ). + WithPorts(corev1ac.ContainerPort(). + WithName("https"). + WithContainerPort(443). + WithProtocol(corev1.ProtocolTCP)). WithSecurityContext(corev1ac.SecurityContext(). WithRunAsUser(0). WithRunAsGroup(0). @@ -449,10 +491,20 @@ func expectedDeploymentApplyConfig(mutatePodSpec func(*corev1ac.PodSpecApplyConf WithValueFrom(corev1ac.EnvVarSource(). WithFieldRef(corev1ac.ObjectFieldSelector(). WithFieldPath("metadata.uid")))). - WithVolumeMounts(corev1ac.VolumeMount(). - WithName("run-ateom"). - WithMountPath(ateompath.BasePath). - WithMountPropagation(corev1.MountPropagationHostToContainer)). + WithVolumeMounts( + corev1ac.VolumeMount(). + WithName("run-ateom"). + WithMountPath(ateompath.BasePath). + WithMountPropagation(corev1.MountPropagationHostToContainer), + corev1ac.VolumeMount(). + WithName(atunnelIdentityVolume). + WithMountPath(atunnelIdentityMountPath). + WithReadOnly(true), + corev1ac.VolumeMount(). + WithName(atunnelEgressTrustVolume). + WithMountPath(atunnelEgressTrustMountPath). + WithReadOnly(true), + ). WithResources(corev1ac.ResourceRequirements())) podSpecAC.NodeSelector = map[string]string{} diff --git a/cmd/atecontroller/internal/controllers/workerpool_controller_test.go b/cmd/atecontroller/internal/controllers/workerpool_controller_test.go index a6d9fd307..d6861e2d0 100644 --- a/cmd/atecontroller/internal/controllers/workerpool_controller_test.go +++ b/cmd/atecontroller/internal/controllers/workerpool_controller_test.go @@ -146,8 +146,10 @@ func TestWorkerPoolCreatesDeployment(t *testing.T) { if len(dep.OwnerReferences) == 0 || dep.OwnerReferences[0].Name != wp.Name { return false, nil } - return len(dep.Spec.Template.Spec.Volumes) == 1 && - dep.Spec.Template.Spec.Volumes[0].Name == "run-ateom", nil + return len(dep.Spec.Template.Spec.Volumes) == 3 && + dep.Spec.Template.Spec.Volumes[0].Name == "run-ateom" && + dep.Spec.Template.Spec.Volumes[1].Name == atunnelIdentityVolume && + dep.Spec.Template.Spec.Volumes[2].Name == atunnelEgressTrustVolume, nil }) } diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index ca2b15399..49989b5ef 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -18,9 +18,11 @@ package main import ( "context" + "errors" "fmt" "log/slog" "net" + "net/url" "os" "sort" "strings" @@ -31,6 +33,7 @@ import ( "github.com/agent-substrate/substrate/internal/ateinterceptors" "github.com/agent-substrate/substrate/internal/ateomnet" "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/internal/atunnel" "github.com/agent-substrate/substrate/internal/contextlogging" "github.com/agent-substrate/substrate/internal/imagecache" "github.com/agent-substrate/substrate/internal/proto/ateompb" @@ -51,11 +54,22 @@ import ( var ( podUID = pflag.String("pod-uid", "", "The UID of the current pod") + atunnelListenAddress = pflag.String("atunnel-listen-address", "0.0.0.0:443", "Address for actor ingress HTTPS") + atunnelCredentialBundle = pflag.String("atunnel-credential-bundle", "/run/podidentity.podcert.ate.dev/credential-bundle.pem", "PEM credential bundle for actor ingress HTTPS") + atunnelTrustBundle = pflag.String("atunnel-trust-bundle", "/run/podidentity.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle for actor ingress clients") + atunnelClientIdentity = pflag.String("atunnel-client-identity", "spiffe://cluster.local/ns/ate-system/sa/atenet-router", "SPIFFE identity allowed to call actor ingress HTTPS") + atunnelEgressListenAddress = pflag.String("atunnel-egress-listen-address", "0.0.0.0:15001", "Address for transparently intercepted actor egress TCP") + atunnelEgressTrustBundle = pflag.String("atunnel-egress-trust-bundle", "/run/servicedns.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle for the egress gateway") + showVersion = pflag.Bool("version", false, "Print version and exit.") reapLock sync.RWMutex ) +// actorHTTPUpstream is the in-sandbox HTTP endpoint atunnel proxies actor +// ingress to. +const actorHTTPUpstream = "http://" + ateomnet.ActorVethIP + ":80" + func main() { pflag.Parse() if *showVersion { @@ -137,7 +151,16 @@ func do(ctx context.Context) error { } actorLogger := actorlog.NewActorLogger(syncedWriter, metadata.OnGCE()) - ateomService := NewService(interiorNetNS, actorLogger) + upstream, err := url.Parse(actorHTTPUpstream) + if err != nil { + return fmt.Errorf("while parsing atunnel upstream: %w", err) + } + atunnelServer, atunnelEgress, atunnelEgressPort, err := runAtunnel(ctx, upstream) + if err != nil { + return err + } + + ateomService := NewService(interiorNetNS, actorLogger, atunnelServer, atunnelEgress, atunnelEgressPort, *atunnelCredentialBundle, *atunnelEgressTrustBundle) svr := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler()), @@ -154,6 +177,50 @@ func do(ctx context.Context) error { return nil } +func runAtunnel(ctx context.Context, upstream *url.URL) (*atunnel.Server, *atunnel.Egress, uint16, error) { + atunnelServer, err := atunnel.NewServer(atunnel.Config{ + CredentialBundlePath: *atunnelCredentialBundle, + TrustBundlePath: *atunnelTrustBundle, + AllowedClientID: *atunnelClientIdentity, + Upstream: upstream, + }) + if err != nil { + return nil, nil, 0, fmt.Errorf("while configuring atunnel: %w", err) + } + atunnelListener, err := net.Listen("tcp", *atunnelListenAddress) + if err != nil { + return nil, nil, 0, fmt.Errorf("while opening atunnel listener: %w", err) + } + go func() { + if err := atunnelServer.Serve(ctx, atunnelListener); err != nil { + serverboot.Fatal(ctx, "Failed to serve actor ingress", err) + } + }() + slog.InfoContext(ctx, "atunnel serving", slog.String("address", *atunnelListenAddress)) + + atunnelEgress, err := atunnel.NewEgress(atunnel.TCPOriginalDestination) + if err != nil { + return nil, nil, 0, fmt.Errorf("while configuring atunnel egress: %w", err) + } + egressListener, err := net.Listen("tcp", *atunnelEgressListenAddress) + if err != nil { + return nil, nil, 0, fmt.Errorf("while opening atunnel egress listener: %w", err) + } + egressTCPAddr, ok := egressListener.Addr().(*net.TCPAddr) + if !ok || egressTCPAddr.Port < 1 || egressTCPAddr.Port > 65535 { + _ = egressListener.Close() + return nil, nil, 0, fmt.Errorf("atunnel egress listener has invalid address %q", egressListener.Addr()) + } + atunnelEgressPort := uint16(egressTCPAddr.Port) + go func() { + if err := atunnelEgress.Serve(ctx, egressListener); err != nil { + serverboot.Fatal(ctx, "Failed to serve actor egress", err) + } + }() + slog.InfoContext(ctx, "atunnel egress serving", slog.String("address", *atunnelEgressListenAddress)) + return atunnelServer, atunnelEgress, atunnelEgressPort, nil +} + // AteomService is a service for shepherding single microvm. type AteomService struct { ateompb.UnimplementedAteomServer @@ -164,22 +231,36 @@ type AteomService struct { interiorNetNS netns.NsHandle actorLogger *actorlog.ActorLogger + atunnel *atunnel.Server + atunnelEgress *atunnel.Egress + // atunnelEgressPort is zero when tunneled egress is disabled. Otherwise, + // actor TCP connections are transparently redirected to this local port. + atunnelEgressPort uint16 + atunnelCredentialBundle string + atunnelEgressTrustBundle string } var _ ateompb.AteomServer = (*AteomService)(nil) // NewService creates a new AteomService. -func NewService(interiorNetNS netns.NsHandle, actorLogger *actorlog.ActorLogger) *AteomService { - svc := &AteomService{ - interiorNetNS: interiorNetNS, - actorLogger: actorLogger, +func NewService(interiorNetNS netns.NsHandle, actorLogger *actorlog.ActorLogger, atunnelServer *atunnel.Server, atunnelEgress *atunnel.Egress, atunnelEgressPort uint16, credentialBundle, egressTrustBundle string) *AteomService { + return &AteomService{ + interiorNetNS: interiorNetNS, + actorLogger: actorLogger, + atunnel: atunnelServer, + atunnelEgress: atunnelEgress, + atunnelEgressPort: atunnelEgressPort, + atunnelCredentialBundle: credentialBundle, + atunnelEgressTrustBundle: egressTrustBundle, } - return svc } func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkloadRequest) (resp *ateompb.RunWorkloadResponse, retErr error) { s.lock.Lock() defer s.lock.Unlock() + if err := s.deactivateActorNetworking(ctx); err != nil { + return nil, err + } actorRef := resources.ActorRef{Atespace: req.GetAtespace(), Name: req.GetActorName()} s.actorLogger.EmitLifecycleLog("Actor starting", actorRef, req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) @@ -189,7 +270,11 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload // * Correct runsc version is downloaded and placed on disk. // * All OCI bundles are set up, including for "pause" container. - if err := ateomnet.SetupActorNetwork(ctx, ateomnet.NetworkConfig{InteriorNetNS: s.interiorNetNS, DumpNetInfo: true}); err != nil { + if err := ateomnet.SetupActorNetwork(ctx, ateomnet.NetworkConfig{ + InteriorNetNS: s.interiorNetNS, + DumpNetInfo: true, + EgressRedirectPort: s.egressRedirectPort(req.GetEgressGatewayAddress() != ""), + }); err != nil { return nil, fmt.Errorf("while setting up actor network: %w", err) } defer func() { @@ -251,6 +336,9 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload if err := readyz.WaitAll(ctx, req.GetSpec().GetContainers(), ateomnet.ActorVethIP); err != nil { return nil, fmt.Errorf("while waiting for container readyz: %w", err) } + if err := s.activateActorNetworking(req.GetAtespace(), req.GetActorName(), req.GetActorVersion(), req.GetEgressGatewayAddress()); err != nil { + return nil, err + } s.actorLogger.EmitLifecycleLog("Actor started", actorRef, req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) @@ -260,6 +348,9 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.CheckpointWorkloadRequest) (*ateompb.CheckpointWorkloadResponse, error) { s.lock.Lock() defer s.lock.Unlock() + if err := s.deactivateActorNetworking(ctx); err != nil { + return nil, err + } actorRef := resources.ActorRef{Atespace: req.GetAtespace(), Name: req.GetActorName()} s.actorLogger.EmitLifecycleLog("Actor checkpointing", actorRef, req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) @@ -385,6 +476,9 @@ func (r *runsc) cleanupContainersAfterCheckpoint(ctx context.Context, containers func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.RestoreWorkloadRequest) (resp *ateompb.RestoreWorkloadResponse, retErr error) { s.lock.Lock() defer s.lock.Unlock() + if err := s.deactivateActorNetworking(ctx); err != nil { + return nil, err + } actorRef := resources.ActorRef{Atespace: req.GetAtespace(), Name: req.GetActorName()} s.actorLogger.EmitLifecycleLog("Actor restoring", actorRef, req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) @@ -395,7 +489,11 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore // * All OCI bundles are set up, including for "pause" container. // * Checkpoint downloaded and placed on disk - if err := ateomnet.SetupActorNetwork(ctx, ateomnet.NetworkConfig{InteriorNetNS: s.interiorNetNS, DumpNetInfo: true}); err != nil { + if err := ateomnet.SetupActorNetwork(ctx, ateomnet.NetworkConfig{ + InteriorNetNS: s.interiorNetNS, + DumpNetInfo: true, + EgressRedirectPort: s.egressRedirectPort(req.GetEgressGatewayAddress() != ""), + }); err != nil { return nil, fmt.Errorf("while setting up actor network: %w", err) } defer func() { @@ -481,12 +579,74 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore if err := readyz.WaitAll(ctx, req.GetSpec().GetContainers(), ateomnet.ActorVethIP); err != nil { return nil, fmt.Errorf("while waiting for container readyz: %w", err) } + if err := s.activateActorNetworking(req.GetAtespace(), req.GetActorName(), req.GetActorVersion(), req.GetEgressGatewayAddress()); err != nil { + return nil, err + } s.actorLogger.EmitLifecycleLog("Actor restored", actorRef, req.GetActorUid(), req.GetActorTemplateNamespace(), req.GetActorTemplateName()) return &ateompb.RestoreWorkloadResponse{}, nil } +func (s *AteomService) activateActorNetworking(atespace, actorName string, actorVersion int64, egressGatewayAddress string) error { + var egressClient atunnel.EgressDialer + if s.atunnelEgress != nil && egressGatewayAddress != "" { + serverName, _, err := net.SplitHostPort(egressGatewayAddress) + if err != nil { + return fmt.Errorf("invalid egress gateway address %q: %w", egressGatewayAddress, err) + } + egressClient, err = atunnel.NewClient(atunnel.ClientConfig{ + GatewayAddress: egressGatewayAddress, + ServerName: serverName, + CredentialBundlePath: s.atunnelCredentialBundle, + TrustBundlePath: s.atunnelEgressTrustBundle, + }) + if err != nil { + return fmt.Errorf("while configuring actor egress client: %w", err) + } + } + if s.atunnel != nil { + if err := s.atunnel.Activate(atespace, actorName); err != nil { + return fmt.Errorf("while activating actor ingress: %w", err) + } + } + if egressClient != nil { + if err := s.atunnelEgress.Activate(egressClient, atespace, actorName, actorVersion, ""); err != nil { + if s.atunnel != nil { + _ = s.atunnel.Deactivate(context.Background()) + } + return fmt.Errorf("while activating actor egress: %w", err) + } + } + return nil +} + +func (s *AteomService) deactivateActorNetworking(ctx context.Context) error { + // Stop admitting traffic and drain active streams before the Actor network + // is torn down. Attempt both directions even if one fails to deactivate. + var err error + if s.atunnel != nil { + err = errors.Join(err, s.atunnel.Deactivate(ctx)) + } + if s.atunnelEgress != nil { + err = errors.Join(err, s.atunnelEgress.Deactivate(ctx)) + } + if err != nil { + return fmt.Errorf("while deactivating actor networking: %w", err) + } + return nil +} + +// egressRedirectPort returns the local atunnel egress listener port when the +// activation arms tunneled egress, and zero otherwise, which leaves the +// prerouting redirect uninstalled and actor egress on the masquerade path. +func (s *AteomService) egressRedirectPort(redirectEgress bool) uint16 { + if !redirectEgress { + return 0 + } + return s.atunnelEgressPort +} + // setupCgroupDelegation prepares the worker pod's cgroup so runsc can create a // per-actor-container leaf under it with real cpu/memory/pids accounting. // diff --git a/cmd/ateom-microvm/checkpoint.go b/cmd/ateom-microvm/checkpoint.go index 226673866..d53aeb57c 100644 --- a/cmd/ateom-microvm/checkpoint.go +++ b/cmd/ateom-microvm/checkpoint.go @@ -61,6 +61,9 @@ import ( func (s *AteomService) CheckpointWorkload(ctx context.Context, req *ateompb.CheckpointWorkloadRequest) (*ateompb.CheckpointWorkloadResponse, error) { s.lock.Lock() defer s.lock.Unlock() + if err := s.deactivateActorNetworking(ctx); err != nil { + return nil, err + } actorRef := resources.ActorRef{Atespace: req.GetAtespace(), Name: req.GetActorName()} actorUID := req.GetActorUid() diff --git a/cmd/ateom-microvm/main.go b/cmd/ateom-microvm/main.go index 73fa9d1a7..55fe84ff6 100644 --- a/cmd/ateom-microvm/main.go +++ b/cmd/ateom-microvm/main.go @@ -24,10 +24,12 @@ package main import ( "context" + "errors" "flag" "fmt" "log/slog" "net" + "net/url" "os" "strings" "sync" @@ -38,6 +40,7 @@ import ( "github.com/agent-substrate/substrate/internal/ateinterceptors" "github.com/agent-substrate/substrate/internal/ateomnet" "github.com/agent-substrate/substrate/internal/ateompath" + "github.com/agent-substrate/substrate/internal/atunnel" "github.com/agent-substrate/substrate/internal/proto/ateompb" "github.com/agent-substrate/substrate/internal/serverboot" "github.com/agent-substrate/substrate/internal/version" @@ -55,6 +58,13 @@ var ( kataConfig = flag.String("kata-config", "", "Path to a kata configuration.toml (passed to the shim as KATA_CONF_FILE). Empty uses kata's default. atelet generates one pointing at runtime-fetched assets.") kataDebug = flag.Bool("kata-debug", false, "Verbose kata-agent debugging: raise the guest agent log level and forward the guest console (incl. agent logs) into the pod logs.") showVersion = flag.Bool("version", false, "Print version and exit.") + + atunnelListenAddress = flag.String("atunnel-listen-address", "0.0.0.0:443", "Address for actor ingress HTTPS") + atunnelCredentialBundle = flag.String("atunnel-credential-bundle", "/run/podidentity.podcert.ate.dev/credential-bundle.pem", "PEM credential bundle for actor ingress HTTPS") + atunnelTrustBundle = flag.String("atunnel-trust-bundle", "/run/podidentity.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle for actor ingress clients") + atunnelClientIdentity = flag.String("atunnel-client-identity", "spiffe://cluster.local/ns/ate-system/sa/atenet-router", "SPIFFE identity allowed to call actor ingress HTTPS") + atunnelEgressListenAddress = flag.String("atunnel-egress-listen-address", "0.0.0.0:15001", "Address for transparently intercepted actor egress TCP") + atunnelEgressTrustBundle = flag.String("atunnel-egress-trust-bundle", "/run/servicedns.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle for the egress gateway") ) func main() { @@ -146,12 +156,55 @@ func do(ctx context.Context) error { // logWriter with the runtime logger so the two streams to os.Stdout are // serialized through one SyncedWriter and never interleave-corrupt lines. actorLogger := actorlog.NewActorLogger(logWriter, metadata.OnGCE()) + upstream, err := url.Parse("http://169.254.17.2:80") + if err != nil { + return fmt.Errorf("while parsing atunnel upstream: %w", err) + } + atunnelServer, err := atunnel.NewServer(atunnel.Config{ + CredentialBundlePath: *atunnelCredentialBundle, + TrustBundlePath: *atunnelTrustBundle, + AllowedClientID: *atunnelClientIdentity, + Upstream: upstream, + }) + if err != nil { + return fmt.Errorf("while configuring atunnel: %w", err) + } + atunnelListener, err := net.Listen("tcp", *atunnelListenAddress) + if err != nil { + return fmt.Errorf("while opening atunnel listener: %w", err) + } + go func() { + if err := atunnelServer.Serve(ctx, atunnelListener); err != nil { + serverboot.Fatal(ctx, "Failed to serve actor ingress", err) + } + }() + slog.InfoContext(ctx, "atunnel serving", slog.String("address", *atunnelListenAddress)) + atunnelEgress, err := atunnel.NewEgress(atunnel.TCPOriginalDestination) + if err != nil { + return fmt.Errorf("while configuring atunnel egress: %w", err) + } + egressListener, err := net.Listen("tcp", *atunnelEgressListenAddress) + if err != nil { + return fmt.Errorf("while opening atunnel egress listener: %w", err) + } + egressTCPAddr, ok := egressListener.Addr().(*net.TCPAddr) + if !ok || egressTCPAddr.Port < 1 || egressTCPAddr.Port > 65535 { + _ = egressListener.Close() + return fmt.Errorf("atunnel egress listener has invalid address %q", egressListener.Addr()) + } + atunnelEgressPort := uint16(egressTCPAddr.Port) + go func() { + if err := atunnelEgress.Serve(ctx, egressListener); err != nil { + serverboot.Fatal(ctx, "Failed to serve actor egress", err) + } + }() + slog.InfoContext(ctx, "atunnel egress serving", slog.String("address", *atunnelEgressListenAddress)) svr := grpc.NewServer( grpc.StatsHandler(otelgrpc.NewServerHandler()), grpc.UnaryInterceptor(ateinterceptors.InternalServerUnaryInterceptor), ) - ateompb.RegisterAteomServer(svr, NewService(*podUID, *chBinary, *kataConfig, *kataDebug, interiorNetNS, actorLogger)) + ateompb.RegisterAteomServer(svr, NewService(*podUID, *chBinary, *kataConfig, *kataDebug, interiorNetNS, actorLogger, atunnelServer, atunnelEgress, atunnelEgressPort, *atunnelCredentialBundle, *atunnelEgressTrustBundle)) reflection.Register(svr) slog.InfoContext(ctx, "ateom-microvm serving", slog.String("socket", sockPath)) @@ -209,7 +262,14 @@ type AteomService struct { // actorLogger forwards the actor container's stdout/stderr to the worker pod's // stdout as ate.dev/*-labeled JSON and emits actor lifecycle events (parity // with ateom-gvisor). - actorLogger *actorlog.ActorLogger + actorLogger *actorlog.ActorLogger + atunnel *atunnel.Server + atunnelEgress *atunnel.Egress + // atunnelEgressPort is zero when tunneled egress is disabled. Otherwise, + // actor TCP connections are transparently redirected to this local port. + atunnelEgressPort uint16 + atunnelCredentialBundle string + atunnelEgressTrustBundle string // running maps actor UID -> the live micro-VM, kept so CheckpointWorkload can // pause+snapshot+teardown the same sandbox (and RestoreWorkload can track the @@ -220,14 +280,78 @@ type AteomService struct { var _ ateompb.AteomServer = (*AteomService)(nil) // NewService creates a new AteomService. -func NewService(podUID, chBinary, kataConfig string, kataDebug bool, interiorNetNS netns.NsHandle, actorLogger *actorlog.ActorLogger) *AteomService { +func NewService(podUID, chBinary, kataConfig string, kataDebug bool, interiorNetNS netns.NsHandle, actorLogger *actorlog.ActorLogger, atunnelServer *atunnel.Server, atunnelEgress *atunnel.Egress, atunnelEgressPort uint16, credentialBundle, egressTrustBundle string) *AteomService { return &AteomService{ - podUID: podUID, - chBinary: chBinary, - kataConfig: kataConfig, - kataDebug: kataDebug, - interiorNetNS: interiorNetNS, - actorLogger: actorLogger, - running: map[string]*runningActor{}, + podUID: podUID, + chBinary: chBinary, + kataConfig: kataConfig, + kataDebug: kataDebug, + interiorNetNS: interiorNetNS, + actorLogger: actorLogger, + atunnel: atunnelServer, + atunnelEgress: atunnelEgress, + atunnelEgressPort: atunnelEgressPort, + atunnelCredentialBundle: credentialBundle, + atunnelEgressTrustBundle: egressTrustBundle, + running: map[string]*runningActor{}, + } +} + +func (s *AteomService) activateActorNetworking(atespace, actorName string, actorVersion int64, egressGatewayAddress string) error { + var egressClient atunnel.EgressDialer + if s.atunnelEgress != nil && egressGatewayAddress != "" { + serverName, _, err := net.SplitHostPort(egressGatewayAddress) + if err != nil { + return fmt.Errorf("invalid egress gateway address %q: %w", egressGatewayAddress, err) + } + egressClient, err = atunnel.NewClient(atunnel.ClientConfig{ + GatewayAddress: egressGatewayAddress, + ServerName: serverName, + CredentialBundlePath: s.atunnelCredentialBundle, + TrustBundlePath: s.atunnelEgressTrustBundle, + }) + if err != nil { + return fmt.Errorf("while configuring actor egress client: %w", err) + } + } + if s.atunnel != nil { + if err := s.atunnel.Activate(atespace, actorName); err != nil { + return fmt.Errorf("while activating actor ingress: %w", err) + } + } + if egressClient != nil { + if err := s.atunnelEgress.Activate(egressClient, atespace, actorName, actorVersion, ""); err != nil { + if s.atunnel != nil { + _ = s.atunnel.Deactivate(context.Background()) + } + return fmt.Errorf("while activating actor egress: %w", err) + } + } + return nil +} + +func (s *AteomService) deactivateActorNetworking(ctx context.Context) error { + // Stop admitting traffic and drain active streams before the Actor network + // is torn down. Attempt both directions even if one fails to deactivate. + var err error + if s.atunnel != nil { + err = errors.Join(err, s.atunnel.Deactivate(ctx)) + } + if s.atunnelEgress != nil { + err = errors.Join(err, s.atunnelEgress.Deactivate(ctx)) + } + if err != nil { + return fmt.Errorf("while deactivating actor networking: %w", err) + } + return nil +} + +// egressRedirectPort returns the local atunnel egress listener port when the +// activation arms tunneled egress, and zero otherwise, which leaves the +// prerouting redirect uninstalled and actor egress on the masquerade path. +func (s *AteomService) egressRedirectPort(redirectEgress bool) uint16 { + if !redirectEgress { + return 0 } + return s.atunnelEgressPort } diff --git a/cmd/ateom-microvm/restore.go b/cmd/ateom-microvm/restore.go index 5d9e51cee..7ac838968 100644 --- a/cmd/ateom-microvm/restore.go +++ b/cmd/ateom-microvm/restore.go @@ -54,6 +54,10 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore s.lock.Lock() defer s.lock.Unlock() + if err := s.deactivateActorNetworking(ctx); err != nil { + return nil, err + } + p := actorBootParams{ actorRef: resources.ActorRef{Atespace: req.GetAtespace(), Name: req.GetActorName()}, actorUID: req.GetActorUid(), @@ -61,6 +65,9 @@ func (s *AteomService) RestoreWorkload(ctx context.Context, req *ateompb.Restore templateName: req.GetActorTemplateName(), containers: req.GetSpec().GetContainers(), assetPaths: req.GetRuntimeAssetPaths(), + + actorVersion: req.GetActorVersion(), + egressGatewayAddress: req.GetEgressGatewayAddress(), } restoreDir := ateompath.RestoreStateDir(p.actorUID) durableDir := ateompath.DurableDirVolumeMountsDir(p.actorUID) @@ -187,6 +194,7 @@ func (s *AteomService) restoreFullScope(ctx context.Context, p actorBootParams, InteriorNetNS: s.interiorNetNS, HostVethHWAddr: hostVethHWAddr, SweepInteriorLinks: true, + EgressRedirectPort: s.egressRedirectPort(p.egressGatewayAddress != ""), }); err != nil { return fmt.Errorf("while setting up actor network: %w", err) } @@ -281,6 +289,9 @@ func (s *AteomService) restoreFullScope(ctx context.Context, p actorBootParams, } } + if err := s.activateActorNetworking(p.actorRef.Atespace, p.actorRef.Name, p.actorVersion, p.egressGatewayAddress); err != nil { + return err + } s.running[actorUID] = ra slog.InfoContext(ctx, "Actor restored (overlay rootfs)", slog.String("id", actorUID), slog.Duration("total", time.Since(tStart))) diff --git a/cmd/ateom-microvm/run.go b/cmd/ateom-microvm/run.go index a47eba3c3..5179d6c50 100644 --- a/cmd/ateom-microvm/run.go +++ b/cmd/ateom-microvm/run.go @@ -199,6 +199,9 @@ func writeGuestResolvConf(rootfs string) error { func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkloadRequest) (*ateompb.RunWorkloadResponse, error) { s.lock.Lock() defer s.lock.Unlock() + if err := s.deactivateActorNetworking(ctx); err != nil { + return nil, err + } p := actorBootParams{ actorRef: resources.ActorRef{Atespace: req.GetAtespace(), Name: req.GetActorName()}, @@ -207,6 +210,9 @@ func (s *AteomService) RunWorkload(ctx context.Context, req *ateompb.RunWorkload templateName: req.GetActorTemplateName(), containers: req.GetSpec().GetContainers(), assetPaths: req.GetRuntimeAssetPaths(), + + actorVersion: req.GetActorVersion(), + egressGatewayAddress: req.GetEgressGatewayAddress(), } s.actorLogger.EmitLifecycleLog("Actor starting", p.actorRef, p.actorUID, p.templateNS, p.templateName) @@ -228,6 +234,12 @@ type actorBootParams struct { templateName string containers []*ateompb.Container assetPaths map[string]string + // actorVersion is the Actor resource version ate-api observed when it + // assigned this worker; atunnel asserts it to the egress gateway. + actorVersion int64 + // egressGatewayAddress is empty unless an egress gateway is configured, in + // which case actor TCP egress is redirected to atunnel's local listener. + egressGatewayAddress string } // coldBootActor boots the actor's micro-VM from scratch and starts its @@ -264,6 +276,7 @@ func (s *AteomService) coldBootActor(ctx context.Context, p actorBootParams) (re InteriorNetNS: s.interiorNetNS, HostVethHWAddr: hostVethHWAddr, SweepInteriorLinks: true, + EgressRedirectPort: s.egressRedirectPort(p.egressGatewayAddress != ""), }); err != nil { return fmt.Errorf("while setting up actor network: %w", err) } @@ -421,6 +434,9 @@ func (s *AteomService) coldBootActor(ctx context.Context, p actorBootParams) (re } ra := &runningActor{chCmd: chCmd, vfsdCmd: vfsdCmd, durableVfsdCmd: durableVfsdCmd, apiSocket: apiSocket, baseID: actorUID, logAgent: ac} + if err := s.activateActorNetworking(p.actorRef.Atespace, p.actorRef.Name, p.actorVersion, p.egressGatewayAddress); err != nil { + return err + } s.running[actorUID] = ra // Forward each container's stdout/stderr into the pod logs. The overlay workload's diff --git a/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner.go b/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner.go index 815d38b6d..3ea8c6688 100644 --- a/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner.go +++ b/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner.go @@ -30,6 +30,7 @@ import ( "github.com/agent-substrate/substrate/internal/localca" "github.com/agent-substrate/substrate/internal/substratex509" certsv1beta1 "k8s.io/api/certificates/v1beta1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/utils/clock" @@ -47,9 +48,18 @@ const ( ateletServiceAccount = "atelet" ) -func extKeyUsages(namespace, serviceAccount string) []x509.ExtKeyUsage { +// workerPoolLabel marks pods created by atecontroller for a WorkerPool. Worker +// pods host the atunnel ingress server, presenting this cert as a TLS server +// cert to atenet-router, so they need the serverAuth EKU too. They run as the +// actor namespace's default ServiceAccount, so the label is what distinguishes +// them rather than their identity. +const workerPoolLabel = "ate.dev/worker-pool" + +func extKeyUsages(pod *corev1.Pod, namespace, serviceAccount string) []x509.ExtKeyUsage { usages := []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} - if namespace == ateletNamespace && serviceAccount == ateletServiceAccount { + _, isWorker := pod.ObjectMeta.Labels[workerPoolLabel] + isAtelet := namespace == ateletNamespace && serviceAccount == ateletServiceAccount + if isAtelet || isWorker { usages = append(usages, x509.ExtKeyUsageServerAuth) } return usages @@ -146,7 +156,7 @@ func (h *Impl) MakeCert(ctx context.Context, pcr *certsv1beta1.PodCertificateReq NotAfter: notAfter, URIs: []*url.URL{spiffeURI}, KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: extKeyUsages(pcr.ObjectMeta.Namespace, pcr.Spec.ServiceAccountName), + ExtKeyUsage: extKeyUsages(pod, pcr.ObjectMeta.Namespace, pcr.Spec.ServiceAccountName), // Link the leaf to its issuing CA by key id so verifiers can disambiguate // a multi-CA trust bundle (e.g. valkey trusts both the servicedns and // podidentity CAs). diff --git a/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner_test.go b/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner_test.go index 6afd1d50c..f3bd18d1f 100644 --- a/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner_test.go +++ b/cmd/podcertcontroller/internal/podidentitysigner/podidentitysigner_test.go @@ -97,6 +97,7 @@ func TestMakeCert(t *testing.T) { namespace string podName string serviceAccount string + podLabels map[string]string maxExpirationSeconds int32 wantLifetime time.Duration wantURI string @@ -122,6 +123,28 @@ func TestMakeCert(t *testing.T) { NodeUID: "node-uid-1", }, }, + { + // Worker pods host the atunnel ingress server, so they serve TLS + // despite running as the actor namespace's default ServiceAccount. + name: "worker pod also serves", + namespace: "ate-demo-counter", + podName: "counter-abcde", + serviceAccount: "default", + podLabels: map[string]string{"ate.dev/worker-pool": "counter"}, + maxExpirationSeconds: 86400, + wantLifetime: 24 * time.Hour, + wantURI: "spiffe://cluster.local/ns/ate-demo-counter/sa/default", + wantEKUs: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + wantIdentity: &substratex509.PodIdentity{ + Namespace: "ate-demo-counter", + ServiceAccountName: "default", + ServiceAccountUID: "sa-uid-1", + PodName: "counter-abcde", + PodUID: "pod-uid-1", + NodeName: "node-1", + NodeUID: "node-uid-1", + }, + }, { name: "ordinary workload is client-only", namespace: "default", @@ -197,6 +220,7 @@ func TestMakeCert(t *testing.T) { } pod, pcr := makePodAndPCR(tc.namespace, tc.podName, tc.serviceAccount, tc.maxExpirationSeconds) + pod.ObjectMeta.Labels = tc.podLabels pcr.Spec.StubPKCS10Request = stubCSR(t, subjectPriv) kc := fake.NewSimpleClientset(pod, pcr) diff --git a/internal/ateomnet/net.go b/internal/ateomnet/net.go index 1be4c1bbd..cd6755c2c 100644 --- a/internal/ateomnet/net.go +++ b/internal/ateomnet/net.go @@ -232,27 +232,27 @@ func EnableIPv4Forwarding() error { return nil } -// InstallActorNftablesRules configures the NAT and filtering rules for the actor. -func InstallActorNftablesRules(podIP net.IP) error { +// InstallActorNftablesRules configures the NAT and filtering rules for the +// actor. egressPort, when non-zero, is the local atunnel egress listener actor +// TCP egress is redirected to; zero leaves the redirect uninstalled. +func InstallActorNftablesRules(egressPort uint16) error { // Install a dedicated nftables table for the active actor. Keeping all // rules in an ateom-owned table makes cleanup simple and avoids mutating // Kubernetes or CNI-managed chains directly. // // TODO: Add IPv6 veth addressing, forwarding, and nftables rules once actor - // networking supports dual-stack pods. The current compatibility path is - // IPv4-only. + // networking supports dual-stack pods. The current actor network is IPv4-only. // - // The temporary compatibility rules do three things: + // The rules do three things: // - // * postrouting: masquerade actor egress from 169.254.17.2 behind the worker - // pod IP so replies route back to the pod. - // * prerouting: DNAT traffic sent to the worker pod IP on TCP/80 to the - // actor veth IP on TCP/80, preserving existing inbound behavior. + // * prerouting: redirect new actor TCP connections to atunnel's local + // listener. REDIRECT preserves SO_ORIGINAL_DST for the CONNECT authority. + // * postrouting: masquerade traffic not handled by the TCP tunnel, notably + // DNS over UDP, so hostname resolution continues to work. // * forward: accept forwarded packets between the actor veth and pod eth0. // - // This is not the final egress policy path. The later AgentGateway phase - // should replace the broad masquerade path with transparent TCP capture and - // default-deny rules. + // TODO: Restrict the compatibility masquerade to DNS traffic sent to the + // configured cluster resolver and drop all other non-tunneled actor egress. if err := RemoveActorNftablesRules(); err != nil { return err } @@ -271,32 +271,9 @@ func InstallActorNftablesRules(podIP net.IP) error { Hooknum: nftables.ChainHookPrerouting, Priority: nftables.ChainPriorityNATDest, }) - // TODO: Support inbound UDP DNAT for actors that expose UDP protocols such - // as QUIC. - // TODO: Replace the hard-coded HTTP port with the actor's configured - // inbound ports, either by adding one rule per port or by matching a set. - preroutingExprs := append(IPDestinationEqual(podIP.String()), TCPDestinationPortEqual(80)...) - preroutingExprs = append(preroutingExprs, - &expr.Immediate{ - Register: 1, - Data: net.ParseIP(ActorVethIP).To4(), - }, - &expr.Immediate{ - Register: 2, - Data: binaryutil.BigEndian.PutUint16(80), - }, - &expr.NAT{ - Type: expr.NATTypeDestNAT, - Family: unix.NFPROTO_IPV4, - RegAddrMin: 1, - RegProtoMin: 2, - }, - ) - c.AddRule(&nftables.Rule{ - Table: table, - Chain: prerouting, - Exprs: preroutingExprs, - }) + if redirectRule := ActorEgressRedirectRule(table, prerouting, egressPort); redirectRule != nil { + c.AddRule(redirectRule) + } postrouting := c.AddChain(&nftables.Chain{ Name: "postrouting", @@ -361,10 +338,6 @@ func IPSourceEqual(ip string) []expr.Any { return IPPayloadEqual(12, ip) } -func IPDestinationEqual(ip string) []expr.Any { - return IPPayloadEqual(16, ip) -} - func IPPayloadEqual(offset uint32, ip string) []expr.Any { return []expr.Any{ &expr.Payload{ @@ -381,7 +354,7 @@ func IPPayloadEqual(offset uint32, ip string) []expr.Any { } } -func TCPDestinationPortEqual(port uint16) []expr.Any { +func TCPProtocol() []expr.Any { return []expr.Any{ &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}, &expr.Cmp{ @@ -389,18 +362,25 @@ func TCPDestinationPortEqual(port uint16) []expr.Any { Register: 1, Data: []byte{unix.IPPROTO_TCP}, }, - &expr.Payload{ - DestRegister: 1, - Base: expr.PayloadBaseTransportHeader, - Offset: 2, - Len: 2, - }, - &expr.Cmp{ - Op: expr.CmpOpEq, + } +} + +// ActorEgressRedirectRule returns the prerouting rule that redirects actor TCP +// egress to the local atunnel egress listener on port, or nil when port is zero +// (tunneled egress disabled, so actor egress stays on the masquerade path). +func ActorEgressRedirectRule(table *nftables.Table, chain *nftables.Chain, port uint16) *nftables.Rule { + if port == 0 { + return nil + } + exprs := append(IPSourceEqual(ActorVethIP), TCPProtocol()...) + exprs = append(exprs, + &expr.Immediate{ Register: 1, Data: binaryutil.BigEndian.PutUint16(port), }, - } + &expr.Redir{RegisterProtoMin: 1}, + ) + return &nftables.Rule{Table: table, Chain: chain, Exprs: exprs} } // CreateNetNSWithoutSwitching creates a named netns and returns its handle, @@ -499,6 +479,12 @@ type NetworkConfig struct { // DumpNetInfo indicates whether to dump network information to the logs for debugging purposes. // Used by: gVisor. DumpNetInfo bool + + // EgressRedirectPort is the local atunnel egress listener port actor TCP + // egress is redirected to. Zero installs no redirect, leaving actor egress + // on the masquerade path. + // Used by: Both gVisor and MicroVM. + EgressRedirectPort uint16 } // SetupActorNetwork builds a fresh point-to-point network between the worker @@ -511,10 +497,9 @@ func SetupActorNetwork(ctx context.Context, cfg NetworkConfig) (retErr error) { // the worker-side veth address. This replaces the old behavior of moving the // Kubernetes-provided eth0 out of the worker pod. // - // The nftables rules installed here are a compatibility bridge for the - // current router assumptions: actor egress is masqueraded behind the worker - // pod IP, and inbound traffic to the worker pod's HTTP port is DNAT'd to the - // actor veth IP. + // The nftables rules installed here redirect actor TCP egress to atunnel + // when configured and masquerade traffic the TCP tunnel does not handle + // (notably DNS over UDP). // // Clean up stale state from a failed prior activation before creating the // next actor-side network. The worker currently runs one actor at a time. @@ -529,11 +514,6 @@ func SetupActorNetwork(ctx context.Context, cfg NetworkConfig) (retErr error) { } }() - podIP, err := PodIPv4() - if err != nil { - return fmt.Errorf("while resolving pod IPv4 address: %w", err) - } - if cfg.SweepInteriorLinks { if err := NetNSDo(ctx, cfg.InteriorNetNS, func(ctx context.Context) error { links, err := netlink.LinkList() @@ -594,7 +574,7 @@ func SetupActorNetwork(ctx context.Context, cfg NetworkConfig) (retErr error) { if err := EnableIPv4Forwarding(); err != nil { return err } - if err := InstallActorNftablesRules(podIP); err != nil { + if err := InstallActorNftablesRules(cfg.EgressRedirectPort); err != nil { return err } diff --git a/internal/atunnel/server.go b/internal/atunnel/server.go index b8dca5e8b..05dac1aad 100644 --- a/internal/atunnel/server.go +++ b/internal/atunnel/server.go @@ -231,7 +231,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.reject(w) return } - atespace, actorName, err := resources.ParseActorDNSName(host) + actorRef, err := resources.ParseActorDNSName(host) if err != nil { s.reject(w) return @@ -239,7 +239,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.mu.Lock() active := s.active - if active == nil || active.atespace != atespace || active.actorName != actorName { + if active == nil || active.atespace != actorRef.Atespace || active.actorName != actorRef.Name { s.mu.Unlock() s.reject(w) return diff --git a/internal/proto/ateompb/ateom.pb.go b/internal/proto/ateompb/ateom.pb.go index 12c4fa1a7..b91d4beef 100644 --- a/internal/proto/ateompb/ateom.pb.go +++ b/internal/proto/ateompb/ateom.pb.go @@ -91,21 +91,26 @@ func (SnapshotScope) EnumDescriptor() ([]byte, []int) { } type RunWorkloadRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` - ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` - Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` + ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` + ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + // Actor resource version observed by ate-api when assigning this worker. + ActorVersion int64 `protobuf:"varint,9,opt,name=actor_version,json=actorVersion,proto3" json:"actor_version,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // runtime_asset_paths maps a runtime asset name (e.g. "cloud-hypervisor", // "virtiofsd", "kata-kernel", "kata-image", "kata-config") // to the local on-disk path atelet fetched it to (content-addressed, like // runsc_path). Empty for the gVisor runtime, which uses runsc_path. RuntimeAssetPaths map[string]string `protobuf:"bytes,8,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // Remote egress gateway selected for this activation. When absent, actor + // traffic uses direct egress instead of being redirected through atunnel. + EgressGatewayAddress *string `protobuf:"bytes,10,opt,name=egress_gateway_address,json=egressGatewayAddress,proto3,oneof" json:"egress_gateway_address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RunWorkloadRequest) Reset() { @@ -159,6 +164,13 @@ func (x *RunWorkloadRequest) GetActorUid() string { return "" } +func (x *RunWorkloadRequest) GetActorVersion() int64 { + if x != nil { + return x.ActorVersion + } + return 0 +} + func (x *RunWorkloadRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -194,6 +206,13 @@ func (x *RunWorkloadRequest) GetRuntimeAssetPaths() map[string]string { return nil } +func (x *RunWorkloadRequest) GetEgressGatewayAddress() string { + if x != nil && x.EgressGatewayAddress != nil { + return *x.EgressGatewayAddress + } + return "" +} + // WorkloadSpec parallels Pod, but with far fewer configurable fields. type WorkloadSpec struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -611,23 +630,28 @@ func (x *CheckpointWorkloadResponse) GetSnapshotFiles() []string { } type RestoreWorkloadRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` - ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` - ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` - ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` - ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` - RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` - Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Atespace string `protobuf:"bytes,1,opt,name=atespace,proto3" json:"atespace,omitempty"` + ActorName string `protobuf:"bytes,2,opt,name=actor_name,json=actorName,proto3" json:"actor_name,omitempty"` + ActorUid string `protobuf:"bytes,3,opt,name=actor_uid,json=actorUid,proto3" json:"actor_uid,omitempty"` + // Actor resource version observed by ate-api when assigning this worker. + ActorVersion int64 `protobuf:"varint,11,opt,name=actor_version,json=actorVersion,proto3" json:"actor_version,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + RunscPath string `protobuf:"bytes,6,opt,name=runsc_path,json=runscPath,proto3" json:"runsc_path,omitempty"` + Spec *WorkloadSpec `protobuf:"bytes,7,opt,name=spec,proto3" json:"spec,omitempty"` // The object storage URI prefix of the snapshot to restore. SnapshotUriPrefix string `protobuf:"bytes,8,opt,name=snapshot_uri_prefix,json=snapshotUriPrefix,proto3" json:"snapshot_uri_prefix,omitempty"` // runtime_asset_paths maps a runtime asset name to the local on-disk path // atelet fetched it to (see RunWorkloadRequest). Empty for gVisor. RuntimeAssetPaths map[string]string `protobuf:"bytes,9,rep,name=runtime_asset_paths,json=runtimeAssetPaths,proto3" json:"runtime_asset_paths,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // What content to restore from the snapshot. - Scope SnapshotScope `protobuf:"varint,10,opt,name=scope,proto3,enum=ateom.SnapshotScope" json:"scope,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + Scope SnapshotScope `protobuf:"varint,10,opt,name=scope,proto3,enum=ateom.SnapshotScope" json:"scope,omitempty"` + // Remote egress gateway selected for this activation. When absent, actor + // traffic uses direct egress instead of being redirected through atunnel. + EgressGatewayAddress *string `protobuf:"bytes,12,opt,name=egress_gateway_address,json=egressGatewayAddress,proto3,oneof" json:"egress_gateway_address,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RestoreWorkloadRequest) Reset() { @@ -681,6 +705,13 @@ func (x *RestoreWorkloadRequest) GetActorUid() string { return "" } +func (x *RestoreWorkloadRequest) GetActorVersion() int64 { + if x != nil { + return x.ActorVersion + } + return 0 +} + func (x *RestoreWorkloadRequest) GetActorTemplateNamespace() string { if x != nil { return x.ActorTemplateNamespace @@ -730,6 +761,13 @@ func (x *RestoreWorkloadRequest) GetScope() SnapshotScope { return SnapshotScope_SNAPSHOT_SCOPE_UNSPECIFIED } +func (x *RestoreWorkloadRequest) GetEgressGatewayAddress() string { + if x != nil && x.EgressGatewayAddress != nil { + return *x.EgressGatewayAddress + } + return "" +} + type RestoreWorkloadResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -770,21 +808,25 @@ var File_ateom_proto protoreflect.FileDescriptor const file_ateom_proto_rawDesc = "" + "\n" + - "\vateom.proto\x12\x05ateom\"\xc6\x03\n" + + "\vateom.proto\x12\x05ateom\"\xc1\x04\n" + "\x12RunWorkloadRequest\x12\x1a\n" + "\batespace\x18\x01 \x01(\tR\batespace\x12\x1d\n" + "\n" + "actor_name\x18\x02 \x01(\tR\tactorName\x12\x1b\n" + - "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x128\n" + + "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x12#\n" + + "\ractor_version\x18\t \x01(\x03R\factorVersion\x128\n" + "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12\x1d\n" + "\n" + "runsc_path\x18\x06 \x01(\tR\trunscPath\x12'\n" + "\x04spec\x18\a \x01(\v2\x13.ateom.WorkloadSpecR\x04spec\x12`\n" + - "\x13runtime_asset_paths\x18\b \x03(\v20.ateom.RunWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x1aD\n" + + "\x13runtime_asset_paths\x18\b \x03(\v20.ateom.RunWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x129\n" + + "\x16egress_gateway_address\x18\n" + + " \x01(\tH\x00R\x14egressGatewayAddress\x88\x01\x01\x1aD\n" + "\x16RuntimeAssetPathsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"@\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x19\n" + + "\x17_egress_gateway_address\"@\n" + "\fWorkloadSpec\x120\n" + "\n" + "containers\x18\x01 \x03(\v2\x10.ateom.ContainerR\n" + @@ -817,12 +859,13 @@ const file_ateom_proto_rawDesc = "" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"C\n" + "\x1aCheckpointWorkloadResponse\x12%\n" + - "\x0esnapshot_files\x18\x01 \x03(\tR\rsnapshotFiles\"\xaa\x04\n" + + "\x0esnapshot_files\x18\x01 \x03(\tR\rsnapshotFiles\"\xa5\x05\n" + "\x16RestoreWorkloadRequest\x12\x1a\n" + "\batespace\x18\x01 \x01(\tR\batespace\x12\x1d\n" + "\n" + "actor_name\x18\x02 \x01(\tR\tactorName\x12\x1b\n" + - "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x128\n" + + "\tactor_uid\x18\x03 \x01(\tR\bactorUid\x12#\n" + + "\ractor_version\x18\v \x01(\x03R\factorVersion\x128\n" + "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12\x1d\n" + "\n" + @@ -831,10 +874,12 @@ const file_ateom_proto_rawDesc = "" + "\x13snapshot_uri_prefix\x18\b \x01(\tR\x11snapshotUriPrefix\x12d\n" + "\x13runtime_asset_paths\x18\t \x03(\v24.ateom.RestoreWorkloadRequest.RuntimeAssetPathsEntryR\x11runtimeAssetPaths\x12*\n" + "\x05scope\x18\n" + - " \x01(\x0e2\x14.ateom.SnapshotScopeR\x05scope\x1aD\n" + + " \x01(\x0e2\x14.ateom.SnapshotScopeR\x05scope\x129\n" + + "\x16egress_gateway_address\x18\f \x01(\tH\x00R\x14egressGatewayAddress\x88\x01\x01\x1aD\n" + "\x16RuntimeAssetPathsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x19\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01B\x19\n" + + "\x17_egress_gateway_address\"\x19\n" + "\x17RestoreWorkloadResponse*a\n" + "\rSnapshotScope\x12\x1e\n" + "\x1aSNAPSHOT_SCOPE_UNSPECIFIED\x10\x00\x12\x17\n" + @@ -905,6 +950,8 @@ func file_ateom_proto_init() { if File_ateom_proto != nil { return } + file_ateom_proto_msgTypes[0].OneofWrappers = []any{} + file_ateom_proto_msgTypes[8].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ diff --git a/internal/proto/ateompb/ateom.proto b/internal/proto/ateompb/ateom.proto index a282d81cb..89ca97c27 100644 --- a/internal/proto/ateompb/ateom.proto +++ b/internal/proto/ateompb/ateom.proto @@ -51,6 +51,8 @@ message RunWorkloadRequest { string atespace = 1; string actor_name = 2; string actor_uid = 3; + // Actor resource version observed by ate-api when assigning this worker. + int64 actor_version = 9; string actor_template_namespace = 4; string actor_template_name = 5; @@ -64,6 +66,10 @@ message RunWorkloadRequest { // to the local on-disk path atelet fetched it to (content-addressed, like // runsc_path). Empty for the gVisor runtime, which uses runsc_path. map runtime_asset_paths = 8; + + // Remote egress gateway selected for this activation. When absent, actor + // traffic uses direct egress instead of being redirected through atunnel. + optional string egress_gateway_address = 10; } // WorkloadSpec parallels Pod, but with far fewer configurable fields. @@ -147,6 +153,8 @@ message RestoreWorkloadRequest { string atespace = 1; string actor_name = 2; string actor_uid = 3; + // Actor resource version observed by ate-api when assigning this worker. + int64 actor_version = 11; string actor_template_namespace = 4; string actor_template_name = 5; @@ -164,6 +172,10 @@ message RestoreWorkloadRequest { // What content to restore from the snapshot. SnapshotScope scope = 10; + + // Remote egress gateway selected for this activation. When absent, actor + // traffic uses direct egress instead of being redirected through atunnel. + optional string egress_gateway_address = 12; } message RestoreWorkloadResponse { From 88ff2d097b005392725aa91ae21fc95a9babf53d Mon Sep 17 00:00:00 2001 From: Lior Lieberman Date: Mon, 27 Jul 2026 14:54:20 -0700 Subject: [PATCH 3/5] ingress: route actor ingress through the atunnel mTLS server The router used to rewrite :authority to the actor's worker pod IP and let the dynamic_forward_proxy cluster resolve it, reaching the actor over plaintext pod-IP:80. The worker no longer DNATs pod-IP:80 to the actor, so that path is gone. Instead the ext_proc resolves the actor to its worker's atunnel ingress address (IP:443) and puts it in x-ate-original-dst. An ORIGINAL_DST cluster dials exactly that address, which leaves the request Host as the actor DNS name -- atunnel needs it to authorize the active actor. The header is set with OVERWRITE_IF_EXISTS_OR_ADD so a client-supplied value can never influence the address Envoy dials. The upstream hop is mTLS: the cluster presents the router's podidentity credential bundle as its client cert and validates the atunnel server against the podidentity trust bundle. Validation matches the SPIFFE URI SAN prefix rather than the dialed pod IP, because the atunnel cert carries only a spiffe:// URI SAN and Envoy's default SAN check against an ephemeral pod IP would never match. atunnel in turn only accepts spiffe://cluster.local/ns/ate-system/sa/atenet-router. The dynamic_forward_proxy cluster, HTTP filter and DNS cache config are removed along with the :authority rewrite. --- cmd/atenet/internal/router/cmd.go | 3 + cmd/atenet/internal/router/config.go | 13 +- cmd/atenet/internal/router/extproc.go | 14 +- cmd/atenet/internal/router/extproc_out.go | 16 +- cmd/atenet/internal/router/extproc_test.go | 4 +- cmd/atenet/internal/router/resumer_test.go | 3 - cmd/atenet/internal/router/router.go | 1 + cmd/atenet/internal/router/xds.go | 187 ++++-- cmd/atenet/internal/router/xds_test.go | 60 +- manifests/ate-install/atenet-router.yaml | 15 + .../config/common/key_value/v3/config.pb.go | 131 ---- .../common/key_value/v3/config.pb.validate.go | 179 ----- .../common/key_value/v3/config_vtproto.pb.go | 95 --- .../dynamic_forward_proxy/v3/cluster.pb.go | 327 ---------- .../v3/cluster.pb.validate.go | 424 ------------ .../v3/cluster_vtproto.pb.go | 315 --------- .../dynamic_forward_proxy/v3/dns_cache.pb.go | 417 ------------ .../v3/dns_cache.pb.validate.go | 612 ------------------ .../v3/dns_cache_vtproto.pb.go | 415 ------------ .../v3/dynamic_forward_proxy.pb.go | 383 ----------- .../v3/dynamic_forward_proxy.pb.validate.go | 486 -------------- .../v3/dynamic_forward_proxy_vtproto.pb.go | 364 ----------- .../v3/getaddrinfo_dns_resolver.pb.go | 150 ----- .../getaddrinfo_dns_resolver.pb.validate.go | 198 ------ .../v3/getaddrinfo_dns_resolver_vtproto.pb.go | 91 --- vendor/modules.txt | 5 - 26 files changed, 187 insertions(+), 4721 deletions(-) delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.validate.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config_vtproto.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.validate.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster_vtproto.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.validate.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache_vtproto.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.validate.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy_vtproto.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.validate.go delete mode 100644 vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver_vtproto.pb.go diff --git a/cmd/atenet/internal/router/cmd.go b/cmd/atenet/internal/router/cmd.go index 0857909c9..1601097a4 100644 --- a/cmd/atenet/internal/router/cmd.go +++ b/cmd/atenet/internal/router/cmd.go @@ -54,6 +54,9 @@ func NewRouterCmd() *cobra.Command { cmd.Flags().DurationVar(&cfg.HealthInterval, "health-interval", 1*time.Second, "Interval for checking health of dependent services") cmd.Flags().IntVar(&cfg.HttpsPort, "port-https", 8443, "TCP port for HTTPS workload traffic entering through the Envoy Router") cmd.Flags().StringVar(&cfg.EnvoyCertPath, "envoy-cert-path", "", "Path to the Envoy certificate file.") + cmd.Flags().StringVar(&cfg.UpstreamCredentialBundlePath, "upstream-credential-bundle", "/run/podidentity.podcert.ate.dev/credential-bundle.pem", "PEM credential bundle (cert+key) the router presents as the client cert when dialing the actor's atunnel ingress server over mTLS. Empty disables upstream mTLS (legacy plaintext pod-IP:80).") + cmd.Flags().StringVar(&cfg.UpstreamTrustBundlePath, "upstream-trust-bundle", "/run/podidentity.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle used to validate the actor's atunnel ingress server certificate.") + cmd.Flags().StringVar(&cfg.UpstreamSpiffePrefix, "upstream-spiffe-prefix", "spiffe://cluster.local/", "SPIFFE URI SAN prefix (trust domain) the actor's atunnel server cert must match. Empty falls back to default SAN check against the dialed pod IP (which SPIFFE-only certs never match).") cmd.Flags().StringVar(&cfg.OtlpCollectorAddress, "otlp-collector-address", "", "host:port of the OTLP gRPC collector that Envoy reports tracing spans to (empty disables Envoy tracing)") cmd.Flags().StringVar(&cfg.Auth.AteapiCAFile, "ateapi-ca-file", "", "PEM file with CAs trusted to verify the ateapi server cert. Required.") cmd.Flags().StringVar(&cfg.Auth.AteapiClientCertPath, "ateapi-client-cert", "", "Credential bundle presented as the client certificate when dialing ateapi. Required unless --ateapi-use-token-auth is set, ignored otherwise.") diff --git a/cmd/atenet/internal/router/config.go b/cmd/atenet/internal/router/config.go index 64ada021f..45b56e11d 100644 --- a/cmd/atenet/internal/router/config.go +++ b/cmd/atenet/internal/router/config.go @@ -49,8 +49,17 @@ type routerConfig struct { HealthInterval time.Duration HttpsPort int EnvoyCertPath string - LogLevel string - MetricsAddr string + // UpstreamCredentialBundlePath is the router's podidentity credential bundle + // (cert+key) presented as the client cert when dialing the actor's atunnel + // ingress server over mTLS. UpstreamTrustBundlePath is the CA bundle used to + // validate that server. Empty UpstreamCredentialBundlePath disables upstream mTLS. + UpstreamCredentialBundlePath string + UpstreamTrustBundlePath string + // UpstreamSpiffePrefix validates the actor's atunnel server cert by its + // SPIFFE URI SAN prefix (trust domain) instead of the dialed pod IP. + UpstreamSpiffePrefix string + LogLevel string + MetricsAddr string // OtlpCollectorAddress is the host:port of the OTLP gRPC collector that // Envoy reports tracing spans to. Empty disables Envoy-side tracing. OtlpCollectorAddress string diff --git a/cmd/atenet/internal/router/extproc.go b/cmd/atenet/internal/router/extproc.go index 2463930f5..2df72d063 100644 --- a/cmd/atenet/internal/router/extproc.go +++ b/cmd/atenet/internal/router/extproc.go @@ -184,14 +184,22 @@ func (s *ExtProcServer) handleRequestHeaders( "actor %s routing failed", actorRef) } + // The actor is reached through the in-worker atunnel ingress server, which + // listens on :443 (mTLS) and forwards to the actor's :80. The worker no + // longer DNATs pod-IP:80 to the actor, so the router dials :443 and the + // ORIGINAL_DST cluster's upstream TLS context presents the router's + // podidentity client cert (see buildOriginalDstCluster and + // buildUpstreamTransportSocket). // TODO(bowei) -- handle more than port 80 on the actor. - targetAddr := net.JoinHostPort(workerIP, "80") + targetAddr := net.JoinHostPort(workerIP, "443") slog.InfoContext(ctx, "Route ok", slog.Any("actor", actorRef), slog.String("targetAddr", targetAddr)) - // Route by rewriting the :authority header. + // Route by telling the ORIGINAL_DST cluster which worker atunnel address to + // dial, without touching :authority — atunnel authorizes the actor by the + // original Host (actor DNS name). mutation := &extprocv3.HeaderMutation{} - addAuthorityMutation(targetAddr, mutation) + addOriginalDstMutation(targetAddr, mutation) return &extprocv3.HeadersResponse{ Response: &extprocv3.CommonResponse{ diff --git a/cmd/atenet/internal/router/extproc_out.go b/cmd/atenet/internal/router/extproc_out.go index dfdf019b0..964a1a4f7 100644 --- a/cmd/atenet/internal/router/extproc_out.go +++ b/cmd/atenet/internal/router/extproc_out.go @@ -32,12 +32,22 @@ type reqError struct { func (e *reqError) Error() string { return e.msg } func (e *reqError) Unwrap() error { return e.cause } -func addAuthorityMutation(auth string, mut *extproc.HeaderMutation) { +// addOriginalDstMutation sets the header the ORIGINAL_DST cluster reads to pick +// the upstream address (the worker atunnel IP:443). Unlike an :authority +// rewrite it leaves the request Host intact, so atunnel still sees the actor +// DNS name and can authorize the active actor. +// +// Nothing strips this header from the incoming request, so overwrite rather +// than append: a client-supplied value must never influence the address Envoy +// dials. ext_proc mutations already default to replace, but the default is +// split across the deprecated append field and append_action — pin it. +func addOriginalDstMutation(dst string, mut *extproc.HeaderMutation) { mut.SetHeaders = append(mut.SetHeaders, &corev3.HeaderValueOption{ + AppendAction: corev3.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD, Header: &corev3.HeaderValue{ - Key: ":authority", - RawValue: []byte(auth), + Key: OriginalDstHeader, + RawValue: []byte(dst), }, }, ) diff --git a/cmd/atenet/internal/router/extproc_test.go b/cmd/atenet/internal/router/extproc_test.go index b6a889c11..eb739c58b 100644 --- a/cmd/atenet/internal/router/extproc_test.go +++ b/cmd/atenet/internal/router/extproc_test.go @@ -171,7 +171,7 @@ func TestExtProcHeadersEvaluation(t *testing.T) { }, }, expectErr: false, - expectedTarget: "10.0.0.52:80", + expectedTarget: "10.0.0.52:443", }, } @@ -238,7 +238,7 @@ func TestExtProcHeadersEvaluation(t *testing.T) { } headerOption := mutation.GetSetHeaders()[0] - if strings.ToLower(headerOption.Header.Key) != ":authority" { + if strings.ToLower(headerOption.Header.Key) != OriginalDstHeader { t.Errorf("invalid resulting dynamic parameter key: %s", headerOption.Header.Key) } diff --git a/cmd/atenet/internal/router/resumer_test.go b/cmd/atenet/internal/router/resumer_test.go index 1ab6308aa..ece13c993 100644 --- a/cmd/atenet/internal/router/resumer_test.go +++ b/cmd/atenet/internal/router/resumer_test.go @@ -54,7 +54,6 @@ func TestActorResumer_ResumeActor(t *testing.T) { resumeCalled++ return &ateapipb.ResumeActorResponse{ Actor: &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{Name: testActorName}, Status: ateapipb.Actor_STATUS_RUNNING, AteomPodIp: expectedIP, }, @@ -85,7 +84,6 @@ func TestActorResumer_ResumeActor(t *testing.T) { } return &ateapipb.ResumeActorResponse{ Actor: &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{Name: testActorName}, Status: ateapipb.Actor_STATUS_RUNNING, AteomPodIp: expectedIP, }, @@ -132,7 +130,6 @@ func TestActorResumer_ResumeActor(t *testing.T) { time.Sleep(20 * time.Millisecond) return &ateapipb.ResumeActorResponse{ Actor: &ateapipb.Actor{ - Metadata: &ateapipb.ResourceMetadata{Name: testActorName}, Status: ateapipb.Actor_STATUS_RUNNING, AteomPodIp: expectedIP, }, diff --git a/cmd/atenet/internal/router/router.go b/cmd/atenet/internal/router/router.go index 117099fcc..6f68412df 100644 --- a/cmd/atenet/internal/router/router.go +++ b/cmd/atenet/internal/router/router.go @@ -208,6 +208,7 @@ func (s *RouterServer) Run(ctx context.Context) error { } xdsSrv.SetTlsConfig(s.cfg.HttpsPort, s.cfg.EnvoyCertPath) + xdsSrv.SetUpstreamTls(s.cfg.UpstreamCredentialBundlePath, s.cfg.UpstreamTrustBundlePath, s.cfg.UpstreamSpiffePrefix) if s.extprocSrv == nil { routeDuration, err := newRouteDurationHistogram() if err != nil { diff --git a/cmd/atenet/internal/router/xds.go b/cmd/atenet/internal/router/xds.go index e2e66a1e7..ceed0bc09 100644 --- a/cmd/atenet/internal/router/xds.go +++ b/cmd/atenet/internal/router/xds.go @@ -39,13 +39,9 @@ import ( routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" tracev3 "github.com/envoyproxy/go-control-plane/envoy/config/trace/v3" streamaccesslogv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/stream/v3" - dfpclusterv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3" - dfpcommonv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3" - dfpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3" extprocv3filter "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3" routerv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3" hcmv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3" - getaddrinfov3 "github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3" tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" httpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" clustergrpc "github.com/envoyproxy/go-control-plane/envoy/service/cluster/v3" @@ -54,6 +50,7 @@ import ( listenergrpc "github.com/envoyproxy/go-control-plane/envoy/service/listener/v3" routegrpc "github.com/envoyproxy/go-control-plane/envoy/service/route/v3" secretgrpc "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3" + matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" typev3 "github.com/envoyproxy/go-control-plane/envoy/type/v3" "github.com/envoyproxy/go-control-plane/pkg/cache/types" cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3" @@ -75,6 +72,14 @@ const ( // full proto type name exactly; a typo is silently ignored rather than // rejected, so the options simply never take effect. httpProtocolOptionsName = "envoy.extensions.upstreams.http.v3.HttpProtocolOptions" + + // OriginalDstClusterName routes actor traffic to the worker's atunnel + // ingress by the IP:port the ext_proc puts in OriginalDstHeader, while the + // request :authority stays the actor DNS name so atunnel can identify the + // active actor. + OriginalDstClusterName = "actor_original_dst" + // OriginalDstHeader carries the resolved worker atunnel address (IP:443). + OriginalDstHeader = "x-ate-original-dst" ) // defaultExtProcMessageTimeout is Envoy's per-message ext_proc response timeout @@ -103,6 +108,19 @@ type XdsServer struct { httpsPort int certPath string + // Upstream (actor-facing) mTLS. When upstreamCredentialBundlePath is set, the + // ORIGINAL_DST actor cluster dials the actor's in-worker atunnel ingress + // server over mTLS: it presents this podidentity credential bundle as the + // client cert and validates the atunnel server against upstreamTrustBundlePath. + upstreamCredentialBundlePath string + upstreamTrustBundlePath string + // upstreamSpiffePrefix, when set, makes the upstream validator accept the + // atunnel server cert by matching its SPIFFE URI SAN against this prefix + // (trust-domain match) instead of the actor's ephemeral pod IP. The atunnel + // cert carries only a spiffe:// URI SAN, so without this Envoy's default + // SAN check against the dialed IP fails ("verify SAN list"). + upstreamSpiffePrefix string + otlpHost string otlpPort uint32 @@ -174,6 +192,19 @@ func (x *XdsServer) SetTlsConfig(httpsPort int, certPath string) { x.certPath = certPath } +// SetUpstreamTls configures actor-facing mTLS on the ORIGINAL_DST actor +// cluster. credentialBundle is the router's podidentity credential bundle +// (cert+key concatenated) presented to the actor's atunnel ingress server; +// trustBundle is the CA bundle used to validate that server. Empty +// credentialBundle leaves the upstream as plaintext. +func (x *XdsServer) SetUpstreamTls(credentialBundlePath, trustBundlePath, spiffePrefix string) { + x.mu.Lock() + defer x.mu.Unlock() + x.upstreamCredentialBundlePath = credentialBundlePath + x.upstreamTrustBundlePath = trustBundlePath + x.upstreamSpiffePrefix = spiffePrefix +} + // SetOtlpCollector enables Envoy-side tracing pointed at the OTLP gRPC // collector at host:port. addr empty disables tracing. port defaults to // 4317 if omitted. @@ -209,7 +240,7 @@ func (x *XdsServer) UpdateSnapshot() error { // Clusters clusters := []types.Resource{ x.buildCluster(), - x.buildDynamicForwardProxyCluster(), + x.buildOriginalDstCluster(), } if x.otlpHost != "" { clusters = append(clusters, x.buildOtlpCollectorCluster()) @@ -335,18 +366,6 @@ func (x *XdsServer) buildCluster() *clusterv3.Cluster { } } -func buildDnsCacheConfig() *dfpcommonv3.DnsCacheConfig { - resolverConfigAny, _ := anypb.New(&getaddrinfov3.GetAddrInfoDnsResolverConfig{}) - return &dfpcommonv3.DnsCacheConfig{ - Name: "dynamic_forward_proxy_cache_config", - DnsLookupFamily: clusterv3.Cluster_V4_ONLY, - TypedDnsResolverConfig: &corev3.TypedExtensionConfig{ - Name: "envoy.network.dns_resolver.getaddrinfo", - TypedConfig: resolverConfigAny, - }, - } -} - // buildOtlpCollectorCluster builds a STRICT_DNS HTTP/2 cluster that // targets the OTLP gRPC collector. Required when HCM tracing is enabled // so Envoy has somewhere to ship spans. @@ -397,50 +416,104 @@ func (x *XdsServer) buildOtlpCollectorCluster() *clusterv3.Cluster { } } -func (x *XdsServer) buildDynamicForwardProxyCluster() *clusterv3.Cluster { - dfpClusterConfig := &dfpclusterv3.ClusterConfig{ - ClusterImplementationSpecifier: &dfpclusterv3.ClusterConfig_DnsCacheConfig{ - DnsCacheConfig: buildDnsCacheConfig(), - }, - // A DFP cluster rejects HttpProtocolOptions unless auto_sni and - // auto_san_validation are on or this is set. This cluster has no - // transport socket — plaintext to worker pod IPs, with an IP literal for - // the authority — so there is no certificate to validate against. - AllowInsecureClusterOptions: true, +// buildUpstreamTransportSocket returns the actor-facing mTLS transport socket +// for the ORIGINAL_DST actor cluster, or nil when upstream mTLS is not +// configured. The router presents its podidentity credential bundle as the +// client cert and validates the atunnel ingress server against the trust +// bundle. Validation is by the SPIFFE URI SAN prefix (see upstreamSpiffePrefix) +// rather than the dialed pod IP. +func (x *XdsServer) buildUpstreamTransportSocket() *corev3.TransportSocket { + if x.upstreamCredentialBundlePath == "" { + return nil } - clusterConfigAny, _ := anypb.New(dfpClusterConfig) - - // One request per connection. Envoy pools by destination address, which - // assumes an address means one stable server. A worker pod's IP is stable - // but the actor sandbox behind port 80 is destroyed on every Suspend and a - // different actor takes the slot, so a pooled connection can belong to an - // actor that is already gone and the request 503s. - httpOpts, _ := anypb.New(&httpv3.HttpProtocolOptions{ - CommonHttpProtocolOptions: &corev3.HttpProtocolOptions{ - MaxRequestsPerConnection: wrapperspb.UInt32(1), + commonTls := &tlsv3.CommonTlsContext{ + TlsCertificates: []*tlsv3.TlsCertificate{ + { + CertificateChain: &corev3.DataSource{ + Specifier: &corev3.DataSource_Filename{Filename: x.upstreamCredentialBundlePath}, + }, + PrivateKey: &corev3.DataSource{ + Specifier: &corev3.DataSource_Filename{Filename: x.upstreamCredentialBundlePath}, + }, + }, }, - UpstreamProtocolOptions: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{ - ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{ - // HTTP/1.1 upstream, matching what actors serve on port 80. - ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{}, + } + if x.upstreamTrustBundlePath != "" { + validationCtx := &tlsv3.CertificateValidationContext{ + TrustedCa: &corev3.DataSource{ + Specifier: &corev3.DataSource_Filename{Filename: x.upstreamTrustBundlePath}, }, + } + // Validate the atunnel server by its SPIFFE URI SAN (trust-domain + // prefix) rather than the dialed pod IP. Without this, Envoy checks the + // cert SAN against the ephemeral pod IP, which the SPIFFE-only cert + // never matches. + if x.upstreamSpiffePrefix != "" { + validationCtx.MatchTypedSubjectAltNames = []*tlsv3.SubjectAltNameMatcher{ + { + SanType: tlsv3.SubjectAltNameMatcher_URI, + Matcher: &matcherv3.StringMatcher{ + MatchPattern: &matcherv3.StringMatcher_Prefix{Prefix: x.upstreamSpiffePrefix}, + }, + }, + } + } + commonTls.ValidationContextType = &tlsv3.CommonTlsContext_ValidationContext{ + ValidationContext: validationCtx, + } + } + + upstreamTls := &tlsv3.UpstreamTlsContext{CommonTlsContext: commonTls} + upstreamTlsAny, _ := anypb.New(upstreamTls) + return &corev3.TransportSocket{ + Name: "envoy.transport_sockets.tls", + ConfigType: &corev3.TransportSocket_TypedConfig{ + TypedConfig: upstreamTlsAny, }, - }) + } +} - return &clusterv3.Cluster{ - Name: "dynamic_forward_proxy_cluster", +// buildOriginalDstCluster dials the exact worker atunnel address supplied by +// the ext_proc in OriginalDstHeader. Unlike the dynamic_forward_proxy cluster, +// it does not derive the destination from :authority, so the request keeps the +// actor DNS name as its Host for atunnel to authorize. mTLS to atunnel is +// applied via the shared upstream transport socket (SPIFFE URI validation). +func (x *XdsServer) buildOriginalDstCluster() *clusterv3.Cluster { + cluster := &clusterv3.Cluster{ + Name: OriginalDstClusterName, + ConnectTimeout: durationpb.New(5 * time.Second), + ClusterDiscoveryType: &clusterv3.Cluster_Type{ + Type: clusterv3.Cluster_ORIGINAL_DST, + }, LbPolicy: clusterv3.Cluster_CLUSTER_PROVIDED, - ClusterDiscoveryType: &clusterv3.Cluster_ClusterType{ - ClusterType: &clusterv3.Cluster_CustomClusterType{ - Name: "envoy.clusters.dynamic_forward_proxy", - TypedConfig: clusterConfigAny, + LbConfig: &clusterv3.Cluster_OriginalDstLbConfig_{ + OriginalDstLbConfig: &clusterv3.Cluster_OriginalDstLbConfig{ + UseHttpHeader: true, + HttpHeaderName: OriginalDstHeader, }, }, - TypedExtensionProtocolOptions: map[string]*anypb.Any{ + } + + if ts := x.buildUpstreamTransportSocket(); ts != nil { + cluster.TransportSocket = ts + // The atunnel ingress server terminates TLS and reverse-proxies to the + // actor over HTTP/1.1. + httpOpts, _ := anypb.New(&httpv3.HttpProtocolOptions{ + UpstreamProtocolOptions: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{ + ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{ + ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_HttpProtocolOptions{ + HttpProtocolOptions: &corev3.Http1ProtocolOptions{}, + }, + }, + }, + }) + cluster.TypedExtensionProtocolOptions = map[string]*anypb.Any{ httpProtocolOptionsName: httpOpts, - }, + } } + + return cluster } func (x *XdsServer) buildRoutes() *routev3.RouteConfiguration { @@ -460,7 +533,7 @@ func (x *XdsServer) buildRoutes() *routev3.RouteConfiguration { Action: &routev3.Route_Route{ Route: &routev3.RouteAction{ ClusterSpecifier: &routev3.RouteAction_Cluster{ - Cluster: "dynamic_forward_proxy_cluster", + Cluster: OriginalDstClusterName, }, Timeout: durationpb.New(10 * time.Second), }, @@ -499,12 +572,6 @@ func (x *XdsServer) buildHcm(statPrefix string) *anypb.Any { }, }) - dfpFilterConfig, _ := anypb.New(&dfpv3.FilterConfig{ - ImplementationSpecifier: &dfpv3.FilterConfig_DnsCacheConfig{ - DnsCacheConfig: buildDnsCacheConfig(), - }, - }) - routerAny, _ := anypb.New(&routerv3.Router{}) accessLogConfig, _ := anypb.New(&streamaccesslogv3.StdoutAccessLog{}) @@ -528,12 +595,6 @@ func (x *XdsServer) buildHcm(statPrefix string) *anypb.Any { TypedConfig: extProcConfig, }, }, - { - Name: "envoy.filters.http.dynamic_forward_proxy", - ConfigType: &hcmv3.HttpFilter_TypedConfig{ - TypedConfig: dfpFilterConfig, - }, - }, { Name: "envoy.filters.http.router", ConfigType: &hcmv3.HttpFilter_TypedConfig{ diff --git a/cmd/atenet/internal/router/xds_test.go b/cmd/atenet/internal/router/xds_test.go index da211c8ff..4cf00a931 100644 --- a/cmd/atenet/internal/router/xds_test.go +++ b/cmd/atenet/internal/router/xds_test.go @@ -38,9 +38,7 @@ import ( corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" - dfpclusterv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3" tlsv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3" - httpv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3" discoverygrpc "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" secretgrpc "github.com/envoyproxy/go-control-plane/envoy/service/secret/v3" cachev3 "github.com/envoyproxy/go-control-plane/pkg/cache/v3" @@ -95,12 +93,15 @@ func TestXdsServer_UpdateSnapshot(t *testing.T) { } } - if raw, exists := clustersMap["dynamic_forward_proxy_cluster"]; !exists { - t.Error("'dynamic_forward_proxy_cluster' is missing from clusters") + if raw, exists := clustersMap[OriginalDstClusterName]; !exists { + t.Errorf("'%s' is missing from clusters", OriginalDstClusterName) } else { c := raw.(*clusterv3.Cluster) - if c.GetName() != "dynamic_forward_proxy_cluster" { - t.Errorf("Expected 'dynamic_forward_proxy_cluster', got %s", c.GetName()) + if c.GetName() != OriginalDstClusterName { + t.Errorf("Expected '%s', got %s", OriginalDstClusterName, c.GetName()) + } + if c.GetType() != clusterv3.Cluster_ORIGINAL_DST { + t.Errorf("Expected ORIGINAL_DST cluster, got %s", c.GetType()) } } @@ -532,53 +533,6 @@ func rotateProjectedVolume(t *testing.T, dir, newTsDir, oldTsDir string, bundle } } -// TestDynamicForwardProxyCluster_EnvoyAcceptsHttpProtocolOptions guards a -// coupling that is invisible on the Go side but fatal at runtime. -// -// Envoy refuses a dynamic_forward_proxy cluster that carries -// HttpProtocolOptions unless the cluster config either turns on both auto_sni -// and auto_san_validation or sets allow_insecure_cluster_options. A snapshot -// that breaks the rule is a perfectly well-formed proto and passes -// Consistent(), so nothing here fails: the only symptom is Envoy NACKing every -// CDS push and the cluster silently missing from its config dump, which reads -// as "all actor traffic 503s" rather than as a config error. -func TestDynamicForwardProxyCluster_EnvoyAcceptsHttpProtocolOptions(t *testing.T) { - cluster := NewXdsServer(18000).buildDynamicForwardProxyCluster() - - var clusterConfig dfpclusterv3.ClusterConfig - if err := cluster.GetClusterType().GetTypedConfig().UnmarshalTo(&clusterConfig); err != nil { - t.Fatalf("Failed to unmarshal dynamic forward proxy cluster config: %v", err) - } - - if !clusterConfig.GetAllowInsecureClusterOptions() { - t.Errorf("Cluster carries %s but neither allows insecure cluster options nor "+ - "enables auto_sni and auto_san_validation; Envoy will reject every CDS push", - httpProtocolOptionsName) - } -} - -// TestDynamicForwardProxyCluster_DisablesConnectionReuse pins the fix for the -// 503 storm seen under actor churn. Worker pod IPs are stable while the actor -// sandbox behind them is destroyed on every Suspend, so a pooled connection -// outlives the actor that owned it. -func TestDynamicForwardProxyCluster_DisablesConnectionReuse(t *testing.T) { - cluster := NewXdsServer(18000).buildDynamicForwardProxyCluster() - - raw, ok := cluster.GetTypedExtensionProtocolOptions()[httpProtocolOptionsName] - if !ok { - t.Fatalf("Cluster is missing %s", httpProtocolOptionsName) - } - - var opts httpv3.HttpProtocolOptions - if err := raw.UnmarshalTo(&opts); err != nil { - t.Fatalf("Failed to unmarshal HttpProtocolOptions: %v", err) - } - - if got := opts.GetCommonHttpProtocolOptions().GetMaxRequestsPerConnection().GetValue(); got != 1 { - t.Errorf("Expected max_requests_per_connection 1, got %d", got) - } -} - func TestXdsServer_ExtProcCircuitBreaker(t *testing.T) { t.Run("DefaultCoversLotPlusHeadroom", func(t *testing.T) { x := NewXdsServer(0) diff --git a/manifests/ate-install/atenet-router.yaml b/manifests/ate-install/atenet-router.yaml index 8da013ab0..d0e9bdc32 100644 --- a/manifests/ate-install/atenet-router.yaml +++ b/manifests/ate-install/atenet-router.yaml @@ -193,6 +193,15 @@ spec: mountPath: /etc/envoy - name: "servicedns" mountPath: "/run/servicedns.podcert.ate.dev" + # podidentity credential + trust bundles for actor-facing (upstream) + # mTLS. Envoy's ORIGINAL_DST actor cluster presents this credential + # bundle as its client cert when dialing the actor's atunnel ingress + # server on :443, and validates that server against the trust bundle. + # The issued SPIFFE identity is + # spiffe://cluster.local/ns/ate-system/sa/atenet-router, which atunnel's + # --atunnel-client-identity allows. + - name: "podidentity" + mountPath: "/run/podidentity.podcert.ate.dev" volumes: - name: envoy-config configMap: @@ -211,6 +220,12 @@ spec: signerName: podidentity.podcert.ate.dev/identity keyType: ECDSAP256 credentialBundlePath: credential-bundle.pem + - clusterTrustBundle: + signerName: podidentity.podcert.ate.dev/identity + labelSelector: + matchLabels: + podcert.ate.dev/canarying: live + path: trust-bundle.pem - name: "servicedns-ca" projected: sources: diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.go deleted file mode 100644 index fdc417226..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.go +++ /dev/null @@ -1,131 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.10 -// protoc v6.33.2 -// source: envoy/config/common/key_value/v3/config.proto - -package key_valuev3 - -import ( - _ "github.com/cncf/xds/go/udpa/annotations" - _ "github.com/cncf/xds/go/xds/annotations/v3" - v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - _ "github.com/envoyproxy/protoc-gen-validate/validate" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// This shared configuration for Envoy key value stores. -type KeyValueStoreConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // [#extension-category: envoy.common.key_value] - Config *v3.TypedExtensionConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *KeyValueStoreConfig) Reset() { - *x = KeyValueStoreConfig{} - mi := &file_envoy_config_common_key_value_v3_config_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *KeyValueStoreConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*KeyValueStoreConfig) ProtoMessage() {} - -func (x *KeyValueStoreConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_config_common_key_value_v3_config_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use KeyValueStoreConfig.ProtoReflect.Descriptor instead. -func (*KeyValueStoreConfig) Descriptor() ([]byte, []int) { - return file_envoy_config_common_key_value_v3_config_proto_rawDescGZIP(), []int{0} -} - -func (x *KeyValueStoreConfig) GetConfig() *v3.TypedExtensionConfig { - if x != nil { - return x.Config - } - return nil -} - -var File_envoy_config_common_key_value_v3_config_proto protoreflect.FileDescriptor - -const file_envoy_config_common_key_value_v3_config_proto_rawDesc = "" + - "\n" + - "-envoy/config/common/key_value/v3/config.proto\x12 envoy.config.common.key_value.v3\x1a$envoy/config/core/v3/extension.proto\x1a\x1fxds/annotations/v3/status.proto\x1a\x1dudpa/annotations/status.proto\x1a\x17validate/validate.proto\"m\n" + - "\x13KeyValueStoreConfig\x12L\n" + - "\x06config\x18\x01 \x01(\v2*.envoy.config.core.v3.TypedExtensionConfigB\b\xfaB\x05\x8a\x01\x02\x10\x01R\x06config:\b\xd2Ƥ\xe1\x06\x02\b\x01B\x9c\x01\xba\x80\xc8\xd1\x06\x02\x10\x02\n" + - ".io.envoyproxy.envoy.config.common.key_value.v3B\vConfigProtoP\x01ZSgithub.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3;key_valuev3b\x06proto3" - -var ( - file_envoy_config_common_key_value_v3_config_proto_rawDescOnce sync.Once - file_envoy_config_common_key_value_v3_config_proto_rawDescData []byte -) - -func file_envoy_config_common_key_value_v3_config_proto_rawDescGZIP() []byte { - file_envoy_config_common_key_value_v3_config_proto_rawDescOnce.Do(func() { - file_envoy_config_common_key_value_v3_config_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_envoy_config_common_key_value_v3_config_proto_rawDesc), len(file_envoy_config_common_key_value_v3_config_proto_rawDesc))) - }) - return file_envoy_config_common_key_value_v3_config_proto_rawDescData -} - -var file_envoy_config_common_key_value_v3_config_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_envoy_config_common_key_value_v3_config_proto_goTypes = []any{ - (*KeyValueStoreConfig)(nil), // 0: envoy.config.common.key_value.v3.KeyValueStoreConfig - (*v3.TypedExtensionConfig)(nil), // 1: envoy.config.core.v3.TypedExtensionConfig -} -var file_envoy_config_common_key_value_v3_config_proto_depIdxs = []int32{ - 1, // 0: envoy.config.common.key_value.v3.KeyValueStoreConfig.config:type_name -> envoy.config.core.v3.TypedExtensionConfig - 1, // [1:1] is the sub-list for method output_type - 1, // [1:1] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_envoy_config_common_key_value_v3_config_proto_init() } -func file_envoy_config_common_key_value_v3_config_proto_init() { - if File_envoy_config_common_key_value_v3_config_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_envoy_config_common_key_value_v3_config_proto_rawDesc), len(file_envoy_config_common_key_value_v3_config_proto_rawDesc)), - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_envoy_config_common_key_value_v3_config_proto_goTypes, - DependencyIndexes: file_envoy_config_common_key_value_v3_config_proto_depIdxs, - MessageInfos: file_envoy_config_common_key_value_v3_config_proto_msgTypes, - }.Build() - File_envoy_config_common_key_value_v3_config_proto = out.File - file_envoy_config_common_key_value_v3_config_proto_goTypes = nil - file_envoy_config_common_key_value_v3_config_proto_depIdxs = nil -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.validate.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.validate.go deleted file mode 100644 index 7243a25c4..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config.pb.validate.go +++ /dev/null @@ -1,179 +0,0 @@ -//go:build !disable_pgv -// Code generated by protoc-gen-validate. DO NOT EDIT. -// source: envoy/config/common/key_value/v3/config.proto - -package key_valuev3 - -import ( - "bytes" - "errors" - "fmt" - "net" - "net/mail" - "net/url" - "regexp" - "sort" - "strings" - "time" - "unicode/utf8" - - "google.golang.org/protobuf/types/known/anypb" -) - -// ensure the imports are used -var ( - _ = bytes.MinRead - _ = errors.New("") - _ = fmt.Print - _ = utf8.UTFMax - _ = (*regexp.Regexp)(nil) - _ = (*strings.Reader)(nil) - _ = net.IPv4len - _ = time.Duration(0) - _ = (*url.URL)(nil) - _ = (*mail.Address)(nil) - _ = anypb.Any{} - _ = sort.Sort -) - -// Validate checks the field values on KeyValueStoreConfig with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *KeyValueStoreConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on KeyValueStoreConfig with the rules -// defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// KeyValueStoreConfigMultiError, or nil if none found. -func (m *KeyValueStoreConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *KeyValueStoreConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - if m.GetConfig() == nil { - err := KeyValueStoreConfigValidationError{ - field: "Config", - reason: "value is required", - } - if !all { - return err - } - errors = append(errors, err) - } - - if all { - switch v := interface{}(m.GetConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, KeyValueStoreConfigValidationError{ - field: "Config", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, KeyValueStoreConfigValidationError{ - field: "Config", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return KeyValueStoreConfigValidationError{ - field: "Config", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if len(errors) > 0 { - return KeyValueStoreConfigMultiError(errors) - } - - return nil -} - -// KeyValueStoreConfigMultiError is an error wrapping multiple validation -// errors returned by KeyValueStoreConfig.ValidateAll() if the designated -// constraints aren't met. -type KeyValueStoreConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m KeyValueStoreConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m KeyValueStoreConfigMultiError) AllErrors() []error { return m } - -// KeyValueStoreConfigValidationError is the validation error returned by -// KeyValueStoreConfig.Validate if the designated constraints aren't met. -type KeyValueStoreConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e KeyValueStoreConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e KeyValueStoreConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e KeyValueStoreConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e KeyValueStoreConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e KeyValueStoreConfigValidationError) ErrorName() string { - return "KeyValueStoreConfigValidationError" -} - -// Error satisfies the builtin error interface -func (e KeyValueStoreConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sKeyValueStoreConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = KeyValueStoreConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = KeyValueStoreConfigValidationError{} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config_vtproto.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config_vtproto.pb.go deleted file mode 100644 index 0d7bf3742..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3/config_vtproto.pb.go +++ /dev/null @@ -1,95 +0,0 @@ -//go:build vtprotobuf -// +build vtprotobuf - -// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// source: envoy/config/common/key_value/v3/config.proto - -package key_valuev3 - -import ( - protohelpers "github.com/planetscale/vtprotobuf/protohelpers" - proto "google.golang.org/protobuf/proto" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -func (m *KeyValueStoreConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *KeyValueStoreConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *KeyValueStoreConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.Config != nil { - if vtmsg, ok := interface{}(m.Config).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.Config) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *KeyValueStoreConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Config != nil { - if size, ok := interface{}(m.Config).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.Config) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.go deleted file mode 100644 index f21ba01c2..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.go +++ /dev/null @@ -1,327 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.10 -// protoc v6.33.2 -// source: envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto - -package dynamic_forward_proxyv3 - -import ( - _ "github.com/cncf/xds/go/udpa/annotations" - v31 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - v32 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3" - _ "github.com/envoyproxy/protoc-gen-validate/validate" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - durationpb "google.golang.org/protobuf/types/known/durationpb" - wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Configuration for the dynamic forward proxy cluster. See the :ref:`architecture overview -// ` for more information. -// [#extension: envoy.clusters.dynamic_forward_proxy] -type ClusterConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to ClusterImplementationSpecifier: - // - // *ClusterConfig_DnsCacheConfig - // *ClusterConfig_SubClustersConfig - ClusterImplementationSpecifier isClusterConfig_ClusterImplementationSpecifier `protobuf_oneof:"cluster_implementation_specifier"` - // If true allow the cluster configuration to disable the auto_sni and auto_san_validation options - // in the :ref:`cluster's upstream_http_protocol_options - // ` - AllowInsecureClusterOptions bool `protobuf:"varint,2,opt,name=allow_insecure_cluster_options,json=allowInsecureClusterOptions,proto3" json:"allow_insecure_cluster_options,omitempty"` - // If true allow HTTP/2 and HTTP/3 connections to be reused for requests to different - // origins than the connection was initially created for. This will only happen when the - // resolved address for the new connection matches the peer address of the connection and - // the TLS certificate is also valid for the new hostname. For example, if a connection - // has previously been established to foo.example.com at IP 1.2.3.4 with a certificate - // that is valid for “*.example.com“, then this connection could be used for requests to - // bar.example.com if that also resolved to 1.2.3.4. - // - // .. note:: - // - // By design, this feature will maximize reuse of connections. This means that instead - // opening a new connection when an existing connection reaches the maximum number of - // concurrent streams, requests will instead be sent to the existing connection. - // - // .. note:: - // - // The coalesced connections might be to upstreams that would not be otherwise - // selected by Envoy. See the section `Connection Reuse in RFC 7540 - // `_ - AllowCoalescedConnections bool `protobuf:"varint,3,opt,name=allow_coalesced_connections,json=allowCoalescedConnections,proto3" json:"allow_coalesced_connections,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *ClusterConfig) Reset() { - *x = ClusterConfig{} - mi := &file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *ClusterConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ClusterConfig) ProtoMessage() {} - -func (x *ClusterConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ClusterConfig.ProtoReflect.Descriptor instead. -func (*ClusterConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescGZIP(), []int{0} -} - -func (x *ClusterConfig) GetClusterImplementationSpecifier() isClusterConfig_ClusterImplementationSpecifier { - if x != nil { - return x.ClusterImplementationSpecifier - } - return nil -} - -func (x *ClusterConfig) GetDnsCacheConfig() *v3.DnsCacheConfig { - if x != nil { - if x, ok := x.ClusterImplementationSpecifier.(*ClusterConfig_DnsCacheConfig); ok { - return x.DnsCacheConfig - } - } - return nil -} - -func (x *ClusterConfig) GetSubClustersConfig() *SubClustersConfig { - if x != nil { - if x, ok := x.ClusterImplementationSpecifier.(*ClusterConfig_SubClustersConfig); ok { - return x.SubClustersConfig - } - } - return nil -} - -func (x *ClusterConfig) GetAllowInsecureClusterOptions() bool { - if x != nil { - return x.AllowInsecureClusterOptions - } - return false -} - -func (x *ClusterConfig) GetAllowCoalescedConnections() bool { - if x != nil { - return x.AllowCoalescedConnections - } - return false -} - -type isClusterConfig_ClusterImplementationSpecifier interface { - isClusterConfig_ClusterImplementationSpecifier() -} - -type ClusterConfig_DnsCacheConfig struct { - // The DNS cache configuration that the cluster will attach to. Note this configuration must - // match that of associated :ref:`dynamic forward proxy HTTP filter configuration - // `. - DnsCacheConfig *v3.DnsCacheConfig `protobuf:"bytes,1,opt,name=dns_cache_config,json=dnsCacheConfig,proto3,oneof"` -} - -type ClusterConfig_SubClustersConfig struct { - // Configuration for sub clusters, when this configuration is enabled, - // Envoy will create an independent sub cluster dynamically for each host:port. - // Most of the configuration of a sub cluster is inherited from the current cluster, - // i.e. health_checks, dns_resolvers and etc. - // And the load_assignment will be set to the only one endpoint, host:port. - // - // Compared to the dns_cache_config, it has the following advantages: - // - // 1. sub clusters will be created with the STRICT_DNS DiscoveryType, - // so that Envoy will use all of the IPs resolved from the host. - // - // 2. each sub cluster is full featured cluster, with lb_policy and health check and etc enabled. - SubClustersConfig *SubClustersConfig `protobuf:"bytes,4,opt,name=sub_clusters_config,json=subClustersConfig,proto3,oneof"` -} - -func (*ClusterConfig_DnsCacheConfig) isClusterConfig_ClusterImplementationSpecifier() {} - -func (*ClusterConfig_SubClustersConfig) isClusterConfig_ClusterImplementationSpecifier() {} - -// Configuration for sub clusters. Hard code STRICT_DNS cluster type now. -type SubClustersConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The :ref:`load balancer type ` to use - // when picking a host in a sub cluster. Note that CLUSTER_PROVIDED is not allowed here. - LbPolicy v31.Cluster_LbPolicy `protobuf:"varint,1,opt,name=lb_policy,json=lbPolicy,proto3,enum=envoy.config.cluster.v3.Cluster_LbPolicy" json:"lb_policy,omitempty"` - // The maximum number of sub clusters that the DFP cluster will hold. If not specified defaults to 1024. - MaxSubClusters *wrapperspb.UInt32Value `protobuf:"bytes,2,opt,name=max_sub_clusters,json=maxSubClusters,proto3" json:"max_sub_clusters,omitempty"` - // The TTL for sub clusters that are unused. Sub clusters that have not been used in the configured time - // interval will be purged. If not specified defaults to 5m. - SubClusterTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=sub_cluster_ttl,json=subClusterTtl,proto3" json:"sub_cluster_ttl,omitempty"` - // Sub clusters that should be created & warmed upon creation. This might provide a - // performance improvement, in the form of cache hits, for sub clusters that are going to be - // warmed during steady state and are known at config load time. - PreresolveClusters []*v32.SocketAddress `protobuf:"bytes,4,rep,name=preresolve_clusters,json=preresolveClusters,proto3" json:"preresolve_clusters,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubClustersConfig) Reset() { - *x = SubClustersConfig{} - mi := &file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubClustersConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubClustersConfig) ProtoMessage() {} - -func (x *SubClustersConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubClustersConfig.ProtoReflect.Descriptor instead. -func (*SubClustersConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescGZIP(), []int{1} -} - -func (x *SubClustersConfig) GetLbPolicy() v31.Cluster_LbPolicy { - if x != nil { - return x.LbPolicy - } - return v31.Cluster_LbPolicy(0) -} - -func (x *SubClustersConfig) GetMaxSubClusters() *wrapperspb.UInt32Value { - if x != nil { - return x.MaxSubClusters - } - return nil -} - -func (x *SubClustersConfig) GetSubClusterTtl() *durationpb.Duration { - if x != nil { - return x.SubClusterTtl - } - return nil -} - -func (x *SubClustersConfig) GetPreresolveClusters() []*v32.SocketAddress { - if x != nil { - return x.PreresolveClusters - } - return nil -} - -var File_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto protoreflect.FileDescriptor - -const file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDesc = "" + - "\n" + - "@envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto\x122envoy.extensions.clusters.dynamic_forward_proxy.v3\x1a%envoy/config/cluster/v3/cluster.proto\x1a\"envoy/config/core/v3/address.proto\x1a@envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1dudpa/annotations/status.proto\x1a!udpa/annotations/versioning.proto\x1a\x17validate/validate.proto\"\xe8\x03\n" + - "\rClusterConfig\x12l\n" + - "\x10dns_cache_config\x18\x01 \x01(\v2@.envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfigH\x00R\x0ednsCacheConfig\x12w\n" + - "\x13sub_clusters_config\x18\x04 \x01(\v2E.envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfigH\x00R\x11subClustersConfig\x12C\n" + - "\x1eallow_insecure_cluster_options\x18\x02 \x01(\bR\x1ballowInsecureClusterOptions\x12>\n" + - "\x1ballow_coalesced_connections\x18\x03 \x01(\bR\x19allowCoalescedConnections:G\x9aň\x1eB\n" + - "@envoy.config.cluster.dynamic_forward_proxy.v2alpha.ClusterConfigB\"\n" + - " cluster_implementation_specifier\"\xd9\x02\n" + - "\x11SubClustersConfig\x12P\n" + - "\tlb_policy\x18\x01 \x01(\x0e2).envoy.config.cluster.v3.Cluster.LbPolicyB\b\xfaB\x05\x82\x01\x02\x10\x01R\blbPolicy\x12O\n" + - "\x10max_sub_clusters\x18\x02 \x01(\v2\x1c.google.protobuf.UInt32ValueB\a\xfaB\x04*\x02 \x00R\x0emaxSubClusters\x12K\n" + - "\x0fsub_cluster_ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationB\b\xfaB\x05\xaa\x01\x02*\x00R\rsubClusterTtl\x12T\n" + - "\x13preresolve_clusters\x18\x04 \x03(\v2#.envoy.config.core.v3.SocketAddressR\x12preresolveClustersB\xcd\x01\xba\x80\xc8\xd1\x06\x02\x10\x02\n" + - "@io.envoyproxy.envoy.extensions.clusters.dynamic_forward_proxy.v3B\fClusterProtoP\x01Zqgithub.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3;dynamic_forward_proxyv3b\x06proto3" - -var ( - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescOnce sync.Once - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescData []byte -) - -func file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescGZIP() []byte { - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescOnce.Do(func() { - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDesc), len(file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDesc))) - }) - return file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDescData -} - -var file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_goTypes = []any{ - (*ClusterConfig)(nil), // 0: envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig - (*SubClustersConfig)(nil), // 1: envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfig - (*v3.DnsCacheConfig)(nil), // 2: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig - (v31.Cluster_LbPolicy)(0), // 3: envoy.config.cluster.v3.Cluster.LbPolicy - (*wrapperspb.UInt32Value)(nil), // 4: google.protobuf.UInt32Value - (*durationpb.Duration)(nil), // 5: google.protobuf.Duration - (*v32.SocketAddress)(nil), // 6: envoy.config.core.v3.SocketAddress -} -var file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_depIdxs = []int32{ - 2, // 0: envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig.dns_cache_config:type_name -> envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig - 1, // 1: envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig.sub_clusters_config:type_name -> envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfig - 3, // 2: envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfig.lb_policy:type_name -> envoy.config.cluster.v3.Cluster.LbPolicy - 4, // 3: envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfig.max_sub_clusters:type_name -> google.protobuf.UInt32Value - 5, // 4: envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfig.sub_cluster_ttl:type_name -> google.protobuf.Duration - 6, // 5: envoy.extensions.clusters.dynamic_forward_proxy.v3.SubClustersConfig.preresolve_clusters:type_name -> envoy.config.core.v3.SocketAddress - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name -} - -func init() { file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_init() } -func file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_init() { - if File_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto != nil { - return - } - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes[0].OneofWrappers = []any{ - (*ClusterConfig_DnsCacheConfig)(nil), - (*ClusterConfig_SubClustersConfig)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDesc), len(file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_rawDesc)), - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_goTypes, - DependencyIndexes: file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_depIdxs, - MessageInfos: file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_msgTypes, - }.Build() - File_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto = out.File - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_goTypes = nil - file_envoy_extensions_clusters_dynamic_forward_proxy_v3_cluster_proto_depIdxs = nil -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.validate.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.validate.go deleted file mode 100644 index 26884ce91..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.pb.validate.go +++ /dev/null @@ -1,424 +0,0 @@ -//go:build !disable_pgv -// Code generated by protoc-gen-validate. DO NOT EDIT. -// source: envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto - -package dynamic_forward_proxyv3 - -import ( - "bytes" - "errors" - "fmt" - "net" - "net/mail" - "net/url" - "regexp" - "sort" - "strings" - "time" - "unicode/utf8" - - "google.golang.org/protobuf/types/known/anypb" - - v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" -) - -// ensure the imports are used -var ( - _ = bytes.MinRead - _ = errors.New("") - _ = fmt.Print - _ = utf8.UTFMax - _ = (*regexp.Regexp)(nil) - _ = (*strings.Reader)(nil) - _ = net.IPv4len - _ = time.Duration(0) - _ = (*url.URL)(nil) - _ = (*mail.Address)(nil) - _ = anypb.Any{} - _ = sort.Sort - - _ = v3.Cluster_LbPolicy(0) -) - -// Validate checks the field values on ClusterConfig with the rules defined in -// the proto definition for this message. If any rules are violated, the first -// error encountered is returned, or nil if there are no violations. -func (m *ClusterConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on ClusterConfig with the rules defined -// in the proto definition for this message. If any rules are violated, the -// result is a list of violation errors wrapped in ClusterConfigMultiError, or -// nil if none found. -func (m *ClusterConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *ClusterConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - // no validation rules for AllowInsecureClusterOptions - - // no validation rules for AllowCoalescedConnections - - switch v := m.ClusterImplementationSpecifier.(type) { - case *ClusterConfig_DnsCacheConfig: - if v == nil { - err := ClusterConfigValidationError{ - field: "ClusterImplementationSpecifier", - reason: "oneof value cannot be a typed-nil", - } - if !all { - return err - } - errors = append(errors, err) - } - - if all { - switch v := interface{}(m.GetDnsCacheConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, ClusterConfigValidationError{ - field: "DnsCacheConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, ClusterConfigValidationError{ - field: "DnsCacheConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetDnsCacheConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return ClusterConfigValidationError{ - field: "DnsCacheConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - case *ClusterConfig_SubClustersConfig: - if v == nil { - err := ClusterConfigValidationError{ - field: "ClusterImplementationSpecifier", - reason: "oneof value cannot be a typed-nil", - } - if !all { - return err - } - errors = append(errors, err) - } - - if all { - switch v := interface{}(m.GetSubClustersConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, ClusterConfigValidationError{ - field: "SubClustersConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, ClusterConfigValidationError{ - field: "SubClustersConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetSubClustersConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return ClusterConfigValidationError{ - field: "SubClustersConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - default: - _ = v // ensures v is used - } - - if len(errors) > 0 { - return ClusterConfigMultiError(errors) - } - - return nil -} - -// ClusterConfigMultiError is an error wrapping multiple validation errors -// returned by ClusterConfig.ValidateAll() if the designated constraints -// aren't met. -type ClusterConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m ClusterConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m ClusterConfigMultiError) AllErrors() []error { return m } - -// ClusterConfigValidationError is the validation error returned by -// ClusterConfig.Validate if the designated constraints aren't met. -type ClusterConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e ClusterConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e ClusterConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e ClusterConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e ClusterConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e ClusterConfigValidationError) ErrorName() string { return "ClusterConfigValidationError" } - -// Error satisfies the builtin error interface -func (e ClusterConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sClusterConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = ClusterConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = ClusterConfigValidationError{} - -// Validate checks the field values on SubClustersConfig with the rules defined -// in the proto definition for this message. If any rules are violated, the -// first error encountered is returned, or nil if there are no violations. -func (m *SubClustersConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on SubClustersConfig with the rules -// defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// SubClustersConfigMultiError, or nil if none found. -func (m *SubClustersConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *SubClustersConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - if _, ok := v3.Cluster_LbPolicy_name[int32(m.GetLbPolicy())]; !ok { - err := SubClustersConfigValidationError{ - field: "LbPolicy", - reason: "value must be one of the defined enum values", - } - if !all { - return err - } - errors = append(errors, err) - } - - if wrapper := m.GetMaxSubClusters(); wrapper != nil { - - if wrapper.GetValue() <= 0 { - err := SubClustersConfigValidationError{ - field: "MaxSubClusters", - reason: "value must be greater than 0", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - - if d := m.GetSubClusterTtl(); d != nil { - dur, err := d.AsDuration(), d.CheckValid() - if err != nil { - err = SubClustersConfigValidationError{ - field: "SubClusterTtl", - reason: "value is not a valid duration", - cause: err, - } - if !all { - return err - } - errors = append(errors, err) - } else { - - gt := time.Duration(0*time.Second + 0*time.Nanosecond) - - if dur <= gt { - err := SubClustersConfigValidationError{ - field: "SubClusterTtl", - reason: "value must be greater than 0s", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - } - - for idx, item := range m.GetPreresolveClusters() { - _, _ = idx, item - - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, SubClustersConfigValidationError{ - field: fmt.Sprintf("PreresolveClusters[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, SubClustersConfigValidationError{ - field: fmt.Sprintf("PreresolveClusters[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return SubClustersConfigValidationError{ - field: fmt.Sprintf("PreresolveClusters[%v]", idx), - reason: "embedded message failed validation", - cause: err, - } - } - } - - } - - if len(errors) > 0 { - return SubClustersConfigMultiError(errors) - } - - return nil -} - -// SubClustersConfigMultiError is an error wrapping multiple validation errors -// returned by SubClustersConfig.ValidateAll() if the designated constraints -// aren't met. -type SubClustersConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m SubClustersConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m SubClustersConfigMultiError) AllErrors() []error { return m } - -// SubClustersConfigValidationError is the validation error returned by -// SubClustersConfig.Validate if the designated constraints aren't met. -type SubClustersConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e SubClustersConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e SubClustersConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e SubClustersConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e SubClustersConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e SubClustersConfigValidationError) ErrorName() string { - return "SubClustersConfigValidationError" -} - -// Error satisfies the builtin error interface -func (e SubClustersConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sSubClustersConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = SubClustersConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = SubClustersConfigValidationError{} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster_vtproto.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster_vtproto.pb.go deleted file mode 100644 index ac609ad10..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster_vtproto.pb.go +++ /dev/null @@ -1,315 +0,0 @@ -//go:build vtprotobuf -// +build vtprotobuf - -// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// source: envoy/extensions/clusters/dynamic_forward_proxy/v3/cluster.proto - -package dynamic_forward_proxyv3 - -import ( - protohelpers "github.com/planetscale/vtprotobuf/protohelpers" - durationpb "github.com/planetscale/vtprotobuf/types/known/durationpb" - wrapperspb "github.com/planetscale/vtprotobuf/types/known/wrapperspb" - proto "google.golang.org/protobuf/proto" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -func (m *ClusterConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ClusterConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *ClusterConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if msg, ok := m.ClusterImplementationSpecifier.(*ClusterConfig_SubClustersConfig); ok { - size, err := msg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - } - if m.AllowCoalescedConnections { - i-- - if m.AllowCoalescedConnections { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x18 - } - if m.AllowInsecureClusterOptions { - i-- - if m.AllowInsecureClusterOptions { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x10 - } - if msg, ok := m.ClusterImplementationSpecifier.(*ClusterConfig_DnsCacheConfig); ok { - size, err := msg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - } - return len(dAtA) - i, nil -} - -func (m *ClusterConfig_DnsCacheConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *ClusterConfig_DnsCacheConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - i := len(dAtA) - if m.DnsCacheConfig != nil { - if vtmsg, ok := interface{}(m.DnsCacheConfig).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.DnsCacheConfig) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0xa - } else { - i = protohelpers.EncodeVarint(dAtA, i, 0) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} -func (m *ClusterConfig_SubClustersConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *ClusterConfig_SubClustersConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - i := len(dAtA) - if m.SubClustersConfig != nil { - size, err := m.SubClustersConfig.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x22 - } else { - i = protohelpers.EncodeVarint(dAtA, i, 0) - i-- - dAtA[i] = 0x22 - } - return len(dAtA) - i, nil -} -func (m *SubClustersConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SubClustersConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *SubClustersConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if len(m.PreresolveClusters) > 0 { - for iNdEx := len(m.PreresolveClusters) - 1; iNdEx >= 0; iNdEx-- { - if vtmsg, ok := interface{}(m.PreresolveClusters[iNdEx]).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.PreresolveClusters[iNdEx]) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x22 - } - } - if m.SubClusterTtl != nil { - size, err := (*durationpb.Duration)(m.SubClusterTtl).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x1a - } - if m.MaxSubClusters != nil { - size, err := (*wrapperspb.UInt32Value)(m.MaxSubClusters).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 - } - if m.LbPolicy != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.LbPolicy)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - -func (m *ClusterConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if vtmsg, ok := m.ClusterImplementationSpecifier.(interface{ SizeVT() int }); ok { - n += vtmsg.SizeVT() - } - if m.AllowInsecureClusterOptions { - n += 2 - } - if m.AllowCoalescedConnections { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *ClusterConfig_DnsCacheConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.DnsCacheConfig != nil { - if size, ok := interface{}(m.DnsCacheConfig).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.DnsCacheConfig) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } else { - n += 2 - } - return n -} -func (m *ClusterConfig_SubClustersConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.SubClustersConfig != nil { - l = m.SubClustersConfig.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } else { - n += 2 - } - return n -} -func (m *SubClustersConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.LbPolicy != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.LbPolicy)) - } - if m.MaxSubClusters != nil { - l = (*wrapperspb.UInt32Value)(m.MaxSubClusters).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.SubClusterTtl != nil { - l = (*durationpb.Duration)(m.SubClusterTtl).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if len(m.PreresolveClusters) > 0 { - for _, e := range m.PreresolveClusters { - if size, ok := interface{}(e).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(e) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - n += len(m.unknownFields) - return n -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.go deleted file mode 100644 index 884066627..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.go +++ /dev/null @@ -1,417 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.10 -// protoc v6.33.2 -// source: envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto - -package dynamic_forward_proxyv3 - -import ( - _ "github.com/cncf/xds/go/udpa/annotations" - _ "github.com/envoyproxy/go-control-plane/envoy/annotations" - v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" - v32 "github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3" - v31 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" - _ "github.com/envoyproxy/protoc-gen-validate/validate" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - durationpb "google.golang.org/protobuf/types/known/durationpb" - wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Configuration of circuit breakers for resolver. -type DnsCacheCircuitBreakers struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The maximum number of pending requests that Envoy will allow to the - // resolver. If not specified, the default is 1024. - MaxPendingRequests *wrapperspb.UInt32Value `protobuf:"bytes,1,opt,name=max_pending_requests,json=maxPendingRequests,proto3" json:"max_pending_requests,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DnsCacheCircuitBreakers) Reset() { - *x = DnsCacheCircuitBreakers{} - mi := &file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DnsCacheCircuitBreakers) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DnsCacheCircuitBreakers) ProtoMessage() {} - -func (x *DnsCacheCircuitBreakers) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DnsCacheCircuitBreakers.ProtoReflect.Descriptor instead. -func (*DnsCacheCircuitBreakers) Descriptor() ([]byte, []int) { - return file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescGZIP(), []int{0} -} - -func (x *DnsCacheCircuitBreakers) GetMaxPendingRequests() *wrapperspb.UInt32Value { - if x != nil { - return x.MaxPendingRequests - } - return nil -} - -// Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview -// ` for more information. -// [#next-free-field: 16] -type DnsCacheConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The name of the cache. Multiple named caches allow independent dynamic forward proxy - // configurations to operate within a single Envoy process using different configurations. All - // configurations with the same name *must* otherwise have the same settings when referenced - // from different configuration components. Configuration will fail to load if this is not - // the case. - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // The DNS lookup family to use during resolution. - // - // [#comment:TODO(mattklein123): Figure out how to support IPv4/IPv6 "happy eyeballs" mode. The - // way this might work is a new lookup family which returns both IPv4 and IPv6 addresses, and - // then configures a host to have a primary and fall back address. With this, we could very - // likely build a "happy eyeballs" connection pool which would race the primary / fall back - // address and return the one that wins. This same method could potentially also be used for - // QUIC to TCP fall back.] - DnsLookupFamily v3.Cluster_DnsLookupFamily `protobuf:"varint,2,opt,name=dns_lookup_family,json=dnsLookupFamily,proto3,enum=envoy.config.cluster.v3.Cluster_DnsLookupFamily" json:"dns_lookup_family,omitempty"` - // The DNS refresh rate for unresolved DNS hosts. If not specified defaults to 60s. - // - // The refresh rate is rounded to the closest millisecond, and must be at least 1ms. - // - // Once a host has been resolved, the refresh rate will be the DNS TTL, capped - // at a minimum of “dns_min_refresh_rate“. - DnsRefreshRate *durationpb.Duration `protobuf:"bytes,3,opt,name=dns_refresh_rate,json=dnsRefreshRate,proto3" json:"dns_refresh_rate,omitempty"` - // The minimum rate that DNS resolution will occur. Per “dns_refresh_rate“, once a host is - // resolved, the DNS TTL will be used, with a minimum set by “dns_min_refresh_rate“. - // “dns_min_refresh_rate“ defaults to 5s and must also be >= 1s. - DnsMinRefreshRate *durationpb.Duration `protobuf:"bytes,14,opt,name=dns_min_refresh_rate,json=dnsMinRefreshRate,proto3" json:"dns_min_refresh_rate,omitempty"` - // The TTL for hosts that are unused. Hosts that have not been used in the configured time - // interval will be purged. If not specified defaults to 5m. - // - // .. note: - // - // The TTL is only checked at the time of DNS refresh, as specified by ``dns_refresh_rate``. This - // means that if the configured TTL is shorter than the refresh rate the host may not be removed - // immediately. - // - // .. note: - // - // The TTL has no relation to DNS TTL and is only used to control Envoy's resource usage. - HostTtl *durationpb.Duration `protobuf:"bytes,4,opt,name=host_ttl,json=hostTtl,proto3" json:"host_ttl,omitempty"` - // The maximum number of hosts that the cache will hold. If not specified defaults to 1024. - // - // .. note: - // - // The implementation is approximate and enforced independently on each worker thread, thus - // it is possible for the maximum hosts in the cache to go slightly above the configured - // value depending on timing. This is similar to how other circuit breakers work. - MaxHosts *wrapperspb.UInt32Value `protobuf:"bytes,5,opt,name=max_hosts,json=maxHosts,proto3" json:"max_hosts,omitempty"` - // Disable the DNS refresh on failure. If this field is set to true, it will ignore the - // :ref:`typed_dns_resolver_config `. - // If not specified, it defaults to false. By enabling this feature, the failed hosts will now be treated as a cache miss, - // allowing the failed hosts to be resolved on demand. - DisableDnsRefreshOnFailure bool `protobuf:"varint,15,opt,name=disable_dns_refresh_on_failure,json=disableDnsRefreshOnFailure,proto3" json:"disable_dns_refresh_on_failure,omitempty"` - // If the DNS failure refresh rate is specified, - // this is used as the cache's DNS refresh rate when DNS requests are failing. If this setting is - // not specified, the failure refresh rate defaults to the dns_refresh_rate. - DnsFailureRefreshRate *v3.Cluster_RefreshRate `protobuf:"bytes,6,opt,name=dns_failure_refresh_rate,json=dnsFailureRefreshRate,proto3" json:"dns_failure_refresh_rate,omitempty"` - // The config of circuit breakers for resolver. It provides a configurable threshold. - // Envoy will use dns cache circuit breakers with default settings even if this value is not set. - DnsCacheCircuitBreaker *DnsCacheCircuitBreakers `protobuf:"bytes,7,opt,name=dns_cache_circuit_breaker,json=dnsCacheCircuitBreaker,proto3" json:"dns_cache_circuit_breaker,omitempty"` - // Always use TCP queries instead of UDP queries for DNS lookups. - // This field is deprecated in favor of “dns_resolution_config“ - // which aggregates all of the DNS resolver configuration in a single message. - // - // Deprecated: Marked as deprecated in envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto. - UseTcpForDnsLookups bool `protobuf:"varint,8,opt,name=use_tcp_for_dns_lookups,json=useTcpForDnsLookups,proto3" json:"use_tcp_for_dns_lookups,omitempty"` - // DNS resolution configuration which includes the underlying dns resolver addresses and options. - // This field is deprecated in favor of - // :ref:`typed_dns_resolver_config `. - // - // Deprecated: Marked as deprecated in envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto. - DnsResolutionConfig *v31.DnsResolutionConfig `protobuf:"bytes,9,opt,name=dns_resolution_config,json=dnsResolutionConfig,proto3" json:"dns_resolution_config,omitempty"` - // DNS resolver type configuration extension. This extension can be used to configure c-ares, apple, - // or any other DNS resolver types and the related parameters. - // For example, an object of - // :ref:`CaresDnsResolverConfig ` - // can be packed into this “typed_dns_resolver_config“. This configuration replaces the - // :ref:`dns_resolution_config ` - // configuration. - // During the transition period when both “dns_resolution_config“ and “typed_dns_resolver_config“ exists, - // when “typed_dns_resolver_config“ is in place, Envoy will use it and ignore “dns_resolution_config“. - // When “typed_dns_resolver_config“ is missing, the default behavior is in place. - // [#extension-category: envoy.network.dns_resolver] - TypedDnsResolverConfig *v31.TypedExtensionConfig `protobuf:"bytes,12,opt,name=typed_dns_resolver_config,json=typedDnsResolverConfig,proto3" json:"typed_dns_resolver_config,omitempty"` - // Hostnames that should be preresolved into the cache upon creation. This might provide a - // performance improvement, in the form of cache hits, for hostnames that are going to be - // resolved during steady state and are known at config load time. - PreresolveHostnames []*v31.SocketAddress `protobuf:"bytes,10,rep,name=preresolve_hostnames,json=preresolveHostnames,proto3" json:"preresolve_hostnames,omitempty"` - // The timeout used for DNS queries. This timeout is independent of any timeout and retry policy - // used by the underlying DNS implementation (e.g., c-areas and Apple DNS) which are opaque. - // Setting this timeout will ensure that queries succeed or fail within the specified time frame - // and are then retried using the standard refresh rates. Setting it to 0 will disable the Envoy DNS - // query timeout and use the underlying DNS implementation timeout. Defaults to 5s if not set. - DnsQueryTimeout *durationpb.Duration `protobuf:"bytes,11,opt,name=dns_query_timeout,json=dnsQueryTimeout,proto3" json:"dns_query_timeout,omitempty"` - // Configuration to flush the DNS cache to long term storage. - KeyValueConfig *v32.KeyValueStoreConfig `protobuf:"bytes,13,opt,name=key_value_config,json=keyValueConfig,proto3" json:"key_value_config,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *DnsCacheConfig) Reset() { - *x = DnsCacheConfig{} - mi := &file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *DnsCacheConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*DnsCacheConfig) ProtoMessage() {} - -func (x *DnsCacheConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use DnsCacheConfig.ProtoReflect.Descriptor instead. -func (*DnsCacheConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescGZIP(), []int{1} -} - -func (x *DnsCacheConfig) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *DnsCacheConfig) GetDnsLookupFamily() v3.Cluster_DnsLookupFamily { - if x != nil { - return x.DnsLookupFamily - } - return v3.Cluster_DnsLookupFamily(0) -} - -func (x *DnsCacheConfig) GetDnsRefreshRate() *durationpb.Duration { - if x != nil { - return x.DnsRefreshRate - } - return nil -} - -func (x *DnsCacheConfig) GetDnsMinRefreshRate() *durationpb.Duration { - if x != nil { - return x.DnsMinRefreshRate - } - return nil -} - -func (x *DnsCacheConfig) GetHostTtl() *durationpb.Duration { - if x != nil { - return x.HostTtl - } - return nil -} - -func (x *DnsCacheConfig) GetMaxHosts() *wrapperspb.UInt32Value { - if x != nil { - return x.MaxHosts - } - return nil -} - -func (x *DnsCacheConfig) GetDisableDnsRefreshOnFailure() bool { - if x != nil { - return x.DisableDnsRefreshOnFailure - } - return false -} - -func (x *DnsCacheConfig) GetDnsFailureRefreshRate() *v3.Cluster_RefreshRate { - if x != nil { - return x.DnsFailureRefreshRate - } - return nil -} - -func (x *DnsCacheConfig) GetDnsCacheCircuitBreaker() *DnsCacheCircuitBreakers { - if x != nil { - return x.DnsCacheCircuitBreaker - } - return nil -} - -// Deprecated: Marked as deprecated in envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto. -func (x *DnsCacheConfig) GetUseTcpForDnsLookups() bool { - if x != nil { - return x.UseTcpForDnsLookups - } - return false -} - -// Deprecated: Marked as deprecated in envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto. -func (x *DnsCacheConfig) GetDnsResolutionConfig() *v31.DnsResolutionConfig { - if x != nil { - return x.DnsResolutionConfig - } - return nil -} - -func (x *DnsCacheConfig) GetTypedDnsResolverConfig() *v31.TypedExtensionConfig { - if x != nil { - return x.TypedDnsResolverConfig - } - return nil -} - -func (x *DnsCacheConfig) GetPreresolveHostnames() []*v31.SocketAddress { - if x != nil { - return x.PreresolveHostnames - } - return nil -} - -func (x *DnsCacheConfig) GetDnsQueryTimeout() *durationpb.Duration { - if x != nil { - return x.DnsQueryTimeout - } - return nil -} - -func (x *DnsCacheConfig) GetKeyValueConfig() *v32.KeyValueStoreConfig { - if x != nil { - return x.KeyValueConfig - } - return nil -} - -var File_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto protoreflect.FileDescriptor - -const file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDesc = "" + - "\n" + - "@envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto\x120envoy.extensions.common.dynamic_forward_proxy.v3\x1a%envoy/config/cluster/v3/cluster.proto\x1a-envoy/config/common/key_value/v3/config.proto\x1a\"envoy/config/core/v3/address.proto\x1a$envoy/config/core/v3/extension.proto\x1a#envoy/config/core/v3/resolver.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1egoogle/protobuf/wrappers.proto\x1a#envoy/annotations/deprecation.proto\x1a\x1dudpa/annotations/status.proto\x1a!udpa/annotations/versioning.proto\x1a\x17validate/validate.proto\"i\n" + - "\x17DnsCacheCircuitBreakers\x12N\n" + - "\x14max_pending_requests\x18\x01 \x01(\v2\x1c.google.protobuf.UInt32ValueR\x12maxPendingRequests\"\xdf\n" + - "\n" + - "\x0eDnsCacheConfig\x12\x1b\n" + - "\x04name\x18\x01 \x01(\tB\a\xfaB\x04r\x02\x10\x01R\x04name\x12f\n" + - "\x11dns_lookup_family\x18\x02 \x01(\x0e20.envoy.config.cluster.v3.Cluster.DnsLookupFamilyB\b\xfaB\x05\x82\x01\x02\x10\x01R\x0fdnsLookupFamily\x12Q\n" + - "\x10dns_refresh_rate\x18\x03 \x01(\v2\x19.google.protobuf.DurationB\f\xfaB\t\xaa\x01\x062\x04\x10\xc0\x84=R\x0ednsRefreshRate\x12V\n" + - "\x14dns_min_refresh_rate\x18\x0e \x01(\v2\x19.google.protobuf.DurationB\n" + - "\xfaB\a\xaa\x01\x042\x02\b\x01R\x11dnsMinRefreshRate\x12>\n" + - "\bhost_ttl\x18\x04 \x01(\v2\x19.google.protobuf.DurationB\b\xfaB\x05\xaa\x01\x02*\x00R\ahostTtl\x12B\n" + - "\tmax_hosts\x18\x05 \x01(\v2\x1c.google.protobuf.UInt32ValueB\a\xfaB\x04*\x02 \x00R\bmaxHosts\x12B\n" + - "\x1edisable_dns_refresh_on_failure\x18\x0f \x01(\bR\x1adisableDnsRefreshOnFailure\x12e\n" + - "\x18dns_failure_refresh_rate\x18\x06 \x01(\v2,.envoy.config.cluster.v3.Cluster.RefreshRateR\x15dnsFailureRefreshRate\x12\x84\x01\n" + - "\x19dns_cache_circuit_breaker\x18\a \x01(\v2I.envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheCircuitBreakersR\x16dnsCacheCircuitBreaker\x12A\n" + - "\x17use_tcp_for_dns_lookups\x18\b \x01(\bB\v\x92dž\xd8\x04\x033.0\x18\x01R\x13useTcpForDnsLookups\x12j\n" + - "\x15dns_resolution_config\x18\t \x01(\v2).envoy.config.core.v3.DnsResolutionConfigB\v\x92dž\xd8\x04\x033.0\x18\x01R\x13dnsResolutionConfig\x12e\n" + - "\x19typed_dns_resolver_config\x18\f \x01(\v2*.envoy.config.core.v3.TypedExtensionConfigR\x16typedDnsResolverConfig\x12V\n" + - "\x14preresolve_hostnames\x18\n" + - " \x03(\v2#.envoy.config.core.v3.SocketAddressR\x13preresolveHostnames\x12O\n" + - "\x11dns_query_timeout\x18\v \x01(\v2\x19.google.protobuf.DurationB\b\xfaB\x05\xaa\x01\x022\x00R\x0fdnsQueryTimeout\x12_\n" + - "\x10key_value_config\x18\r \x01(\v25.envoy.config.common.key_value.v3.KeyValueStoreConfigR\x0ekeyValueConfig:G\x9aň\x1eB\n" + - "@envoy.config.common.dynamic_forward_proxy.v2alpha.DnsCacheConfigB\xca\x01\xba\x80\xc8\xd1\x06\x02\x10\x02\n" + - ">io.envoyproxy.envoy.extensions.common.dynamic_forward_proxy.v3B\rDnsCacheProtoP\x01Zogithub.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3;dynamic_forward_proxyv3b\x06proto3" - -var ( - file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescOnce sync.Once - file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescData []byte -) - -func file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescGZIP() []byte { - file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescOnce.Do(func() { - file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDesc), len(file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDesc))) - }) - return file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDescData -} - -var file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_goTypes = []any{ - (*DnsCacheCircuitBreakers)(nil), // 0: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheCircuitBreakers - (*DnsCacheConfig)(nil), // 1: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig - (*wrapperspb.UInt32Value)(nil), // 2: google.protobuf.UInt32Value - (v3.Cluster_DnsLookupFamily)(0), // 3: envoy.config.cluster.v3.Cluster.DnsLookupFamily - (*durationpb.Duration)(nil), // 4: google.protobuf.Duration - (*v3.Cluster_RefreshRate)(nil), // 5: envoy.config.cluster.v3.Cluster.RefreshRate - (*v31.DnsResolutionConfig)(nil), // 6: envoy.config.core.v3.DnsResolutionConfig - (*v31.TypedExtensionConfig)(nil), // 7: envoy.config.core.v3.TypedExtensionConfig - (*v31.SocketAddress)(nil), // 8: envoy.config.core.v3.SocketAddress - (*v32.KeyValueStoreConfig)(nil), // 9: envoy.config.common.key_value.v3.KeyValueStoreConfig -} -var file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_depIdxs = []int32{ - 2, // 0: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheCircuitBreakers.max_pending_requests:type_name -> google.protobuf.UInt32Value - 3, // 1: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_lookup_family:type_name -> envoy.config.cluster.v3.Cluster.DnsLookupFamily - 4, // 2: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_refresh_rate:type_name -> google.protobuf.Duration - 4, // 3: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_min_refresh_rate:type_name -> google.protobuf.Duration - 4, // 4: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.host_ttl:type_name -> google.protobuf.Duration - 2, // 5: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.max_hosts:type_name -> google.protobuf.UInt32Value - 5, // 6: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_failure_refresh_rate:type_name -> envoy.config.cluster.v3.Cluster.RefreshRate - 0, // 7: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_cache_circuit_breaker:type_name -> envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheCircuitBreakers - 6, // 8: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_resolution_config:type_name -> envoy.config.core.v3.DnsResolutionConfig - 7, // 9: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.typed_dns_resolver_config:type_name -> envoy.config.core.v3.TypedExtensionConfig - 8, // 10: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.preresolve_hostnames:type_name -> envoy.config.core.v3.SocketAddress - 4, // 11: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.dns_query_timeout:type_name -> google.protobuf.Duration - 9, // 12: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig.key_value_config:type_name -> envoy.config.common.key_value.v3.KeyValueStoreConfig - 13, // [13:13] is the sub-list for method output_type - 13, // [13:13] is the sub-list for method input_type - 13, // [13:13] is the sub-list for extension type_name - 13, // [13:13] is the sub-list for extension extendee - 0, // [0:13] is the sub-list for field type_name -} - -func init() { file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_init() } -func file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_init() { - if File_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDesc), len(file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_rawDesc)), - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_goTypes, - DependencyIndexes: file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_depIdxs, - MessageInfos: file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_msgTypes, - }.Build() - File_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto = out.File - file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_goTypes = nil - file_envoy_extensions_common_dynamic_forward_proxy_v3_dns_cache_proto_depIdxs = nil -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.validate.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.validate.go deleted file mode 100644 index d0bb5fdba..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.pb.validate.go +++ /dev/null @@ -1,612 +0,0 @@ -//go:build !disable_pgv -// Code generated by protoc-gen-validate. DO NOT EDIT. -// source: envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto - -package dynamic_forward_proxyv3 - -import ( - "bytes" - "errors" - "fmt" - "net" - "net/mail" - "net/url" - "regexp" - "sort" - "strings" - "time" - "unicode/utf8" - - "google.golang.org/protobuf/types/known/anypb" - - v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" -) - -// ensure the imports are used -var ( - _ = bytes.MinRead - _ = errors.New("") - _ = fmt.Print - _ = utf8.UTFMax - _ = (*regexp.Regexp)(nil) - _ = (*strings.Reader)(nil) - _ = net.IPv4len - _ = time.Duration(0) - _ = (*url.URL)(nil) - _ = (*mail.Address)(nil) - _ = anypb.Any{} - _ = sort.Sort - - _ = v3.Cluster_DnsLookupFamily(0) -) - -// Validate checks the field values on DnsCacheCircuitBreakers with the rules -// defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *DnsCacheCircuitBreakers) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on DnsCacheCircuitBreakers with the -// rules defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// DnsCacheCircuitBreakersMultiError, or nil if none found. -func (m *DnsCacheCircuitBreakers) ValidateAll() error { - return m.validate(true) -} - -func (m *DnsCacheCircuitBreakers) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - if all { - switch v := interface{}(m.GetMaxPendingRequests()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheCircuitBreakersValidationError{ - field: "MaxPendingRequests", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheCircuitBreakersValidationError{ - field: "MaxPendingRequests", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetMaxPendingRequests()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheCircuitBreakersValidationError{ - field: "MaxPendingRequests", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if len(errors) > 0 { - return DnsCacheCircuitBreakersMultiError(errors) - } - - return nil -} - -// DnsCacheCircuitBreakersMultiError is an error wrapping multiple validation -// errors returned by DnsCacheCircuitBreakers.ValidateAll() if the designated -// constraints aren't met. -type DnsCacheCircuitBreakersMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m DnsCacheCircuitBreakersMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m DnsCacheCircuitBreakersMultiError) AllErrors() []error { return m } - -// DnsCacheCircuitBreakersValidationError is the validation error returned by -// DnsCacheCircuitBreakers.Validate if the designated constraints aren't met. -type DnsCacheCircuitBreakersValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e DnsCacheCircuitBreakersValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e DnsCacheCircuitBreakersValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e DnsCacheCircuitBreakersValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e DnsCacheCircuitBreakersValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e DnsCacheCircuitBreakersValidationError) ErrorName() string { - return "DnsCacheCircuitBreakersValidationError" -} - -// Error satisfies the builtin error interface -func (e DnsCacheCircuitBreakersValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sDnsCacheCircuitBreakers.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = DnsCacheCircuitBreakersValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = DnsCacheCircuitBreakersValidationError{} - -// Validate checks the field values on DnsCacheConfig with the rules defined in -// the proto definition for this message. If any rules are violated, the first -// error encountered is returned, or nil if there are no violations. -func (m *DnsCacheConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on DnsCacheConfig with the rules defined -// in the proto definition for this message. If any rules are violated, the -// result is a list of violation errors wrapped in DnsCacheConfigMultiError, -// or nil if none found. -func (m *DnsCacheConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *DnsCacheConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - if utf8.RuneCountInString(m.GetName()) < 1 { - err := DnsCacheConfigValidationError{ - field: "Name", - reason: "value length must be at least 1 runes", - } - if !all { - return err - } - errors = append(errors, err) - } - - if _, ok := v3.Cluster_DnsLookupFamily_name[int32(m.GetDnsLookupFamily())]; !ok { - err := DnsCacheConfigValidationError{ - field: "DnsLookupFamily", - reason: "value must be one of the defined enum values", - } - if !all { - return err - } - errors = append(errors, err) - } - - if d := m.GetDnsRefreshRate(); d != nil { - dur, err := d.AsDuration(), d.CheckValid() - if err != nil { - err = DnsCacheConfigValidationError{ - field: "DnsRefreshRate", - reason: "value is not a valid duration", - cause: err, - } - if !all { - return err - } - errors = append(errors, err) - } else { - - gte := time.Duration(0*time.Second + 1000000*time.Nanosecond) - - if dur < gte { - err := DnsCacheConfigValidationError{ - field: "DnsRefreshRate", - reason: "value must be greater than or equal to 1ms", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - } - - if d := m.GetDnsMinRefreshRate(); d != nil { - dur, err := d.AsDuration(), d.CheckValid() - if err != nil { - err = DnsCacheConfigValidationError{ - field: "DnsMinRefreshRate", - reason: "value is not a valid duration", - cause: err, - } - if !all { - return err - } - errors = append(errors, err) - } else { - - gte := time.Duration(1*time.Second + 0*time.Nanosecond) - - if dur < gte { - err := DnsCacheConfigValidationError{ - field: "DnsMinRefreshRate", - reason: "value must be greater than or equal to 1s", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - } - - if d := m.GetHostTtl(); d != nil { - dur, err := d.AsDuration(), d.CheckValid() - if err != nil { - err = DnsCacheConfigValidationError{ - field: "HostTtl", - reason: "value is not a valid duration", - cause: err, - } - if !all { - return err - } - errors = append(errors, err) - } else { - - gt := time.Duration(0*time.Second + 0*time.Nanosecond) - - if dur <= gt { - err := DnsCacheConfigValidationError{ - field: "HostTtl", - reason: "value must be greater than 0s", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - } - - if wrapper := m.GetMaxHosts(); wrapper != nil { - - if wrapper.GetValue() <= 0 { - err := DnsCacheConfigValidationError{ - field: "MaxHosts", - reason: "value must be greater than 0", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - - // no validation rules for DisableDnsRefreshOnFailure - - if all { - switch v := interface{}(m.GetDnsFailureRefreshRate()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "DnsFailureRefreshRate", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "DnsFailureRefreshRate", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetDnsFailureRefreshRate()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheConfigValidationError{ - field: "DnsFailureRefreshRate", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if all { - switch v := interface{}(m.GetDnsCacheCircuitBreaker()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "DnsCacheCircuitBreaker", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "DnsCacheCircuitBreaker", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetDnsCacheCircuitBreaker()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheConfigValidationError{ - field: "DnsCacheCircuitBreaker", - reason: "embedded message failed validation", - cause: err, - } - } - } - - // no validation rules for UseTcpForDnsLookups - - if all { - switch v := interface{}(m.GetDnsResolutionConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "DnsResolutionConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "DnsResolutionConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetDnsResolutionConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheConfigValidationError{ - field: "DnsResolutionConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if all { - switch v := interface{}(m.GetTypedDnsResolverConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "TypedDnsResolverConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "TypedDnsResolverConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetTypedDnsResolverConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheConfigValidationError{ - field: "TypedDnsResolverConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - for idx, item := range m.GetPreresolveHostnames() { - _, _ = idx, item - - if all { - switch v := interface{}(item).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: fmt.Sprintf("PreresolveHostnames[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: fmt.Sprintf("PreresolveHostnames[%v]", idx), - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheConfigValidationError{ - field: fmt.Sprintf("PreresolveHostnames[%v]", idx), - reason: "embedded message failed validation", - cause: err, - } - } - } - - } - - if d := m.GetDnsQueryTimeout(); d != nil { - dur, err := d.AsDuration(), d.CheckValid() - if err != nil { - err = DnsCacheConfigValidationError{ - field: "DnsQueryTimeout", - reason: "value is not a valid duration", - cause: err, - } - if !all { - return err - } - errors = append(errors, err) - } else { - - gte := time.Duration(0*time.Second + 0*time.Nanosecond) - - if dur < gte { - err := DnsCacheConfigValidationError{ - field: "DnsQueryTimeout", - reason: "value must be greater than or equal to 0s", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - } - - if all { - switch v := interface{}(m.GetKeyValueConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "KeyValueConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, DnsCacheConfigValidationError{ - field: "KeyValueConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetKeyValueConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return DnsCacheConfigValidationError{ - field: "KeyValueConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if len(errors) > 0 { - return DnsCacheConfigMultiError(errors) - } - - return nil -} - -// DnsCacheConfigMultiError is an error wrapping multiple validation errors -// returned by DnsCacheConfig.ValidateAll() if the designated constraints -// aren't met. -type DnsCacheConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m DnsCacheConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m DnsCacheConfigMultiError) AllErrors() []error { return m } - -// DnsCacheConfigValidationError is the validation error returned by -// DnsCacheConfig.Validate if the designated constraints aren't met. -type DnsCacheConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e DnsCacheConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e DnsCacheConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e DnsCacheConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e DnsCacheConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e DnsCacheConfigValidationError) ErrorName() string { return "DnsCacheConfigValidationError" } - -// Error satisfies the builtin error interface -func (e DnsCacheConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sDnsCacheConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = DnsCacheConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = DnsCacheConfigValidationError{} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache_vtproto.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache_vtproto.pb.go deleted file mode 100644 index a4be6cdc2..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache_vtproto.pb.go +++ /dev/null @@ -1,415 +0,0 @@ -//go:build vtprotobuf -// +build vtprotobuf - -// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// source: envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto - -package dynamic_forward_proxyv3 - -import ( - protohelpers "github.com/planetscale/vtprotobuf/protohelpers" - durationpb "github.com/planetscale/vtprotobuf/types/known/durationpb" - wrapperspb "github.com/planetscale/vtprotobuf/types/known/wrapperspb" - proto "google.golang.org/protobuf/proto" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -func (m *DnsCacheCircuitBreakers) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DnsCacheCircuitBreakers) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *DnsCacheCircuitBreakers) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.MaxPendingRequests != nil { - size, err := (*wrapperspb.UInt32Value)(m.MaxPendingRequests).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *DnsCacheConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *DnsCacheConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *DnsCacheConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.DisableDnsRefreshOnFailure { - i-- - if m.DisableDnsRefreshOnFailure { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x78 - } - if m.DnsMinRefreshRate != nil { - size, err := (*durationpb.Duration)(m.DnsMinRefreshRate).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x72 - } - if m.KeyValueConfig != nil { - if vtmsg, ok := interface{}(m.KeyValueConfig).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.KeyValueConfig) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x6a - } - if m.TypedDnsResolverConfig != nil { - if vtmsg, ok := interface{}(m.TypedDnsResolverConfig).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.TypedDnsResolverConfig) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x62 - } - if m.DnsQueryTimeout != nil { - size, err := (*durationpb.Duration)(m.DnsQueryTimeout).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x5a - } - if len(m.PreresolveHostnames) > 0 { - for iNdEx := len(m.PreresolveHostnames) - 1; iNdEx >= 0; iNdEx-- { - if vtmsg, ok := interface{}(m.PreresolveHostnames[iNdEx]).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.PreresolveHostnames[iNdEx]) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x52 - } - } - if m.DnsResolutionConfig != nil { - if vtmsg, ok := interface{}(m.DnsResolutionConfig).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.DnsResolutionConfig) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x4a - } - if m.UseTcpForDnsLookups { - i-- - if m.UseTcpForDnsLookups { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x40 - } - if m.DnsCacheCircuitBreaker != nil { - size, err := m.DnsCacheCircuitBreaker.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x3a - } - if m.DnsFailureRefreshRate != nil { - if vtmsg, ok := interface{}(m.DnsFailureRefreshRate).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.DnsFailureRefreshRate) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x32 - } - if m.MaxHosts != nil { - size, err := (*wrapperspb.UInt32Value)(m.MaxHosts).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x2a - } - if m.HostTtl != nil { - size, err := (*durationpb.Duration)(m.HostTtl).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x22 - } - if m.DnsRefreshRate != nil { - size, err := (*durationpb.Duration)(m.DnsRefreshRate).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x1a - } - if m.DnsLookupFamily != 0 { - i = protohelpers.EncodeVarint(dAtA, i, uint64(m.DnsLookupFamily)) - i-- - dAtA[i] = 0x10 - } - if len(m.Name) > 0 { - i -= len(m.Name) - copy(dAtA[i:], m.Name) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Name))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *DnsCacheCircuitBreakers) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.MaxPendingRequests != nil { - l = (*wrapperspb.UInt32Value)(m.MaxPendingRequests).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} - -func (m *DnsCacheConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Name) - if l > 0 { - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DnsLookupFamily != 0 { - n += 1 + protohelpers.SizeOfVarint(uint64(m.DnsLookupFamily)) - } - if m.DnsRefreshRate != nil { - l = (*durationpb.Duration)(m.DnsRefreshRate).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.HostTtl != nil { - l = (*durationpb.Duration)(m.HostTtl).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.MaxHosts != nil { - l = (*wrapperspb.UInt32Value)(m.MaxHosts).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DnsFailureRefreshRate != nil { - if size, ok := interface{}(m.DnsFailureRefreshRate).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.DnsFailureRefreshRate) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DnsCacheCircuitBreaker != nil { - l = m.DnsCacheCircuitBreaker.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.UseTcpForDnsLookups { - n += 2 - } - if m.DnsResolutionConfig != nil { - if size, ok := interface{}(m.DnsResolutionConfig).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.DnsResolutionConfig) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if len(m.PreresolveHostnames) > 0 { - for _, e := range m.PreresolveHostnames { - if size, ok := interface{}(e).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(e) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } - if m.DnsQueryTimeout != nil { - l = (*durationpb.Duration)(m.DnsQueryTimeout).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.TypedDnsResolverConfig != nil { - if size, ok := interface{}(m.TypedDnsResolverConfig).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.TypedDnsResolverConfig) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.KeyValueConfig != nil { - if size, ok := interface{}(m.KeyValueConfig).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.KeyValueConfig) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DnsMinRefreshRate != nil { - l = (*durationpb.Duration)(m.DnsMinRefreshRate).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.DisableDnsRefreshOnFailure { - n += 2 - } - n += len(m.unknownFields) - return n -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.go deleted file mode 100644 index b244f30b4..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.go +++ /dev/null @@ -1,383 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.10 -// protoc v6.33.2 -// source: envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto - -package dynamic_forward_proxyv3 - -import ( - _ "github.com/cncf/xds/go/udpa/annotations" - v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3" - _ "github.com/envoyproxy/protoc-gen-validate/validate" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - durationpb "google.golang.org/protobuf/types/known/durationpb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Configuration for the dynamic forward proxy HTTP filter. See the :ref:`architecture overview -// ` for more information. -// [#extension: envoy.filters.http.dynamic_forward_proxy] -type FilterConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to ImplementationSpecifier: - // - // *FilterConfig_DnsCacheConfig - // *FilterConfig_SubClusterConfig - ImplementationSpecifier isFilterConfig_ImplementationSpecifier `protobuf_oneof:"implementation_specifier"` - // When this flag is set, the filter will add the resolved upstream address in the filter - // state. The state should be saved with key - // “envoy.stream.upstream_address“ (See - // :repo:`upstream_address.h`). - SaveUpstreamAddress bool `protobuf:"varint,2,opt,name=save_upstream_address,json=saveUpstreamAddress,proto3" json:"save_upstream_address,omitempty"` - // When this flag is set, the filter will check for the “envoy.upstream.dynamic_host“ - // and/or “envoy.upstream.dynamic_port“ filter state values before using the HTTP - // Host header for DNS resolution. This provides consistency with the - // :ref:`SNI dynamic forward proxy ` and - // :ref:`UDP dynamic forward proxy ` - // filters behavior when enabled. - // - // If the flag is not set (default), the filter will use the HTTP Host header - // for DNS resolution, maintaining backward compatibility. - AllowDynamicHostFromFilterState bool `protobuf:"varint,4,opt,name=allow_dynamic_host_from_filter_state,json=allowDynamicHostFromFilterState,proto3" json:"allow_dynamic_host_from_filter_state,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *FilterConfig) Reset() { - *x = FilterConfig{} - mi := &file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *FilterConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*FilterConfig) ProtoMessage() {} - -func (x *FilterConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use FilterConfig.ProtoReflect.Descriptor instead. -func (*FilterConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescGZIP(), []int{0} -} - -func (x *FilterConfig) GetImplementationSpecifier() isFilterConfig_ImplementationSpecifier { - if x != nil { - return x.ImplementationSpecifier - } - return nil -} - -func (x *FilterConfig) GetDnsCacheConfig() *v3.DnsCacheConfig { - if x != nil { - if x, ok := x.ImplementationSpecifier.(*FilterConfig_DnsCacheConfig); ok { - return x.DnsCacheConfig - } - } - return nil -} - -func (x *FilterConfig) GetSubClusterConfig() *SubClusterConfig { - if x != nil { - if x, ok := x.ImplementationSpecifier.(*FilterConfig_SubClusterConfig); ok { - return x.SubClusterConfig - } - } - return nil -} - -func (x *FilterConfig) GetSaveUpstreamAddress() bool { - if x != nil { - return x.SaveUpstreamAddress - } - return false -} - -func (x *FilterConfig) GetAllowDynamicHostFromFilterState() bool { - if x != nil { - return x.AllowDynamicHostFromFilterState - } - return false -} - -type isFilterConfig_ImplementationSpecifier interface { - isFilterConfig_ImplementationSpecifier() -} - -type FilterConfig_DnsCacheConfig struct { - // The DNS cache configuration that the filter will attach to. Note this configuration must - // match that of associated :ref:`dynamic forward proxy cluster configuration - // `. - DnsCacheConfig *v3.DnsCacheConfig `protobuf:"bytes,1,opt,name=dns_cache_config,json=dnsCacheConfig,proto3,oneof"` -} - -type FilterConfig_SubClusterConfig struct { - // The configuration that the filter will use, when the related dynamic forward proxy cluster enabled - // sub clusters. - SubClusterConfig *SubClusterConfig `protobuf:"bytes,3,opt,name=sub_cluster_config,json=subClusterConfig,proto3,oneof"` -} - -func (*FilterConfig_DnsCacheConfig) isFilterConfig_ImplementationSpecifier() {} - -func (*FilterConfig_SubClusterConfig) isFilterConfig_ImplementationSpecifier() {} - -// Per route Configuration for the dynamic forward proxy HTTP filter. -type PerRouteConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Types that are valid to be assigned to HostRewriteSpecifier: - // - // *PerRouteConfig_HostRewriteLiteral - // *PerRouteConfig_HostRewriteHeader - HostRewriteSpecifier isPerRouteConfig_HostRewriteSpecifier `protobuf_oneof:"host_rewrite_specifier"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *PerRouteConfig) Reset() { - *x = PerRouteConfig{} - mi := &file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *PerRouteConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PerRouteConfig) ProtoMessage() {} - -func (x *PerRouteConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PerRouteConfig.ProtoReflect.Descriptor instead. -func (*PerRouteConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescGZIP(), []int{1} -} - -func (x *PerRouteConfig) GetHostRewriteSpecifier() isPerRouteConfig_HostRewriteSpecifier { - if x != nil { - return x.HostRewriteSpecifier - } - return nil -} - -func (x *PerRouteConfig) GetHostRewriteLiteral() string { - if x != nil { - if x, ok := x.HostRewriteSpecifier.(*PerRouteConfig_HostRewriteLiteral); ok { - return x.HostRewriteLiteral - } - } - return "" -} - -func (x *PerRouteConfig) GetHostRewriteHeader() string { - if x != nil { - if x, ok := x.HostRewriteSpecifier.(*PerRouteConfig_HostRewriteHeader); ok { - return x.HostRewriteHeader - } - } - return "" -} - -type isPerRouteConfig_HostRewriteSpecifier interface { - isPerRouteConfig_HostRewriteSpecifier() -} - -type PerRouteConfig_HostRewriteLiteral struct { - // Indicates that before DNS lookup, the host header will be swapped with - // this value. If not set or empty, the original host header value - // will be used and no rewrite will happen. - // - // .. note:: - // - // This rewrite affects both DNS lookup and host header forwarding. However, this option shouldn't be used with - // :ref:`HCM host rewrite header ` given that - // the value set here would be used for DNS lookups whereas the value set in the HCM would be used for host - // header forwarding which might not be the desired outcome. - HostRewriteLiteral string `protobuf:"bytes,1,opt,name=host_rewrite_literal,json=hostRewriteLiteral,proto3,oneof"` -} - -type PerRouteConfig_HostRewriteHeader struct { - // Indicates that before DNS lookup, the host header will be swapped with - // the value of this header. If not set or empty, the original host header - // value will be used and no rewrite will happen. - // - // .. note:: - // - // This rewrite affects both DNS lookup and host header forwarding. However, this option shouldn't be used with - // :ref:`HCM host rewrite header ` given that - // the value set here would be used for DNS lookups whereas the value set in the HCM would be used for host - // header forwarding which might not be the desired outcome. - // - // .. note:: - // - // If the header appears multiple times only the first value is used. - HostRewriteHeader string `protobuf:"bytes,2,opt,name=host_rewrite_header,json=hostRewriteHeader,proto3,oneof"` -} - -func (*PerRouteConfig_HostRewriteLiteral) isPerRouteConfig_HostRewriteSpecifier() {} - -func (*PerRouteConfig_HostRewriteHeader) isPerRouteConfig_HostRewriteSpecifier() {} - -type SubClusterConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // The timeout used for sub cluster initialization. Defaults to **5s** if not set. - ClusterInitTimeout *durationpb.Duration `protobuf:"bytes,3,opt,name=cluster_init_timeout,json=clusterInitTimeout,proto3" json:"cluster_init_timeout,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SubClusterConfig) Reset() { - *x = SubClusterConfig{} - mi := &file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SubClusterConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SubClusterConfig) ProtoMessage() {} - -func (x *SubClusterConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[2] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SubClusterConfig.ProtoReflect.Descriptor instead. -func (*SubClusterConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescGZIP(), []int{2} -} - -func (x *SubClusterConfig) GetClusterInitTimeout() *durationpb.Duration { - if x != nil { - return x.ClusterInitTimeout - } - return nil -} - -var File_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto protoreflect.FileDescriptor - -const file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDesc = "" + - "\n" + - "Renvoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto\x126envoy.extensions.filters.http.dynamic_forward_proxy.v3\x1a@envoy/extensions/common/dynamic_forward_proxy/v3/dns_cache.proto\x1a\x1egoogle/protobuf/duration.proto\x1a\x1dudpa/annotations/status.proto\x1a!udpa/annotations/versioning.proto\x1a\x17validate/validate.proto\"\xe1\x03\n" + - "\fFilterConfig\x12l\n" + - "\x10dns_cache_config\x18\x01 \x01(\v2@.envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfigH\x00R\x0ednsCacheConfig\x12x\n" + - "\x12sub_cluster_config\x18\x03 \x01(\v2H.envoy.extensions.filters.http.dynamic_forward_proxy.v3.SubClusterConfigH\x00R\x10subClusterConfig\x122\n" + - "\x15save_upstream_address\x18\x02 \x01(\bR\x13saveUpstreamAddress\x12M\n" + - "$allow_dynamic_host_from_filter_state\x18\x04 \x01(\bR\x1fallowDynamicHostFromFilterState:J\x9aň\x1eE\n" + - "Cenvoy.config.filter.http.dynamic_forward_proxy.v2alpha.FilterConfigB\x1a\n" + - "\x18implementation_specifier\"\xde\x01\n" + - "\x0ePerRouteConfig\x122\n" + - "\x14host_rewrite_literal\x18\x01 \x01(\tH\x00R\x12hostRewriteLiteral\x120\n" + - "\x13host_rewrite_header\x18\x02 \x01(\tH\x00R\x11hostRewriteHeader:L\x9aň\x1eG\n" + - "Eenvoy.config.filter.http.dynamic_forward_proxy.v2alpha.PerRouteConfigB\x18\n" + - "\x16host_rewrite_specifier\"i\n" + - "\x10SubClusterConfig\x12U\n" + - "\x14cluster_init_timeout\x18\x03 \x01(\v2\x19.google.protobuf.DurationB\b\xfaB\x05\xaa\x01\x02*\x00R\x12clusterInitTimeoutB\xe1\x01\xba\x80\xc8\xd1\x06\x02\x10\x02\n" + - "Dio.envoyproxy.envoy.extensions.filters.http.dynamic_forward_proxy.v3B\x18DynamicForwardProxyProtoP\x01Zugithub.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3;dynamic_forward_proxyv3b\x06proto3" - -var ( - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescOnce sync.Once - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescData []byte -) - -func file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescGZIP() []byte { - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescOnce.Do(func() { - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDesc), len(file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDesc))) - }) - return file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDescData -} - -var file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_goTypes = []any{ - (*FilterConfig)(nil), // 0: envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig - (*PerRouteConfig)(nil), // 1: envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig - (*SubClusterConfig)(nil), // 2: envoy.extensions.filters.http.dynamic_forward_proxy.v3.SubClusterConfig - (*v3.DnsCacheConfig)(nil), // 3: envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig - (*durationpb.Duration)(nil), // 4: google.protobuf.Duration -} -var file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_depIdxs = []int32{ - 3, // 0: envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig.dns_cache_config:type_name -> envoy.extensions.common.dynamic_forward_proxy.v3.DnsCacheConfig - 2, // 1: envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig.sub_cluster_config:type_name -> envoy.extensions.filters.http.dynamic_forward_proxy.v3.SubClusterConfig - 4, // 2: envoy.extensions.filters.http.dynamic_forward_proxy.v3.SubClusterConfig.cluster_init_timeout:type_name -> google.protobuf.Duration - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_init() -} -func file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_init() { - if File_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto != nil { - return - } - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[0].OneofWrappers = []any{ - (*FilterConfig_DnsCacheConfig)(nil), - (*FilterConfig_SubClusterConfig)(nil), - } - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes[1].OneofWrappers = []any{ - (*PerRouteConfig_HostRewriteLiteral)(nil), - (*PerRouteConfig_HostRewriteHeader)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDesc), len(file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_rawDesc)), - NumEnums: 0, - NumMessages: 3, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_goTypes, - DependencyIndexes: file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_depIdxs, - MessageInfos: file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_msgTypes, - }.Build() - File_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto = out.File - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_goTypes = nil - file_envoy_extensions_filters_http_dynamic_forward_proxy_v3_dynamic_forward_proxy_proto_depIdxs = nil -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.validate.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.validate.go deleted file mode 100644 index fa53c4272..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.pb.validate.go +++ /dev/null @@ -1,486 +0,0 @@ -//go:build !disable_pgv -// Code generated by protoc-gen-validate. DO NOT EDIT. -// source: envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto - -package dynamic_forward_proxyv3 - -import ( - "bytes" - "errors" - "fmt" - "net" - "net/mail" - "net/url" - "regexp" - "sort" - "strings" - "time" - "unicode/utf8" - - "google.golang.org/protobuf/types/known/anypb" -) - -// ensure the imports are used -var ( - _ = bytes.MinRead - _ = errors.New("") - _ = fmt.Print - _ = utf8.UTFMax - _ = (*regexp.Regexp)(nil) - _ = (*strings.Reader)(nil) - _ = net.IPv4len - _ = time.Duration(0) - _ = (*url.URL)(nil) - _ = (*mail.Address)(nil) - _ = anypb.Any{} - _ = sort.Sort -) - -// Validate checks the field values on FilterConfig with the rules defined in -// the proto definition for this message. If any rules are violated, the first -// error encountered is returned, or nil if there are no violations. -func (m *FilterConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on FilterConfig with the rules defined -// in the proto definition for this message. If any rules are violated, the -// result is a list of violation errors wrapped in FilterConfigMultiError, or -// nil if none found. -func (m *FilterConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *FilterConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - // no validation rules for SaveUpstreamAddress - - // no validation rules for AllowDynamicHostFromFilterState - - switch v := m.ImplementationSpecifier.(type) { - case *FilterConfig_DnsCacheConfig: - if v == nil { - err := FilterConfigValidationError{ - field: "ImplementationSpecifier", - reason: "oneof value cannot be a typed-nil", - } - if !all { - return err - } - errors = append(errors, err) - } - - if all { - switch v := interface{}(m.GetDnsCacheConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, FilterConfigValidationError{ - field: "DnsCacheConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, FilterConfigValidationError{ - field: "DnsCacheConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetDnsCacheConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return FilterConfigValidationError{ - field: "DnsCacheConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - case *FilterConfig_SubClusterConfig: - if v == nil { - err := FilterConfigValidationError{ - field: "ImplementationSpecifier", - reason: "oneof value cannot be a typed-nil", - } - if !all { - return err - } - errors = append(errors, err) - } - - if all { - switch v := interface{}(m.GetSubClusterConfig()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, FilterConfigValidationError{ - field: "SubClusterConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, FilterConfigValidationError{ - field: "SubClusterConfig", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetSubClusterConfig()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return FilterConfigValidationError{ - field: "SubClusterConfig", - reason: "embedded message failed validation", - cause: err, - } - } - } - - default: - _ = v // ensures v is used - } - - if len(errors) > 0 { - return FilterConfigMultiError(errors) - } - - return nil -} - -// FilterConfigMultiError is an error wrapping multiple validation errors -// returned by FilterConfig.ValidateAll() if the designated constraints aren't met. -type FilterConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m FilterConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m FilterConfigMultiError) AllErrors() []error { return m } - -// FilterConfigValidationError is the validation error returned by -// FilterConfig.Validate if the designated constraints aren't met. -type FilterConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e FilterConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e FilterConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e FilterConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e FilterConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e FilterConfigValidationError) ErrorName() string { return "FilterConfigValidationError" } - -// Error satisfies the builtin error interface -func (e FilterConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sFilterConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = FilterConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = FilterConfigValidationError{} - -// Validate checks the field values on PerRouteConfig with the rules defined in -// the proto definition for this message. If any rules are violated, the first -// error encountered is returned, or nil if there are no violations. -func (m *PerRouteConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on PerRouteConfig with the rules defined -// in the proto definition for this message. If any rules are violated, the -// result is a list of violation errors wrapped in PerRouteConfigMultiError, -// or nil if none found. -func (m *PerRouteConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *PerRouteConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - switch v := m.HostRewriteSpecifier.(type) { - case *PerRouteConfig_HostRewriteLiteral: - if v == nil { - err := PerRouteConfigValidationError{ - field: "HostRewriteSpecifier", - reason: "oneof value cannot be a typed-nil", - } - if !all { - return err - } - errors = append(errors, err) - } - // no validation rules for HostRewriteLiteral - case *PerRouteConfig_HostRewriteHeader: - if v == nil { - err := PerRouteConfigValidationError{ - field: "HostRewriteSpecifier", - reason: "oneof value cannot be a typed-nil", - } - if !all { - return err - } - errors = append(errors, err) - } - // no validation rules for HostRewriteHeader - default: - _ = v // ensures v is used - } - - if len(errors) > 0 { - return PerRouteConfigMultiError(errors) - } - - return nil -} - -// PerRouteConfigMultiError is an error wrapping multiple validation errors -// returned by PerRouteConfig.ValidateAll() if the designated constraints -// aren't met. -type PerRouteConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m PerRouteConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m PerRouteConfigMultiError) AllErrors() []error { return m } - -// PerRouteConfigValidationError is the validation error returned by -// PerRouteConfig.Validate if the designated constraints aren't met. -type PerRouteConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e PerRouteConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e PerRouteConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e PerRouteConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e PerRouteConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e PerRouteConfigValidationError) ErrorName() string { return "PerRouteConfigValidationError" } - -// Error satisfies the builtin error interface -func (e PerRouteConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sPerRouteConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = PerRouteConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = PerRouteConfigValidationError{} - -// Validate checks the field values on SubClusterConfig with the rules defined -// in the proto definition for this message. If any rules are violated, the -// first error encountered is returned, or nil if there are no violations. -func (m *SubClusterConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on SubClusterConfig with the rules -// defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// SubClusterConfigMultiError, or nil if none found. -func (m *SubClusterConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *SubClusterConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - if d := m.GetClusterInitTimeout(); d != nil { - dur, err := d.AsDuration(), d.CheckValid() - if err != nil { - err = SubClusterConfigValidationError{ - field: "ClusterInitTimeout", - reason: "value is not a valid duration", - cause: err, - } - if !all { - return err - } - errors = append(errors, err) - } else { - - gt := time.Duration(0*time.Second + 0*time.Nanosecond) - - if dur <= gt { - err := SubClusterConfigValidationError{ - field: "ClusterInitTimeout", - reason: "value must be greater than 0s", - } - if !all { - return err - } - errors = append(errors, err) - } - - } - } - - if len(errors) > 0 { - return SubClusterConfigMultiError(errors) - } - - return nil -} - -// SubClusterConfigMultiError is an error wrapping multiple validation errors -// returned by SubClusterConfig.ValidateAll() if the designated constraints -// aren't met. -type SubClusterConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m SubClusterConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m SubClusterConfigMultiError) AllErrors() []error { return m } - -// SubClusterConfigValidationError is the validation error returned by -// SubClusterConfig.Validate if the designated constraints aren't met. -type SubClusterConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e SubClusterConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e SubClusterConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e SubClusterConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e SubClusterConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e SubClusterConfigValidationError) ErrorName() string { return "SubClusterConfigValidationError" } - -// Error satisfies the builtin error interface -func (e SubClusterConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sSubClusterConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = SubClusterConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = SubClusterConfigValidationError{} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy_vtproto.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy_vtproto.pb.go deleted file mode 100644 index b81060977..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy_vtproto.pb.go +++ /dev/null @@ -1,364 +0,0 @@ -//go:build vtprotobuf -// +build vtprotobuf - -// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// source: envoy/extensions/filters/http/dynamic_forward_proxy/v3/dynamic_forward_proxy.proto - -package dynamic_forward_proxyv3 - -import ( - protohelpers "github.com/planetscale/vtprotobuf/protohelpers" - durationpb "github.com/planetscale/vtprotobuf/types/known/durationpb" - proto "google.golang.org/protobuf/proto" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -func (m *FilterConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *FilterConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *FilterConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.AllowDynamicHostFromFilterState { - i-- - if m.AllowDynamicHostFromFilterState { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x20 - } - if msg, ok := m.ImplementationSpecifier.(*FilterConfig_SubClusterConfig); ok { - size, err := msg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - } - if m.SaveUpstreamAddress { - i-- - if m.SaveUpstreamAddress { - dAtA[i] = 1 - } else { - dAtA[i] = 0 - } - i-- - dAtA[i] = 0x10 - } - if msg, ok := m.ImplementationSpecifier.(*FilterConfig_DnsCacheConfig); ok { - size, err := msg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - } - return len(dAtA) - i, nil -} - -func (m *FilterConfig_DnsCacheConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *FilterConfig_DnsCacheConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - i := len(dAtA) - if m.DnsCacheConfig != nil { - if vtmsg, ok := interface{}(m.DnsCacheConfig).(interface { - MarshalToSizedBufferVTStrict([]byte) (int, error) - }); ok { - size, err := vtmsg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.DnsCacheConfig) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0xa - } else { - i = protohelpers.EncodeVarint(dAtA, i, 0) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} -func (m *FilterConfig_SubClusterConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *FilterConfig_SubClusterConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - i := len(dAtA) - if m.SubClusterConfig != nil { - size, err := m.SubClusterConfig.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x1a - } else { - i = protohelpers.EncodeVarint(dAtA, i, 0) - i-- - dAtA[i] = 0x1a - } - return len(dAtA) - i, nil -} -func (m *PerRouteConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *PerRouteConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *PerRouteConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if msg, ok := m.HostRewriteSpecifier.(*PerRouteConfig_HostRewriteHeader); ok { - size, err := msg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - } - if msg, ok := m.HostRewriteSpecifier.(*PerRouteConfig_HostRewriteLiteral); ok { - size, err := msg.MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - } - return len(dAtA) - i, nil -} - -func (m *PerRouteConfig_HostRewriteLiteral) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *PerRouteConfig_HostRewriteLiteral) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - i := len(dAtA) - i -= len(m.HostRewriteLiteral) - copy(dAtA[i:], m.HostRewriteLiteral) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.HostRewriteLiteral))) - i-- - dAtA[i] = 0xa - return len(dAtA) - i, nil -} -func (m *PerRouteConfig_HostRewriteHeader) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *PerRouteConfig_HostRewriteHeader) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - i := len(dAtA) - i -= len(m.HostRewriteHeader) - copy(dAtA[i:], m.HostRewriteHeader) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.HostRewriteHeader))) - i-- - dAtA[i] = 0x12 - return len(dAtA) - i, nil -} -func (m *SubClusterConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SubClusterConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *SubClusterConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.ClusterInitTimeout != nil { - size, err := (*durationpb.Duration)(m.ClusterInitTimeout).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x1a - } - return len(dAtA) - i, nil -} - -func (m *FilterConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if vtmsg, ok := m.ImplementationSpecifier.(interface{ SizeVT() int }); ok { - n += vtmsg.SizeVT() - } - if m.SaveUpstreamAddress { - n += 2 - } - if m.AllowDynamicHostFromFilterState { - n += 2 - } - n += len(m.unknownFields) - return n -} - -func (m *FilterConfig_DnsCacheConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.DnsCacheConfig != nil { - if size, ok := interface{}(m.DnsCacheConfig).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.DnsCacheConfig) - } - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } else { - n += 2 - } - return n -} -func (m *FilterConfig_SubClusterConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.SubClusterConfig != nil { - l = m.SubClusterConfig.SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } else { - n += 2 - } - return n -} -func (m *PerRouteConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if vtmsg, ok := m.HostRewriteSpecifier.(interface{ SizeVT() int }); ok { - n += vtmsg.SizeVT() - } - n += len(m.unknownFields) - return n -} - -func (m *PerRouteConfig_HostRewriteLiteral) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.HostRewriteLiteral) - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - return n -} -func (m *PerRouteConfig_HostRewriteHeader) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.HostRewriteHeader) - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - return n -} -func (m *SubClusterConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.ClusterInitTimeout != nil { - l = (*durationpb.Duration)(m.ClusterInitTimeout).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.go deleted file mode 100644 index 0b097d0da..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.go +++ /dev/null @@ -1,150 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.10 -// protoc v6.33.2 -// source: envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.proto - -package getaddrinfov3 - -import ( - _ "github.com/cncf/xds/go/udpa/annotations" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -// Configuration for getaddrinfo DNS resolver. This resolver will use the system's getaddrinfo() -// function to resolve hosts. -// -// .. attention:: -// -// Resolutions currently use a hard coded TTL of 60s because the getaddrinfo() API does not -// provide the actual TTL. Configuration for this can be added in the future if needed. -type GetAddrInfoDnsResolverConfig struct { - state protoimpl.MessageState `protogen:"open.v1"` - // Specifies the number of retries before the resolver gives up. If not specified, the resolver will - // retry indefinitely until it succeeds or the DNS query times out. - NumRetries *wrapperspb.UInt32Value `protobuf:"bytes,1,opt,name=num_retries,json=numRetries,proto3" json:"num_retries,omitempty"` - // Specifies the number of threads used to resolve pending DNS queries. If not specified, one thread is used. - NumResolverThreads *wrapperspb.UInt32Value `protobuf:"bytes,2,opt,name=num_resolver_threads,json=numResolverThreads,proto3" json:"num_resolver_threads,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *GetAddrInfoDnsResolverConfig) Reset() { - *x = GetAddrInfoDnsResolverConfig{} - mi := &file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *GetAddrInfoDnsResolverConfig) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*GetAddrInfoDnsResolverConfig) ProtoMessage() {} - -func (x *GetAddrInfoDnsResolverConfig) ProtoReflect() protoreflect.Message { - mi := &file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use GetAddrInfoDnsResolverConfig.ProtoReflect.Descriptor instead. -func (*GetAddrInfoDnsResolverConfig) Descriptor() ([]byte, []int) { - return file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescGZIP(), []int{0} -} - -func (x *GetAddrInfoDnsResolverConfig) GetNumRetries() *wrapperspb.UInt32Value { - if x != nil { - return x.NumRetries - } - return nil -} - -func (x *GetAddrInfoDnsResolverConfig) GetNumResolverThreads() *wrapperspb.UInt32Value { - if x != nil { - return x.NumResolverThreads - } - return nil -} - -var File_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto protoreflect.FileDescriptor - -const file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDesc = "" + - "\n" + - "Senvoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.proto\x124envoy.extensions.network.dns_resolver.getaddrinfo.v3\x1a\x1egoogle/protobuf/wrappers.proto\x1a\x1dudpa/annotations/status.proto\"\xad\x01\n" + - "\x1cGetAddrInfoDnsResolverConfig\x12=\n" + - "\vnum_retries\x18\x01 \x01(\v2\x1c.google.protobuf.UInt32ValueR\n" + - "numRetries\x12N\n" + - "\x14num_resolver_threads\x18\x02 \x01(\v2\x1c.google.protobuf.UInt32ValueR\x12numResolverThreadsB\xd6\x01\xba\x80\xc8\xd1\x06\x02\x10\x02\n" + - "Bio.envoyproxy.envoy.extensions.network.dns_resolver.getaddrinfo.v3B\x1bGetaddrinfoDnsResolverProtoP\x01Zigithub.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3;getaddrinfov3b\x06proto3" - -var ( - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescOnce sync.Once - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescData []byte -) - -func file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescGZIP() []byte { - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescOnce.Do(func() { - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDesc), len(file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDesc))) - }) - return file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDescData -} - -var file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_goTypes = []any{ - (*GetAddrInfoDnsResolverConfig)(nil), // 0: envoy.extensions.network.dns_resolver.getaddrinfo.v3.GetAddrInfoDnsResolverConfig - (*wrapperspb.UInt32Value)(nil), // 1: google.protobuf.UInt32Value -} -var file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_depIdxs = []int32{ - 1, // 0: envoy.extensions.network.dns_resolver.getaddrinfo.v3.GetAddrInfoDnsResolverConfig.num_retries:type_name -> google.protobuf.UInt32Value - 1, // 1: envoy.extensions.network.dns_resolver.getaddrinfo.v3.GetAddrInfoDnsResolverConfig.num_resolver_threads:type_name -> google.protobuf.UInt32Value - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name -} - -func init() { - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_init() -} -func file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_init() { - if File_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDesc), len(file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_rawDesc)), - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_goTypes, - DependencyIndexes: file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_depIdxs, - MessageInfos: file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_msgTypes, - }.Build() - File_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto = out.File - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_goTypes = nil - file_envoy_extensions_network_dns_resolver_getaddrinfo_v3_getaddrinfo_dns_resolver_proto_depIdxs = nil -} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.validate.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.validate.go deleted file mode 100644 index 769f7d878..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.pb.validate.go +++ /dev/null @@ -1,198 +0,0 @@ -//go:build !disable_pgv -// Code generated by protoc-gen-validate. DO NOT EDIT. -// source: envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.proto - -package getaddrinfov3 - -import ( - "bytes" - "errors" - "fmt" - "net" - "net/mail" - "net/url" - "regexp" - "sort" - "strings" - "time" - "unicode/utf8" - - "google.golang.org/protobuf/types/known/anypb" -) - -// ensure the imports are used -var ( - _ = bytes.MinRead - _ = errors.New("") - _ = fmt.Print - _ = utf8.UTFMax - _ = (*regexp.Regexp)(nil) - _ = (*strings.Reader)(nil) - _ = net.IPv4len - _ = time.Duration(0) - _ = (*url.URL)(nil) - _ = (*mail.Address)(nil) - _ = anypb.Any{} - _ = sort.Sort -) - -// Validate checks the field values on GetAddrInfoDnsResolverConfig with the -// rules defined in the proto definition for this message. If any rules are -// violated, the first error encountered is returned, or nil if there are no violations. -func (m *GetAddrInfoDnsResolverConfig) Validate() error { - return m.validate(false) -} - -// ValidateAll checks the field values on GetAddrInfoDnsResolverConfig with the -// rules defined in the proto definition for this message. If any rules are -// violated, the result is a list of violation errors wrapped in -// GetAddrInfoDnsResolverConfigMultiError, or nil if none found. -func (m *GetAddrInfoDnsResolverConfig) ValidateAll() error { - return m.validate(true) -} - -func (m *GetAddrInfoDnsResolverConfig) validate(all bool) error { - if m == nil { - return nil - } - - var errors []error - - if all { - switch v := interface{}(m.GetNumRetries()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, GetAddrInfoDnsResolverConfigValidationError{ - field: "NumRetries", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, GetAddrInfoDnsResolverConfigValidationError{ - field: "NumRetries", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetNumRetries()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return GetAddrInfoDnsResolverConfigValidationError{ - field: "NumRetries", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if all { - switch v := interface{}(m.GetNumResolverThreads()).(type) { - case interface{ ValidateAll() error }: - if err := v.ValidateAll(); err != nil { - errors = append(errors, GetAddrInfoDnsResolverConfigValidationError{ - field: "NumResolverThreads", - reason: "embedded message failed validation", - cause: err, - }) - } - case interface{ Validate() error }: - if err := v.Validate(); err != nil { - errors = append(errors, GetAddrInfoDnsResolverConfigValidationError{ - field: "NumResolverThreads", - reason: "embedded message failed validation", - cause: err, - }) - } - } - } else if v, ok := interface{}(m.GetNumResolverThreads()).(interface{ Validate() error }); ok { - if err := v.Validate(); err != nil { - return GetAddrInfoDnsResolverConfigValidationError{ - field: "NumResolverThreads", - reason: "embedded message failed validation", - cause: err, - } - } - } - - if len(errors) > 0 { - return GetAddrInfoDnsResolverConfigMultiError(errors) - } - - return nil -} - -// GetAddrInfoDnsResolverConfigMultiError is an error wrapping multiple -// validation errors returned by GetAddrInfoDnsResolverConfig.ValidateAll() if -// the designated constraints aren't met. -type GetAddrInfoDnsResolverConfigMultiError []error - -// Error returns a concatenation of all the error messages it wraps. -func (m GetAddrInfoDnsResolverConfigMultiError) Error() string { - msgs := make([]string, 0, len(m)) - for _, err := range m { - msgs = append(msgs, err.Error()) - } - return strings.Join(msgs, "; ") -} - -// AllErrors returns a list of validation violation errors. -func (m GetAddrInfoDnsResolverConfigMultiError) AllErrors() []error { return m } - -// GetAddrInfoDnsResolverConfigValidationError is the validation error returned -// by GetAddrInfoDnsResolverConfig.Validate if the designated constraints -// aren't met. -type GetAddrInfoDnsResolverConfigValidationError struct { - field string - reason string - cause error - key bool -} - -// Field function returns field value. -func (e GetAddrInfoDnsResolverConfigValidationError) Field() string { return e.field } - -// Reason function returns reason value. -func (e GetAddrInfoDnsResolverConfigValidationError) Reason() string { return e.reason } - -// Cause function returns cause value. -func (e GetAddrInfoDnsResolverConfigValidationError) Cause() error { return e.cause } - -// Key function returns key value. -func (e GetAddrInfoDnsResolverConfigValidationError) Key() bool { return e.key } - -// ErrorName returns error name. -func (e GetAddrInfoDnsResolverConfigValidationError) ErrorName() string { - return "GetAddrInfoDnsResolverConfigValidationError" -} - -// Error satisfies the builtin error interface -func (e GetAddrInfoDnsResolverConfigValidationError) Error() string { - cause := "" - if e.cause != nil { - cause = fmt.Sprintf(" | caused by: %v", e.cause) - } - - key := "" - if e.key { - key = "key for " - } - - return fmt.Sprintf( - "invalid %sGetAddrInfoDnsResolverConfig.%s: %s%s", - key, - e.field, - e.reason, - cause) -} - -var _ error = GetAddrInfoDnsResolverConfigValidationError{} - -var _ interface { - Field() string - Reason() string - Key() bool - Cause() error - ErrorName() string -} = GetAddrInfoDnsResolverConfigValidationError{} diff --git a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver_vtproto.pb.go b/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver_vtproto.pb.go deleted file mode 100644 index 5c11edf1e..000000000 --- a/vendor/github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver_vtproto.pb.go +++ /dev/null @@ -1,91 +0,0 @@ -//go:build vtprotobuf -// +build vtprotobuf - -// Code generated by protoc-gen-go-vtproto. DO NOT EDIT. -// source: envoy/extensions/network/dns_resolver/getaddrinfo/v3/getaddrinfo_dns_resolver.proto - -package getaddrinfov3 - -import ( - protohelpers "github.com/planetscale/vtprotobuf/protohelpers" - wrapperspb "github.com/planetscale/vtprotobuf/types/known/wrapperspb" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -func (m *GetAddrInfoDnsResolverConfig) MarshalVTStrict() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVTStrict(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *GetAddrInfoDnsResolverConfig) MarshalToVTStrict(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVTStrict(dAtA[:size]) -} - -func (m *GetAddrInfoDnsResolverConfig) MarshalToSizedBufferVTStrict(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.NumResolverThreads != nil { - size, err := (*wrapperspb.UInt32Value)(m.NumResolverThreads).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 - } - if m.NumRetries != nil { - size, err := (*wrapperspb.UInt32Value)(m.NumRetries).MarshalToSizedBufferVTStrict(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = protohelpers.EncodeVarint(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *GetAddrInfoDnsResolverConfig) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.NumRetries != nil { - l = (*wrapperspb.UInt32Value)(m.NumRetries).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - if m.NumResolverThreads != nil { - l = (*wrapperspb.UInt32Value)(m.NumResolverThreads).SizeVT() - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - n += len(m.unknownFields) - return n -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 0454f7a91..f4bfd1002 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -293,7 +293,6 @@ github.com/envoyproxy/go-control-plane/envoy/annotations github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3 github.com/envoyproxy/go-control-plane/envoy/config/bootstrap/v3 github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3 -github.com/envoyproxy/go-control-plane/envoy/config/common/key_value/v3 github.com/envoyproxy/go-control-plane/envoy/config/common/matcher/v3 github.com/envoyproxy/go-control-plane/envoy/config/common/mutation_rules/v3 github.com/envoyproxy/go-control-plane/envoy/config/core/v3 @@ -308,10 +307,7 @@ github.com/envoyproxy/go-control-plane/envoy/config/trace/v3 github.com/envoyproxy/go-control-plane/envoy/data/accesslog/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/stream/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3 -github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/dynamic_forward_proxy/v3 -github.com/envoyproxy/go-control-plane/envoy/extensions/common/dynamic_forward_proxy/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/filters/common/fault/v3 -github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_forward_proxy/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/fault/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/gcp_authn/v3 @@ -324,7 +320,6 @@ github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/pick_first/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/ring_hash/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/load_balancing_policies/wrr_locality/v3 -github.com/envoyproxy/go-control-plane/envoy/extensions/network/dns_resolver/getaddrinfo/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/rbac/audit_loggers/stream/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3 github.com/envoyproxy/go-control-plane/envoy/extensions/upstreams/http/v3 From d316c661d57215c771909673dbe2a0b7d648712f Mon Sep 17 00:00:00 2001 From: Lior Lieberman Date: Mon, 27 Jul 2026 14:58:04 -0700 Subject: [PATCH 4/5] networking: update docs and add an ingress e2e suite Bring the architecture and threat-model docs in line with the atunnel ingress path: the router no longer rewrites :authority to a worker pod IP and forwards over plaintext port 80, it opens an mTLS tunnel to atunnel on worker port 443, which forwards to the Actor over its private veth. Drop cmd/atenet/atenet-diagram.png. It predates the ext_proc/ORIGINAL_DST design and is now wrong in the part that matters most. The README section it illustrated gains a short accurate note about the upstream hop instead of a dangling image reference. Add an e2e suite covering the change end to end: TestActorDirectAccess asserts that the worker pod's port 80 is no longer a reachable Actor ingress path (the DNAT rule is gone) and that the same Actor still answers /readyz through atenet-router over the atunnel mTLS hop. It uses the counter demo as its fixture, so it only needs --deploy-demo-counter. internal/e2e/testmain.go picks up ParseSkippedFlags so that `go test` flags (-run, -v, ...) survive pflag parsing and reach the suite. --- cmd/atenet/README.md | 5 +- cmd/atenet/atenet-diagram.png | Bin 75853 -> 0 bytes cmd/atenet/internal/dns/README.md | 2 +- docs/architecture.md | 45 ++++--- docs/threat-model.md | 16 +-- internal/atunnel/server.go | 22 ++-- .../e2e/suites/networking/networking_test.go | 121 ++++++++++++++++++ .../e2e/suites/networking/testmain_test.go | 24 ++++ 8 files changed, 196 insertions(+), 39 deletions(-) delete mode 100644 cmd/atenet/atenet-diagram.png create mode 100644 internal/e2e/suites/networking/networking_test.go create mode 100644 internal/e2e/suites/networking/testmain_test.go diff --git a/cmd/atenet/README.md b/cmd/atenet/README.md index 9200e0f9c..13738e4df 100644 --- a/cmd/atenet/README.md +++ b/cmd/atenet/README.md @@ -9,8 +9,6 @@ This is built as a single binary for convenience in the prototyping. ## Cluster deployment -![atenet diagram](atenet-diagram.png) - ### router (Note: this deployment model combines Envoy dataplane with the router. This will @@ -22,6 +20,9 @@ likely be split in the future for better scalability.) * atenet router * Service will expose: * Envoy port 80 and 443. +* Upstream: Envoy's `ORIGINAL_DST` actor cluster dials the actor's in-worker + `atunnel` ingress server on the worker pod's port 443 over mTLS, using the + address `atenet router`'s ext_proc resolved into `x-ate-original-dst`. RBAC permissions: * read, list on ActorTemplate diff --git a/cmd/atenet/atenet-diagram.png b/cmd/atenet/atenet-diagram.png deleted file mode 100644 index c77c5baad5b7ce9729cf68c67aa127b889a78bad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75853 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 J(S5TQ{|BM4=Rp7f diff --git a/cmd/atenet/internal/dns/README.md b/cmd/atenet/internal/dns/README.md index 90196b7ba..a134e829a 100644 --- a/cmd/atenet/internal/dns/README.md +++ b/cmd/atenet/internal/dns/README.md @@ -4,7 +4,7 @@ The DNS Controller orchestrates the configuration needed to setup the ATE routin We want to resolve requests for ..actors.resources.substrate.ate.dev to the router service address. -* Stub resolver mode: orchestrate running a CoreDNS instance with the actor name mapped to the router service address. +* Stub resolver mode: orchestrate running a CoreDNS instance with the actor name mapped to the atenet-router service address. Cluster resources: diff --git a/docs/architecture.md b/docs/architecture.md index 8c734dce6..496ca4bec 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -340,13 +340,22 @@ A `WorkerPool` selects a **sandbox class** (`spec.sandboxClass`), and each class * **micro-VM** (`ateom-microvm`): Runs the workload inside a [Kata Containers](https://katacontainers.io/) guest on the [Cloud Hypervisor](https://www.cloudhypervisor.org/) VMM. Suspend and resume capture a memory-only VM snapshot and restore it on-demand using `userfaultfd` memory demand-paging, with container rootfs writes captured in guest RAM via a `tmpfs` overlay. `DurableDir` volumes are host-backed instead, served over a second (writable) virtio-fs share and shipped in snapshots as a tar, so a `Data`-scope snapshot can capture them without any guest memory. -### Networking Stack (`atenet` + Envoy) +### Networking Stack (`atenet` DNS + `atunnel`) Handles actor-aware routing and automatic re-animation. * **Uniform DNS Mesh**: Substrate provides a location-transparent actor discovery scheme via a global DNS suffix (`..actors.resources.substrate.ate.dev`). - * **Routing**: The `atenet` router (powered by Envoy and an External Processing server) intercepts traffic destined for the mesh. It extracts the actor name from the `Host` header, queries the Control Plane to determine the actor's current location, and triggers a `ResumeActor` workflow if the actor is currently suspended. + * **Ingress Routing**: `atenet-router` runs Envoy with an `ext_proc` external + processor and accepts HTTP traffic for the Actor DNS suffix. The ext_proc + extracts the Actor name and Atespace from the `Host` header and calls the + Control Plane to resume the Actor and resolve its current worker assignment. + + * **Worker Tunnel**: After resolving the assignment, `atenet-router` opens an + authenticated TLS tunnel to the worker's `atunnel` listener on port 443. + `atunnel`, hosted by `ateom`, forwards the request to the active Actor over + its private veth interface. Worker pod port 80 is not a direct Actor ingress + path. * **Latency**: The data plane is optimized for sub-100ms activation by bypassing Kubernetes' eventual consistency and performing atomic physical assignments. @@ -360,26 +369,29 @@ suspended (UML sequence diagram): sequenceDiagram actor Client participant DNS as atenet DNS - participant Router as atenet router + participant Gateway as atenet-router participant API as ate-api-server participant Atelet as atelet - participant Ateom as ateom + participant Tunnel as ateom / atunnel + participant Actor participant Store as snapshot storage Client->>DNS: resolve actor DNS name - DNS-->>Client: router address - Client->>Router: HTTP request (Host = actor) - Router->>API: ResumeActor(actorName) + DNS-->>Client: ingress gateway address + Client->>Gateway: HTTP request (Host = actor) + Gateway->>API: ResumeActor(atespace, actor name) API->>Atelet: Restore Store-->>Atelet: download snapshot - Atelet->>Ateom: RestoreWorkload - Note over Ateom: runsc restore - Ateom-->>Atelet: ready + Atelet->>Tunnel: RestoreWorkload + Note over Tunnel: restore sandbox and activate Actor network + Tunnel-->>Atelet: ready Atelet-->>API: worker pod IP - API-->>Router: worker pod IP - Router->>Ateom: proxy request to worker pod - Ateom-->>Router: response - Router-->>Client: response + API-->>Gateway: worker assignment + Gateway->>Tunnel: mTLS tunnel to worker port 443 + Tunnel->>Actor: forward request to Actor port + Actor-->>Tunnel: response + Tunnel-->>Gateway: response + Gateway-->>Client: response Note over API,Store: later: an explicit SuspendActor checkpoints back to storage and frees the worker ``` @@ -423,8 +435,9 @@ Triggered by an inbound request at the Gateway or an explicit API call. 4. **Status**: Status transitions to `STATUS_RUNNING`. The actor now has an active Worker IP. - 5. **Response**: The Control Plane returns the worker IP to the Gateway, - which forwards the original request. + 5. **Response**: The Control Plane returns the worker assignment to the + Gateway. The Gateway opens an authenticated tunnel to `atunnel` on that + worker, which forwards the original request to the active Actor. ### Phase 3: Hibernation (`SuspendActor`) diff --git a/docs/threat-model.md b/docs/threat-model.md index 9a8d3fb9a..c0dbbba43 100644 --- a/docs/threat-model.md +++ b/docs/threat-model.md @@ -41,9 +41,9 @@ Substrate is an early, fast moving product. It is full of debate and subject to * **ateom:** Per-worker Pod sidecar, running inside the worker Pod. Ateom sets up "interior" sandboxes in the worker Pod and manages sandbox lifecycle, including image pulls. It currently uses gvisor but Substrate will support multiple microvm solutions. * **Worker:** Preprovisioned Pods that actors get scheduled to. * **Actor:** The core compute primitive, gets scheduled to/from worker via Run for cold start and Resume for snapshot resume. -* **Actor IP:** Actor networking is based on Pod networking. Each actor gets the IP of the worker it's currently scheduled to. The ateom has the opportunity to set up additional rules when it sets up interior sandboxes. -* **Actor DNS:** Each Actor gets a DNS name like `..actors.resources.substrate.ate.dev`. Substrate runs a custom CoreDNS instance that returns atenet-router's IP address for any A record query matching the actor DNS name pattern. Substrate also includes a built-in controller that both keeps Substrate's CoreDNS configuration up to date with the router's Service IP and updates kube-dns with a stub domain for `actors.resources.substrate.ate.dev` that points to the IP of Substrate's CoreDNS Service. The latter enables traditional Kubernetes Pods to resolve Substrate Actor DNS names. -* **atenet-router:** Substrate runs an Envoy proxy to handle ingress to actors. When a client sends a request to an actor's DNS name, it is resolved to atenet-router's Envoy sidecar. Envoy then forwards headers to atenet-router (ext\_proc filter), which extracts the actor name, automatically resumes the actor if it is in a suspended state, queries the Substrate API for the current IP of the actor, and tells Envoy to rewrite the host header to the actor IP before forwarding the request to the actor. atenet-router includes a local xDS server that configures the Envoy sidecar with this behavior. +* **Actor Network:** `ateom` creates a private point-to-point veth network for the active Actor inside a worker Pod. The Actor is not served directly from the worker Pod's port 80; ingress enters through `atunnel`'s authenticated listener on port 443. +* **Actor DNS:** Each Actor gets a DNS name like `..actors.resources.substrate.ate.dev`. Substrate runs a custom CoreDNS instance that returns the `atenet-router` Service IP for A record queries matching the Actor DNS pattern. A controller keeps this target current and configures kube-dns with a stub domain for `actors.resources.substrate.ate.dev`, enabling traditional Kubernetes Pods to resolve Actor names. +* **atenet-router:** Substrate runs Envoy with an `ext_proc` external processor to handle Actor ingress. The ext_proc extracts the Actor name and Atespace from the HTTP `Host` header, calls the Substrate API to resume the Actor and obtain its current worker assignment, and selects that worker as a dynamic backend. The router then connects with mTLS to `atunnel` on worker port 443; `atunnel` validates the router identity and forwards traffic only to the Actor currently assigned to that worker. * **Object Storage:** Used to store actor snapshots. * **Filesystem support:** Container local filesystem is saved in snapshots, future integrations likely to include networked storage. * **Substrate Database:** Currently Valkey (Redis-compatible API). The choice of backend database/interface is under active debate. @@ -73,13 +73,13 @@ While it's probably unlikely that Substrate, which is expected to be an internal | GitHub Issue | Priority | Threat | Mitigating Invariants | Suggested Concrete Mitigations | Notes | | :---- | :---- | :---- | :---- | :---- | :---- | -| | Critical | Access to the internal network allows arbitrary actions to be performed on ate-apiserver, atelet, substrate backend database, etc. | All system components must have basic mutual authentication and authorization, and communicate over TLS. All clients (including end users and actors) must be authenticated and authorized. Unauthenticated traffic must be rejected. Where possible, use firewalls to block access from ranges that have no business communicating with Substrate components. | mTLS or other secure channel (e.g. UDS) between networked system components (ate-apiserver, atelet, ateom, etc) each atelet has a unique identity cryptographically tied to the node identity ate-router should check client permissions before resuming actors or forwarding traffic to actors. The only component authorized to connect directly to the backend database should be ate-apiserver. Use Kubernetes NetworkPolicy to block access to the Substrate API and other core components by default. | | +| | Critical | Access to the internal network allows arbitrary actions to be performed on ate-apiserver, atelet, substrate backend database, etc. | All system components must have basic mutual authentication and authorization, and communicate over TLS. All clients (including end users and actors) must be authenticated and authorized. Unauthenticated traffic must be rejected. Where possible, use firewalls to block access from ranges that have no business communicating with Substrate components. | mTLS or other secure channel (e.g. UDS) between networked system components (ate-apiserver, atelet, ateom, etc); each atelet has a unique identity cryptographically tied to the node identity. The ingress gateway should check client permissions before resuming actors or forwarding traffic to actors. The only component authorized to connect directly to the backend database should be ate-apiserver. Use Kubernetes NetworkPolicy to block access to the Substrate API and other core components by default. | | | | High | Privilege escalation via access to sensitive labels. | If Substrate offers its own resource labeling mechanism, it must also offer a way to authorize label updates on a per-label basis. | Substrate authorization system requires explicit authorization to update metadata, separate from updating resource body. Substrate authorization system supports per-label authorization rules. | Plenty of attacks in K8s were possible because labels had semantic meaning, but the permission model could implicitly granted access to modify labels, even if it was inappropriate. For example, /status subresource allows label updates. Substrate shouldn't repeat this mistake. | -| | High | Attacker gains control of Substrate API server, router, or other ingress/egress proxy. | Isolate the control plane from the data plane, and isolate data plane ingress from sandboxes. | Don't co-locate ate-apiserver or other control-plane components on the same machines as the untrusted sandboxes. This also supports control-plane / data-plane isolation from a reliability and performance perspective. Consider running any gateway/router that enables direct interaction with sandboxes on a separate VM from the sandboxes. Consider using a zero-trust architecture where traffic is encrypted and authenticated end-to-end and routed via a mesh. | While the sandbox is trusted as an isolation layer, the residual risk is typically not worth the marginal cost savings of colocating, and isolation is recommend to avoid noisy neighbor problems and other reliability issues that stem from mixing the control-plane and data-plane, especially for large scale deployments of Substrate. | +| | High | Attacker gains control of the Substrate API server or an ingress/egress gateway. | Isolate the control plane from the data plane, and isolate data plane ingress from sandboxes. | Don't co-locate ate-apiserver or other control-plane components on the same machines as the untrusted sandboxes. This also supports control-plane / data-plane isolation from a reliability and performance perspective. Consider running any gateway that enables direct interaction with sandboxes on a separate VM from the sandboxes. Consider using a zero-trust architecture where traffic is encrypted and authenticated end-to-end and routed via a mesh. | While the sandbox is trusted as an isolation layer, the residual risk is typically not worth the marginal cost savings of colocating, and isolation is recommend to avoid noisy neighbor problems or other reliability issues that stem from mixing the control-plane and data-plane, especially for large scale deployments of Substrate. | | | High | Attacker who can create ActorTemplates specifies malicious runtime. | Ensure available runtime can only be configured by administrators. | Consider a mechanism like RuntimeClass to decouple configuration of available runtimes from consumption of available runtimes. | | | | High | Attacker who can create ActorTemplates can read or write any storage buckets atelet has access to. | Ensure that bucket access is least-privilege. | Use credentials derived from actor identity to read snapshots. Configure permissions to prevent atelet or nodes from having access to sensitive buckets. Don't support arbitrary URLs in APIs, instead design a standard approach for accessing the correct resource given the ActorTemplate name and compute the mapping internally, subject to scheduling-aware authorization checks. | For example: Attacker creates an ActorTemplate with the runsc URL or golden snapshot URL pointing to an arbitrary bucket in the same project/resource scope as the cluster. If atelet has project-wide access to buckets, this could cause the state to be pulled into the worker pod or malicious actor. Similarly, an attacker could set the snapshots URL to point to an internal infrastructure bucket, causing data to be written to that bucket. | -| | Medium | DoS attack against API, router, or available cluster resources. | Minimize exposure and ensure APIs and proxies implement appropriately scoped quotas and rate limiting. | Don't co-locate ate-apiserver or other control-plane components on the same machines as the untrusted sandboxes. Don't expose ate-apiserver directly to the internet. Consider an identity-aware WAF (likely up to providers) if external access is required. Use network policy to block direct interaction between untrusted sandboxes and the ate-apiserver. Implement quotas and rate limiting in the API and in proxies. This includes quotas for the number of actors a user can allocate. Use a zero-trust architecture that prevents identity spoofing or intentional misrouting to get around limits. | Notably, the router checks ate-apiserver for the actor IP on each request. A flood of traffic to actors could potentially result in high read load on ate-apiserver. Caching could be considered, but cache invalidation during actor rescheduling would be important to avoid misrouting traffic. | -| | Medium | Internal network traffic is intercepted or spoofed | Encrypt all traffic by default | Use mTLS between all system components, and between the router and actors. | It may be desirable to rely on cloud providers to transparently encrypt traffic between VMs on their internal network. Worth discussing what makes the most sense. | +| | Medium | DoS attack against API, gateway, or available cluster resources. | Minimize exposure and ensure APIs and proxies implement appropriately scoped quotas and rate limiting. | Don't co-locate ate-apiserver or other control-plane components on the same machines as the untrusted sandboxes. Don't expose ate-apiserver directly to the internet. Consider an identity-aware WAF (likely up to providers) if external access is required. Use network policy to block direct interaction between untrusted sandboxes and the ate-apiserver. Implement quotas and rate limiting in the API and in proxies. This includes quotas for the number of actors a user can allocate. Use a zero-trust architecture that prevents identity spoofing or intentional misrouting to get around limits. | The gateway calls ate-apiserver to resolve Actor assignments and uses a short-lived cache. A flood of Actor traffic could still create high API load; cache invalidation during Actor rescheduling must avoid stale routing. | +| | Medium | Internal network traffic is intercepted or spoofed | Encrypt all traffic by default | Use mTLS between all system components, including the gateway-to-worker `atunnel` connection. | It may be desirable to rely on cloud providers to transparently encrypt traffic between VMs on their internal network. Worth discussing what makes the most sense. | ## Misconfiguration Risks @@ -88,7 +88,7 @@ While it's probably unlikely that Substrate, which is expected to be an internal | | High | Improper handling of Secrets | Ensure there is an official, secure, recommended way to pass secret data, like API access tokens, to actors. | Support env and filesystem plumbing for Kubernetes Secrets, to provide an official path that avoids secret material being plumbed via nonspecific fields that are difficult to audit. Ensure secrets are encrypted in transit and ideally stored in memory. If exposed via the filesystem, do so via in-memory tmpfs. | If we don't support this, users will inevitably put secrets in plaintext. | | | Medium | Complexity configuring permissions for frameworks on top of Substrate may lead to unintentional privilege escalation. | It must be clear to users what the downstream effects of auth config in substrate are. | If it's not intuitive, it must be documented in a user guide. | AI framework has to set up permissions to access ATE, and to access K8s, and potentially for actors (based on ATE identity and K8s identity) to access the framework. We need to make this easy. Think about past K8s issues like escalate/bind risk. Substrate resource model is spread across ate-apiserver and K8s, increasing complexity and chance for error. | | | Medium | Flat namespace of actors encourages broad permission grants or complex graph-oriented policy. | Support a grouping mechanism that can be used in policy controls. | Add namespaces to Substrate, similar to Kubernetes. | | -| | Medium | DNS misconfiguration | Access to DNS configuration should be limited to authoritative controllers. Routing should use stable configurations and query the API for the current IP before routing each request. | Don't co-locate controllers with access to sensitive system state on the same nodes as actors. Limit permissions to update DNS configuration. Actively query Substrate API to ensure IPs are as up-to-date as possible. Potentially use mTLS based on actor DNS name between ate-router and actors. | As noted above, a flood of requests could create high read load on ate-apiserver. Caching could be considered, but cache invalidation during actor rescheduling would be important to avoid misrouting traffic. Establishing a backend mTLS tunnel between ate-router and each actor based on a serving cert signed for the actor's DNS name could be another approach to avoiding misrouting. | +| | Medium | DNS misconfiguration | Access to DNS configuration should be limited to authoritative controllers. Routing should use stable configurations and query the API for the current worker assignment before forwarding requests. | Don't co-locate controllers with access to sensitive system state on the same nodes as actors. Limit permissions to update DNS configuration. Use the Substrate API and bounded caching to keep assignments current. Authenticate gateway-to-worker traffic with mTLS. | A flood of requests could create high read load on ate-apiserver. Cache invalidation during Actor rescheduling is important to avoid stale routing. The gateway-to-`atunnel` mTLS connection prevents the removed direct worker-port path from bypassing worker assignment checks. | ## Attacks from Actors diff --git a/internal/atunnel/server.go b/internal/atunnel/server.go index 05dac1aad..e117a2df6 100644 --- a/internal/atunnel/server.go +++ b/internal/atunnel/server.go @@ -62,11 +62,10 @@ type Server struct { } type activation struct { - atespace string - actorName string - ctx context.Context - cancel context.CancelFunc - wg sync.WaitGroup + ref resources.ActorRef + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup } // NewServer creates a Server and validates its TLS material. @@ -177,14 +176,13 @@ func (s *Server) Activate(atespace, actorName string) error { s.mu.Lock() defer s.mu.Unlock() if s.active != nil { - return fmt.Errorf("atunnel: actor %s/%s is already active", s.active.atespace, s.active.actorName) + return fmt.Errorf("atunnel: actor %s is already active", s.active.ref) } ctx, cancel := context.WithCancel(context.Background()) s.active = &activation{ - atespace: atespace, - actorName: actorName, - ctx: ctx, - cancel: cancel, + ref: resources.ActorRef{Atespace: atespace, Name: actorName}, + ctx: ctx, + cancel: cancel, } return nil } @@ -231,7 +229,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.reject(w) return } - actorRef, err := resources.ParseActorDNSName(host) + ref, err := resources.ParseActorDNSName(host) if err != nil { s.reject(w) return @@ -239,7 +237,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.mu.Lock() active := s.active - if active == nil || active.atespace != actorRef.Atespace || active.actorName != actorRef.Name { + if active == nil || active.ref != ref { s.mu.Unlock() s.reject(w) return diff --git a/internal/e2e/suites/networking/networking_test.go b/internal/e2e/suites/networking/networking_test.go new file mode 100644 index 000000000..5f3c16866 --- /dev/null +++ b/internal/e2e/suites/networking/networking_test.go @@ -0,0 +1,121 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networking + +import ( + "context" + "fmt" + "io" + "net/http" + "testing" + "time" + + "github.com/agent-substrate/substrate/internal/e2e" + "github.com/agent-substrate/substrate/internal/resources" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" +) + +const networkingAtespace = "networking-e2e" + +func TestActorDirectAccess(t *testing.T) { + ctx := context.Background() + actorName, actor := createAndResumeActor(t, ctx, "direct") + router := mustRouterClient(t, ctx) + defer router.Close() + + t.Run("direct", func(t *testing.T) { + assertDirectActorAccess(t, ctx, e2e.GetClients(), actor) + }) + t.Run("via ingress", func(t *testing.T) { + actorRef := resources.ActorRef{Atespace: networkingAtespace, Name: actorName} + response, err := router.Get(ctx, actorRef, "/readyz") + if err != nil { + t.Fatalf("GET Actor through ingress: %v", err) + } + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + if err != nil { + t.Fatalf("reading ingress response body (HTTP %d): %v", response.StatusCode, err) + } + if response.StatusCode != http.StatusOK { + t.Fatalf("Actor access through ingress returned HTTP %d, want 200; body: %s", response.StatusCode, body) + } + t.Logf("Actor access through ingress succeeded; body: %s", body) + }) +} + +func createAndResumeActor(t *testing.T, ctx context.Context, prefix string) (string, *ateapipb.Actor) { + t.Helper() + clients := e2e.GetClients() + actorName := fmt.Sprintf("%s-%d", prefix, time.Now().UnixNano()) + actorRef := &ateapipb.ObjectRef{Atespace: networkingAtespace, Name: actorName} + + t.Logf("creating actor %s/%s", networkingAtespace, actorName) + _, _ = clients.SubstrateAPI.CreateAtespace(ctx, &ateapipb.CreateAtespaceRequest{ + Atespace: &ateapipb.Atespace{Metadata: &ateapipb.ResourceMetadata{Name: networkingAtespace}}, + }) + if _, err := clients.SubstrateAPI.CreateActor(ctx, &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: networkingAtespace, Name: actorName}, + ActorTemplateNamespace: "ate-demo-counter", + ActorTemplateName: "counter", + }}); err != nil { + t.Fatalf("CreateActor: %v (deploy the fixture with --deploy-demo-counter)", err) + } + t.Cleanup(func() { + _, _ = clients.SubstrateAPI.SuspendActor(context.Background(), &ateapipb.SuspendActorRequest{Actor: actorRef}) + _, _ = clients.SubstrateAPI.DeleteActor(context.Background(), &ateapipb.DeleteActorRequest{Actor: actorRef}) + }) + + resumeResponse, err := clients.SubstrateAPI.ResumeActor(ctx, &ateapipb.ResumeActorRequest{Actor: actorRef}) + if err != nil { + t.Fatalf("ResumeActor: %v", err) + } + t.Logf("resumed actor %s/%s", networkingAtespace, actorName) + return actorName, resumeResponse.GetActor() +} + +func mustRouterClient(t *testing.T, ctx context.Context) *e2e.RouterClient { + t.Helper() + router, err := e2e.NewRouterClient(ctx) + if err != nil { + t.Fatalf("NewRouterClient: %v", err) + } + return router +} + +func assertDirectActorAccess(t *testing.T, ctx context.Context, clients *e2e.Clients, actor *ateapipb.Actor) { + t.Helper() + if actor.GetAteomPodNamespace() == "" || actor.GetAteomPodName() == "" { + t.Fatalf("resumed Actor has no worker pod assignment: %+v", actor) + } + + // The Kubernetes pod proxy performs this request from inside the cluster to + // the assigned worker's port 80. It bypasses atenet-router and therefore + // verifies that the old direct path remains unavailable without relying on + // the test runner having a route to the pod CIDR. + result := clients.K8s.CoreV1().RESTClient().Get(). + Namespace(actor.GetAteomPodNamespace()). + Resource("pods"). + Name(actor.GetAteomPodName() + ":80"). + SubResource("proxy"). + Suffix("readyz"). + Do(ctx) + body, err := result.Raw() + + if err == nil { + t.Fatalf("direct Actor access through %s/%s:80 unexpectedly succeeded; body: %s", actor.GetAteomPodNamespace(), actor.GetAteomPodName(), body) + } + t.Logf("direct Actor access through %s/%s:80 was blocked as expected: %v", actor.GetAteomPodNamespace(), actor.GetAteomPodName(), err) +} diff --git a/internal/e2e/suites/networking/testmain_test.go b/internal/e2e/suites/networking/testmain_test.go new file mode 100644 index 000000000..2d31b9c9f --- /dev/null +++ b/internal/e2e/suites/networking/testmain_test.go @@ -0,0 +1,24 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package networking + +import ( + "os" + "testing" + + "github.com/agent-substrate/substrate/internal/e2e" +) + +func TestMain(m *testing.M) { os.Exit(e2e.RunTestMain(m)) } From e253ee090bd37e25d1ea2a2094561b9470e786b9 Mon Sep 17 00:00:00 2001 From: Lior Lieberman Date: Thu, 30 Jul 2026 17:25:56 -0700 Subject: [PATCH 5/5] address feedback --- cmd/atenet/internal/router/config.go | 6 +++-- cmd/ateom-gvisor/main.go | 1 + cmd/ateom-microvm/main.go | 6 ++++- internal/atunnel/client.go | 40 +++++++++++++++++++--------- internal/atunnel/client_test.go | 19 ++++++++----- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/cmd/atenet/internal/router/config.go b/cmd/atenet/internal/router/config.go index 45b56e11d..0e87ea421 100644 --- a/cmd/atenet/internal/router/config.go +++ b/cmd/atenet/internal/router/config.go @@ -49,6 +49,7 @@ type routerConfig struct { HealthInterval time.Duration HttpsPort int EnvoyCertPath string + // UpstreamCredentialBundlePath is the router's podidentity credential bundle // (cert+key) presented as the client cert when dialing the actor's atunnel // ingress server over mTLS. UpstreamTrustBundlePath is the CA bundle used to @@ -58,8 +59,9 @@ type routerConfig struct { // UpstreamSpiffePrefix validates the actor's atunnel server cert by its // SPIFFE URI SAN prefix (trust domain) instead of the dialed pod IP. UpstreamSpiffePrefix string - LogLevel string - MetricsAddr string + + LogLevel string + MetricsAddr string // OtlpCollectorAddress is the host:port of the OTLP gRPC collector that // Envoy reports tracing spans to. Empty disables Envoy-side tracing. OtlpCollectorAddress string diff --git a/cmd/ateom-gvisor/main.go b/cmd/ateom-gvisor/main.go index 49989b5ef..baae40ff0 100644 --- a/cmd/ateom-gvisor/main.go +++ b/cmd/ateom-gvisor/main.go @@ -54,6 +54,7 @@ import ( var ( podUID = pflag.String("pod-uid", "", "The UID of the current pod") + // TODO(liorlieberman) have a sub package for all atunnel releated things like that atunnelListenAddress = pflag.String("atunnel-listen-address", "0.0.0.0:443", "Address for actor ingress HTTPS") atunnelCredentialBundle = pflag.String("atunnel-credential-bundle", "/run/podidentity.podcert.ate.dev/credential-bundle.pem", "PEM credential bundle for actor ingress HTTPS") atunnelTrustBundle = pflag.String("atunnel-trust-bundle", "/run/podidentity.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle for actor ingress clients") diff --git a/cmd/ateom-microvm/main.go b/cmd/ateom-microvm/main.go index 55fe84ff6..24c340794 100644 --- a/cmd/ateom-microvm/main.go +++ b/cmd/ateom-microvm/main.go @@ -67,6 +67,10 @@ var ( atunnelEgressTrustBundle = flag.String("atunnel-egress-trust-bundle", "/run/servicedns.podcert.ate.dev/trust-bundle.pem", "PEM trust bundle for the egress gateway") ) +const ( + actorHTTPUpstream = "http://169.254.17.2:80" +) + func main() { flag.Parse() if *showVersion { @@ -156,7 +160,7 @@ func do(ctx context.Context) error { // logWriter with the runtime logger so the two streams to os.Stdout are // serialized through one SyncedWriter and never interleave-corrupt lines. actorLogger := actorlog.NewActorLogger(logWriter, metadata.OnGCE()) - upstream, err := url.Parse("http://169.254.17.2:80") + upstream, err := url.Parse(actorHTTPUpstream) if err != nil { return fmt.Errorf("while parsing atunnel upstream: %w", err) } diff --git a/internal/atunnel/client.go b/internal/atunnel/client.go index 13692208c..1a7817a5e 100644 --- a/internal/atunnel/client.go +++ b/internal/atunnel/client.go @@ -37,21 +37,36 @@ const ( // for policy decisions. ActorAtespaceHeader = "X-Ate-Atespace" // ActorNameHeader identifies the actor that opened an egress tunnel. - ActorNameHeader = "X-Ate-Actor" + ActorNameHeader = "X-Ate-Actor-Name" // ActorVersionHeader is the Actor resource version observed when the worker // was assigned. Gateways use it as a lower bound on cached Actor metadata. ActorVersionHeader = "X-Ate-Actor-Version" ) +// TODO(liorlieberman): support/use CONNECT on Ingress as well. // ClientConfig configures an egress CONNECT client. type ClientConfig struct { GatewayAddress string ServerName string CredentialBundlePath string TrustBundlePath string +} + +// DialFunc dials a network address. It matches net.Dialer.DialContext. +type DialFunc func(ctx context.Context, network, address string) (net.Conn, error) - // DialContext is injectable for tests. When nil, a net.Dialer is used. - DialContext func(context.Context, string, string) (net.Conn, error) +// ClientOption customizes a Client beyond its configuration. Production +// callers need none of these. +type ClientOption func(*Client) + +// WithDialer overrides how the client reaches the egress gateway. It exists so +// tests can substitute a transport; by default a net.Dialer is used. +func WithDialer(dial DialFunc) ClientOption { + return func(c *Client) { + if dial != nil { + c.dialContext = dial + } + } } // EgressMetadata is attached to an egress CONNECT request. BearerToken is @@ -67,13 +82,14 @@ type EgressMetadata struct { type Client struct { gatewayAddress string tlsConfig *tls.Config - dialContext func(context.Context, string, string) (net.Conn, error) + dialContext DialFunc } +// Client implements EgressDialer. var _ EgressDialer = (*Client)(nil) // NewClient creates an egress CONNECT client and validates its TLS material. -func NewClient(cfg ClientConfig) (*Client, error) { +func NewClient(cfg ClientConfig, opts ...ClientOption) (*Client, error) { if _, _, err := net.SplitHostPort(cfg.GatewayAddress); err != nil { return nil, fmt.Errorf("atunnel: invalid egress gateway address %q: %w", cfg.GatewayAddress, err) } @@ -98,14 +114,10 @@ func NewClient(cfg ClientConfig) (*Client, error) { return nil, fmt.Errorf("atunnel: trust bundle %q contains no certificates", cfg.TrustBundlePath) } - dialContext := cfg.DialContext - if dialContext == nil { - dialContext = (&net.Dialer{}).DialContext - } credentialBundlePath := cfg.CredentialBundlePath - return &Client{ + client := &Client{ gatewayAddress: cfg.GatewayAddress, - dialContext: dialContext, + dialContext: (&net.Dialer{}).DialContext, tlsConfig: &tls.Config{ MinVersion: tls.VersionTLS12, RootCAs: rootCAs, @@ -114,7 +126,11 @@ func NewClient(cfg ClientConfig) (*Client, error) { return loadCredentialBundle(credentialBundlePath) }, }, - }, nil + } + for _, opt := range opts { + opt(client) + } + return client, nil } // DialContext opens a CONNECT tunnel to destination. destination becomes the diff --git a/internal/atunnel/client_test.go b/internal/atunnel/client_test.go index f52629f19..9d74884d4 100644 --- a/internal/atunnel/client_test.go +++ b/internal/atunnel/client_test.go @@ -37,7 +37,6 @@ import ( func TestClientDialContext(t *testing.T) { ca := newTestCA(t) - client := newTestClient(t, ca) request := make(chan *http.Request, 1) gatewayAddress := serveTestConnectGateway(t, ca, func(conn net.Conn, req *http.Request) { request <- req @@ -54,7 +53,7 @@ func TestClientDialContext(t *testing.T) { t.Errorf("tunneled payload = %q, want ping", payload) } }) - client.gatewayAddress = gatewayAddress + client := newTestClient(t, ca, WithDialer(dialFixedAddress(gatewayAddress))) conn, err := client.DialContext(context.Background(), "192.0.2.10:443", EgressMetadata{ Atespace: "team-a", @@ -101,11 +100,11 @@ func TestClientDialContext(t *testing.T) { func TestClientDialContextRejected(t *testing.T) { ca := newTestCA(t) - client := newTestClient(t, ca) - client.gatewayAddress = serveTestConnectGateway(t, ca, func(conn net.Conn, _ *http.Request) { + gatewayAddress := serveTestConnectGateway(t, ca, func(conn net.Conn, _ *http.Request) { body := "denied by policy" _, _ = fmt.Fprintf(conn, "HTTP/1.1 403 Forbidden\r\nContent-Length: %d\r\n\r\n%s", len(body), body) }) + client := newTestClient(t, ca, WithDialer(dialFixedAddress(gatewayAddress))) _, err := client.DialContext(context.Background(), "192.0.2.10:443", EgressMetadata{ Atespace: "team-a", @@ -160,7 +159,15 @@ func TestClientDialContextValidatesInput(t *testing.T) { } } -func newTestClient(t *testing.T, ca *testCA) *Client { +// dialFixedAddress ignores the requested address and connects to address, so +// tests can point a client at a listener on an ephemeral port. +func dialFixedAddress(address string) DialFunc { + return func(ctx context.Context, network, _ string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, network, address) + } +} + +func newTestClient(t *testing.T, ca *testCA, opts ...ClientOption) *Client { t.Helper() dir := t.TempDir() bundlePath := filepath.Join(dir, "client.pem") @@ -177,7 +184,7 @@ func newTestClient(t *testing.T, ca *testCA) *Client { ServerName: "egress.test", CredentialBundlePath: bundlePath, TrustBundlePath: trustPath, - }) + }, opts...) if err != nil { t.Fatal(err) }