diff --git a/charts/substrate/templates/atenet-egress.yaml b/charts/substrate/templates/atenet-egress.yaml index d62a62207e..c75a29c48a 100644 --- a/charts/substrate/templates/atenet-egress.yaml +++ b/charts/substrate/templates/atenet-egress.yaml @@ -70,8 +70,15 @@ data: - backends: - dynamic: {} policies: + {{- if .Values.atenetEgress.upstreamTrust.caBundle }} + {{- /* No root: the gateway then trusts the native roots, which are + the image's public bundle plus upstreamTrust (SSL_CERT_FILE and + SSL_CERT_DIR on the agentgateway container). */}} + backendTLS: {} + {{- else }} backendTLS: root: /etc/ssl/certs/ca-certificates.crt + {{- end }} policies: substrateEgress: host: {{ include "substrate.fullname" (list "api" .) }}.{{ .Release.Namespace }}.svc:443 @@ -115,6 +122,17 @@ data: tcpRoutes: - backends: - dynamic: {} +{{- with .Values.atenetEgress.upstreamTrust.caBundle }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "substrate.fullname" (list "atenet-egress-upstream-trust" $) }} + namespace: {{ $.Release.Namespace }} +data: + ca.crt: | + {{- . | nindent 4 }} +{{- end }} --- apiVersion: apps/v1 kind: Deployment @@ -132,6 +150,10 @@ spec: metadata: labels: app: atenet-egress + {{- with .Values.atenetEgress.upstreamTrust.caBundle }} + annotations: + checksum/upstream-trust: {{ . | sha256sum }} + {{- end }} spec: serviceAccountName: {{ include "substrate.fullname" (list "atenet-egress" .) }} securityContext: @@ -155,6 +177,14 @@ spec: fieldPath: status.podIP - name: RUST_LOG value: debug + {{- if .Values.atenetEgress.upstreamTrust.caBundle }} + # Both set: the upstream roots are the image's public bundle plus + # upstreamTrust (rustls-native-certs reads the two and nothing else). + - name: SSL_CERT_FILE + value: /etc/ssl/certs/ca-certificates.crt + - name: SSL_CERT_DIR + value: /run/egress-upstream-trust + {{- end }} ports: - name: https containerPort: 8443 @@ -189,7 +219,17 @@ spec: - name: actor-id-ca-certs mountPath: /run/actor-id-ca-certs readOnly: true + {{- if .Values.atenetEgress.upstreamTrust.caBundle }} + - name: upstream-trust + mountPath: /run/egress-upstream-trust + readOnly: true + {{- end }} volumes: + {{- if .Values.atenetEgress.upstreamTrust.caBundle }} + - name: upstream-trust + configMap: + name: {{ include "substrate.fullname" (list "atenet-egress-upstream-trust" .) }} + {{- end }} - name: egress-mitm secret: secretName: egress-mitm-ca-pool diff --git a/charts/substrate/tests/atenet_egress_upstream_trust_test.yaml b/charts/substrate/tests/atenet_egress_upstream_trust_test.yaml new file mode 100644 index 0000000000..fe796ab881 --- /dev/null +++ b/charts/substrate/tests/atenet_egress_upstream_trust_test.yaml @@ -0,0 +1,119 @@ +# 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. + +suite: atenet egress upstream trust +templates: +- atenet-egress.yaml +tests: +- it: trusts the image's public roots only by default + documentSelector: + path: kind + value: Deployment + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: SSL_CERT_DIR + value: /run/egress-upstream-trust + - notExists: + path: spec.template.metadata.annotations + - lengthEqual: + path: spec.template.spec.volumes + count: 5 + +- it: pins the dynamic HTTPS backend to the image's public bundle by default + documentSelector: + path: metadata.name + value: RELEASE-NAME-atenet-egress-agentgateway-config + asserts: + - matchRegex: + path: data["config.yaml"] + pattern: '- dynamic: \{\}\s+policies:\s+backendTLS:\s+root: /etc/ssl/certs/ca-certificates.crt' + +- it: renders no upstream trust ConfigMap by default + asserts: + - containsDocument: + kind: ConfigMap + apiVersion: v1 + name: RELEASE-NAME-atenet-egress-upstream-trust + not: true + +- it: publishes the bundle as a ConfigMap + set: + atenetEgress.upstreamTrust.caBundle: | + -----BEGIN CERTIFICATE----- + MIIBlab + -----END CERTIFICATE----- + documentSelector: + path: metadata.name + value: RELEASE-NAME-atenet-egress-upstream-trust + asserts: + - equal: + path: data["ca.crt"] + value: | + -----BEGIN CERTIFICATE----- + MIIBlab + -----END CERTIFICATE----- + +- it: adds the bundle to the public roots of the egress agentgateway and rolls on a change + set: + atenetEgress.upstreamTrust.caBundle: | + -----BEGIN CERTIFICATE----- + MIIBlab + -----END CERTIFICATE----- + documentSelector: + path: kind + value: Deployment + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SSL_CERT_FILE + value: /etc/ssl/certs/ca-certificates.crt + - contains: + path: spec.template.spec.containers[0].env + content: + name: SSL_CERT_DIR + value: /run/egress-upstream-trust + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: upstream-trust + mountPath: /run/egress-upstream-trust + readOnly: true + - contains: + path: spec.template.spec.volumes + content: + name: upstream-trust + configMap: + name: RELEASE-NAME-atenet-egress-upstream-trust + - exists: + path: spec.template.metadata.annotations["checksum/upstream-trust"] + +- it: leaves root out of the dynamic HTTPS backend so the gateway loads the native roots + set: + atenetEgress.upstreamTrust.caBundle: | + -----BEGIN CERTIFICATE----- + MIIBlab + -----END CERTIFICATE----- + documentSelector: + path: metadata.name + value: RELEASE-NAME-atenet-egress-agentgateway-config + asserts: + - matchRegex: + path: data["config.yaml"] + pattern: '- dynamic: \{\}\s+policies:\s+backendTLS: \{\}' + - notMatchRegex: + path: data["config.yaml"] + pattern: /etc/ssl/certs/ca-certificates.crt diff --git a/charts/substrate/values.yaml b/charts/substrate/values.yaml index 336fd71cac..023b384ed4 100644 --- a/charts/substrate/values.yaml +++ b/charts/substrate/values.yaml @@ -161,6 +161,14 @@ atenetEgress: limits: cpu: "2" memory: 1Gi + # Extra CAs the egress gateway trusts on the actors' upstream HTTPS, next to + # the public roots of its image: a PEM bundle for model or tool endpoints + # behind a private CA (an in-cluster Gateway, a corporate proxy). Empty + # trusts the public roots only. The egress gateway terminates the actors' TLS + # and dials the upstream itself, so a CA the runtime trusts counts for + # nothing here. A change rolls the gateway: the roots are read at start. + upstreamTrust: + caBundle: "" # Kubernetes Secret provider and AGW HTTP/HTTPS credential injection. # Includes get-only Secret RBAC. HTTPS requires egress-mitm-ca-pool and actor trust.