Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions charts/substrate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ See `values.yaml` for the full set; the important keys:
| `atelet.imageCredentialProviderConfigYAML` | `""` | Credential provider config as text, rendered into a ConfigMap, for nodes where that file cannot be authored or must differ from the kubelet's; mutually exclusive with the host path |
| `atelet.imageCredentialProviderBinDir` | `""` | Host path to the kubelet credential provider binaries; both paths are mounted read-only |
| `credentialProvider.namespacePolicies` | `[]` | Default-deny atespace-to-namespace grants; the chart includes get-only Secret RBAC for the provider |
| `credentialProvider.additionalProviders` | `[]` | Further credential providers for the egress gateway, each `uriAuthority` (a lowercase DNS name) and `host` (`<service>.<namespace>.svc:<port>`); the gateway verifies the provider's `servicedns.podcert.ate.dev` serving certificate and presents its own pod identity, which the provider must require |
| `ateApi.actorJWTIssuer` | `https://<idp-service>.<namespace>.svc` | Issuer placed in actor JWTs; set a stable URL reachable by relying parties |
| `objectStorePlugin.extraEnv` | `[]` | Object storage credentials/settings shared by both object-store plugins |
| `objectStorePlugin.control.resources` / `objectStorePlugin.node.resources` | see `values.yaml` | Resources for the API and node object-store sidecars |
Expand Down
41 changes: 41 additions & 0 deletions charts/substrate/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,44 @@ definition so the fallback cannot drift between pods.
{{ toYaml . }}
{{- end }}
{{- end }}

{{/*
The egress gateway's credential providers: the bundled k8s.io one and
credentialProvider.additionalProviders, each dialed with the gateway's pod
identity and verified against the servicedns.podcert.ate.dev trust bundle.
*/}}
{{- define "substrate.egressCredentialProviders" -}}
{{- $providers := list (dict "uriAuthority" "k8s.io" "host" (printf "%s.%s.svc:50051" (include "substrate.fullname" (list "k8s-credential-provider" .)) .Release.Namespace)) -}}
{{- range .Values.credentialProvider.additionalProviders -}}
{{- if or (not .uriAuthority) (not .host) -}}
{{- fail "credentialProvider.additionalProviders entries need uriAuthority and host" -}}
{{- end -}}
{{- $authority := toString .uriAuthority -}}
{{- $host := toString .host -}}
{{- if not (regexMatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$" $authority) -}}
{{- fail (printf "credentialProvider.additionalProviders: uriAuthority %q must be a lowercase DNS name" $authority) -}}
{{- end -}}
{{- if eq $authority "k8s.io" -}}
{{- fail "credentialProvider.additionalProviders cannot replace the bundled k8s.io provider" -}}
{{- end -}}
{{- if not (regexMatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?\\.svc:[0-9]{1,5}$" $host) -}}
{{- fail (printf "credentialProvider.additionalProviders: host %q must be <service>.<namespace>.svc:<port>" $host) -}}
{{- end -}}
{{- $providers = append $providers (dict "uriAuthority" $authority "host" $host) -}}
{{- end -}}
{{- $seen := dict -}}
{{- range $providers }}
{{- if hasKey $seen .uriAuthority -}}
{{- fail (printf "credentialProvider.additionalProviders names %s twice" .uriAuthority) -}}
{{- end -}}
{{- $_ := set $seen .uriAuthority true }}
- uriAuthority: {{ .uriAuthority }}
target:
host: {{ .host }}
policies:
backendTLS:
cert: /run/podidentity.podcert.ate.dev/credential-bundle.pem
key: /run/podidentity.podcert.ate.dev/credential-bundle.pem
root: /run/servicedns.podcert.ate.dev/trust-bundle.pem
{{- end }}
{{- end -}}
18 changes: 2 additions & 16 deletions charts/substrate/templates/atenet-egress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,7 @@ data:
key: /run/podidentity.podcert.ate.dev/credential-bundle.pem
root: /run/servicedns.podcert.ate.dev/trust-bundle.pem
credentialProviders:
- uriAuthority: k8s.io
target:
host: {{ include "substrate.fullname" (list "k8s-credential-provider" .) }}.{{ .Release.Namespace }}.svc:50051
policies:
backendTLS:
cert: /run/podidentity.podcert.ate.dev/credential-bundle.pem
key: /run/podidentity.podcert.ate.dev/credential-bundle.pem
root: /run/servicedns.podcert.ate.dev/trust-bundle.pem
{{- include "substrate.egressCredentialProviders" . | trim | nindent 14 }}
- protocol: HTTP
routes:
- backends:
Expand All @@ -103,14 +96,7 @@ data:
key: /run/podidentity.podcert.ate.dev/credential-bundle.pem
root: /run/servicedns.podcert.ate.dev/trust-bundle.pem
credentialProviders:
- uriAuthority: k8s.io
target:
host: {{ include "substrate.fullname" (list "k8s-credential-provider" .) }}.{{ .Release.Namespace }}.svc:50051
policies:
backendTLS:
cert: /run/podidentity.podcert.ate.dev/credential-bundle.pem
key: /run/podidentity.podcert.ate.dev/credential-bundle.pem
root: /run/servicedns.podcert.ate.dev/trust-bundle.pem
{{- include "substrate.egressCredentialProviders" . | trim | nindent 14 }}
- protocol: TLS
tcpRoutes:
- backends:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Copyright 2026 The Agent Substrate Authors
#
# 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 credential providers
templates:
- atenet-egress.yaml
tests:
- it: resolves only the bundled k8s.io provider by default
documentSelector:
path: kind
value: ConfigMap
asserts:
- matchRegex:
path: data["config.yaml"]
pattern: '(?s)credentialProviders:\n\s+- uriAuthority: k8s\.io\n\s+target:\n\s+host: RELEASE-NAME-k8s-credential-provider\.NAMESPACE\.svc:50051'
- notMatchRegex:
path: data["config.yaml"]
pattern: '(?s)(?:uriAuthority:.*){3}'

- it: adds a provider to both the HTTPS and the HTTP egress listener
set:
credentialProvider.additionalProviders:
- uriAuthority: kagent.dev
host: kagent-credentials.kagent.svc:50051
documentSelector:
path: kind
value: ConfigMap
asserts:
- matchRegex:
path: data["config.yaml"]
pattern: '(?s)protocol: HTTPS.*- uriAuthority: kagent\.dev\n\s+target:\n\s+host: kagent-credentials\.kagent\.svc:50051\n\s+policies:\n\s+backendTLS:\n\s+cert: /run/podidentity\.podcert\.ate\.dev/credential-bundle\.pem\n\s+key: /run/podidentity\.podcert\.ate\.dev/credential-bundle\.pem\n\s+root: /run/servicedns\.podcert\.ate\.dev/trust-bundle\.pem.*protocol: HTTP\n.*- uriAuthority: kagent\.dev'

- it: refuses an entry without a host
set:
credentialProvider.additionalProviders:
- uriAuthority: kagent.dev
asserts:
- failedTemplate:
errorMessage: credentialProvider.additionalProviders entries need uriAuthority and host

- it: refuses to replace the bundled provider
set:
credentialProvider.additionalProviders:
- uriAuthority: k8s.io
host: other.example.svc:50051
asserts:
- failedTemplate:
errorMessage: credentialProvider.additionalProviders cannot replace the bundled k8s.io provider

- it: refuses the same authority twice
set:
credentialProvider.additionalProviders:
- uriAuthority: kagent.dev
host: a.kagent.svc:50051
- uriAuthority: kagent.dev
host: b.kagent.svc:50051
asserts:
- failedTemplate:
errorMessage: credentialProvider.additionalProviders names kagent.dev twice

- it: refuses an entry without a uriAuthority
set:
credentialProvider.additionalProviders:
- host: kagent-credentials.kagent.svc:50051
asserts:
- failedTemplate:
errorMessage: credentialProvider.additionalProviders entries need uriAuthority and host

- it: refuses a uriAuthority that is not a lowercase DNS name
set:
credentialProvider.additionalProviders:
- uriAuthority: "Kagent.dev\n target: {}"
host: kagent-credentials.kagent.svc:50051
asserts:
- failedTemplate:
errorMessage: "credentialProvider.additionalProviders: uriAuthority \"Kagent.dev\\n target: {}\" must be a lowercase DNS name"

- it: refuses a host the servicedns certificate cannot name
set:
credentialProvider.additionalProviders:
- uriAuthority: kagent.dev
host: kagent-credentials.kagent.svc.cluster.local:50051
asserts:
- failedTemplate:
errorMessage: "credentialProvider.additionalProviders: host \"kagent-credentials.kagent.svc.cluster.local:50051\" must be <service>.<namespace>.svc:<port>"
12 changes: 12 additions & 0 deletions charts/substrate/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ credentialProvider:
limits:
cpu: 500m
memory: 256Mi
# Further providers the egress gateway resolves ate-secret:// URIs through,
# by URI authority (a lowercase DNS name), beside the bundled k8s.io
# one. host is <service>.<namespace>.svc:<port>: the gateway verifies the
# provider's servicedns.podcert.ate.dev serving certificate, which names its
# Service in that form, and the Service must select the provider's pod. The
# gateway presents its pod identity certificate
# (spiffe://cluster.local/ns/<release namespace>/sa/<release>-atenet-egress);
# the provider must require that client certificate, chained to the
# podidentity.podcert.ate.dev trust bundle, and refuse any other caller.
additionalProviders: []
# - uriAuthority: kagent.dev
# host: kagent-credentials.kagent.svc:50051

# Name of a ConfigMap in the release namespace that supplies per-environment
# overrides for ate-api-server (ATE_API_POSTGRES_READ_WRITE_CONNECTION_STRING, ...).
Expand Down