Skip to content

Commit a7be49f

Browse files
committed
Reconcile fork features with PostgreSQL-only upstream
Signed-off-by: Eitan Yarmush <eitan.yarmush@solo.io>
1 parent 9439768 commit a7be49f

13 files changed

Lines changed: 195 additions & 160 deletions

File tree

charts/substrate-crds/templates/ate.dev_actortemplates.yaml

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -497,9 +497,9 @@ spec:
497497
DataSources is the list of data sources to place within the SystemInfo
498498
volume.
499499
500-
At most one actorMetadata entry may appear; this is what keeps file
501-
paths unique across the whole volume (uniqueness within the entry is
502-
enforced on its items).
500+
At most one actorMetadata entry may appear, and file paths must be
501+
unique across all entries (uniqueness within actorMetadata is enforced
502+
on its items).
503503
items:
504504
description: |-
505505
SystemInfoDataSource is a container allowing you to pick a particular
@@ -534,17 +534,18 @@ spec:
534534
path:
535535
description: |-
536536
Relative path from the root of the SystemInfo volume at which the
537-
field's value is written. Must be a clean relative Unix path: must not
538-
start or end with '/', and contain no ':', '..', '.', '//', or control
539-
characters.
537+
field's value is written. Must be a clean relative Unix path: it must
538+
not start or end with '/' and must not contain ':', '//', '.' or '..'
539+
segments, or control characters.
540540
maxLength: 255
541541
minLength: 1
542542
type: string
543543
x-kubernetes-validations:
544544
- message: 'path must be a clean relative
545-
Unix path: must not start or end with
546-
''/'', and contain no '':'', ''..'',
547-
''.'', ''//'', or control characters'
545+
Unix path: it must not start or end
546+
with ''/'' and must not contain '':'',
547+
''//'', ''.'' or ''..'' segments, or
548+
control characters'
548549
rule: '!self.startsWith(''/'') && !self.endsWith(''/'')
549550
&& !self.contains(''//'') && !self.contains('':'')
550551
&& !self.matches(''[\x00-\x1f\x7f]'')
@@ -567,18 +568,76 @@ spec:
567568
required:
568569
- items
569570
type: object
571+
trustBundle:
572+
description: |-
573+
TrustBundleDataSource is a SystemInfo volume data source that projects the
574+
trust anchors of a named trust bundle to a single PEM file — inspired by
575+
the Kubernetes clusterTrustBundle projected volume source, but
576+
source-neutral: the name selects a bundle substrate knows how to fetch,
577+
and where it is fetched from is a substrate deployment concern, not part
578+
of this API (atelet enforces the supported set and resolves the backend).
579+
580+
Supported names are allowlisted in atelet. Initially the only supported
581+
bundle is "egress-mitm.ate.dev" (the egress gateway CA bundle), resolved
582+
from the Kubernetes ClusterTrustBundle (certificates.k8s.io/v1beta1) that
583+
atecontroller derives from the egress-mitm-ca-pool; a configurable backend
584+
registry may widen this later.
585+
586+
The bundle is resolved and sanitized on the node when the actor starts:
587+
atelet reads the backing object through a cluster-wide watch and keeps
588+
only CERTIFICATE PEM blocks, deduplicated and deliberately shuffled (order
589+
carries no meaning); the actor itself never talks to any bundle backend.
590+
Starting the actor fails if the named bundle is not on the allowlist, its
591+
backend is unavailable in this deployment, or the resolved bundle is
592+
missing, empty, or unparseable.
593+
properties:
594+
name:
595+
description: |-
596+
Name of the trust bundle to project. Must be a bundle name supported
597+
by this deployment (currently only "egress-mitm.ate.dev").
598+
maxLength: 253
599+
minLength: 1
600+
type: string
601+
path:
602+
description: |-
603+
Relative path from the root of the SystemInfo volume at which the PEM
604+
bundle is written. Must be a clean relative Unix path: it must not
605+
start or end with '/' and must not contain ':', '//', '.' or '..'
606+
segments, or control characters.
607+
maxLength: 255
608+
minLength: 1
609+
type: string
610+
x-kubernetes-validations:
611+
- message: 'path must be a clean relative Unix
612+
path: it must not start or end with ''/''
613+
and must not contain '':'', ''//'', ''.''
614+
or ''..'' segments, or control characters'
615+
rule: '!self.startsWith(''/'') && !self.endsWith(''/'')
616+
&& !self.contains(''//'') && !self.contains('':'')
617+
&& !self.matches(''[\x00-\x1f\x7f]'') && !self.matches(''(^|/)[.][.]?(/|$)'')'
618+
required:
619+
- name
620+
- path
621+
type: object
570622
type: object
571623
x-kubernetes-validations:
572-
- message: exactly one of the fields in [actorMetadata]
573-
must be set
574-
rule: '[has(self.actorMetadata)].filter(x,x==true).size()
624+
- message: exactly one of the fields in [actorMetadata
625+
trustBundle] must be set
626+
rule: '[has(self.actorMetadata),has(self.trustBundle)].filter(x,x==true).size()
575627
== 1'
576-
maxItems: 32
628+
maxItems: 8
577629
type: array
578630
x-kubernetes-validations:
579631
- message: dataSources must contain at most one actorMetadata
580632
entry
581633
rule: self.filter(x, has(x.actorMetadata)).size() <= 1
634+
- message: dataSources must not contain duplicate paths
635+
rule: self.all(x, !has(x.trustBundle) || self.exists_one(y,
636+
has(y.trustBundle) && y.trustBundle.path == x.trustBundle.path))
637+
- message: dataSources must not contain duplicate paths
638+
rule: '!self.exists(x, has(x.trustBundle) && self.exists(y,
639+
has(y.actorMetadata) && y.actorMetadata.items.exists(i,
640+
i.path == x.trustBundle.path)))'
582641
type: object
583642
required:
584643
- name

charts/substrate-crds/templates/ate.dev_csidriverconfigs.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ spec:
8181
on the worker nodes. If empty, ATE defaults to unix:///var/lib/kubelet/plugins/[DriverName]/csi.sock.
8282
pattern: ^unix://.+$
8383
type: string
84+
tls:
85+
description: TLS configures TLS/mTLS for the connection to the ControllerEndpoint.
86+
properties:
87+
enabled:
88+
description: Enabled controls whether TLS is used.
89+
type: boolean
90+
serverName:
91+
description: ServerName override for TLS verification.
92+
type: string
93+
usePodIdentity:
94+
description: UsePodIdentity indicates whether to reuse Substrate's
95+
Pod Identity (SPIFFE) certificates.
96+
type: boolean
97+
required:
98+
- enabled
99+
type: object
100+
x-kubernetes-validations:
101+
- message: tls.usePodIdentity must be true when tls.enabled is true;
102+
manual certificates are not yet supported
103+
rule: '!self.enabled || (has(self.usePodIdentity) && self.usePodIdentity)'
84104
required:
85105
- controllerEndpoint
86106
- driverName

charts/substrate/templates/ate-api-server-envvars.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@ metadata:
2020
name: {{ .Values.ateApiServerEnvVarsConfigMap }}
2121
namespace: {{ .Release.Namespace }}
2222
data:
23-
ATE_API_STORE_BACKEND: "postgres"
2423
ATE_API_POSTGRES_CONNECTION_STRING: {{ .Values.postgres.connectionString | default (printf "postgresql://postgres@%s.%s.svc:5432/atepg?sslmode=verify-full&sslrootcert=/run/servicedns.podcert.ate.dev/trust-bundle.pem&sslcert=/run/podidentity.podcert.ate.dev/credential-bundle.pem&sslkey=/run/podidentity.podcert.ate.dev/credential-bundle.pem" (include "substrate.fullname" (list "postgres" .)) .Release.Namespace) | quote }}

charts/substrate/templates/ate-api-server.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ spec:
8484
- "--grpc-listen-addr=0.0.0.0:443"
8585
- "--grpc-server-cred-bundle=/run/servicedns.podcert.ate.dev/credential-bundle.pem"
8686
- "--authentication-config=/etc/ateapi/authentication/authentication.yaml"
87-
- "--store-backend=@env"
8887
- "--postgres-connection-string=@env"
8988
- "--actor-id-jwt-pool=/run/actor-id-jwt-pool/pool.json"
9089
- "--actor-id-ca-pool=/run/actor-id-ca-pool/pool.json"

charts/substrate/templates/atelet.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ rules:
3232
- apiGroups: ["ate.dev"]
3333
resources: ["csidriverconfigs"]
3434
verbs: ["get", "watch", "list"]
35+
# ClusterTrustBundles referenced by SystemInfo trustBundle data sources are
36+
# resolved on the node: atelet reads them through an informer and projects
37+
# the sanitized PEM into actors (see cmd/atelet/trustbundle.go).
38+
- apiGroups: ["certificates.k8s.io"]
39+
resources: ["clustertrustbundles"]
40+
verbs: ["get", "watch", "list"]
3541
---
3642
apiVersion: rbac.authorization.k8s.io/v1
3743
kind: ClusterRoleBinding

charts/substrate/templates/atenet-egress.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ spec:
132132
- --ateapi-client-cert=/run/podidentity.podcert.ate.dev/credential-bundle.pem
133133
- --actor-identity-ca-file=/run/actor-id-ca-certs/ca.crt
134134
- --otlp-collector-address=
135-
- --envoy-admin-address=127.0.0.1:15000
135+
- --envoy-admin-address=localhost:15000
136136
- --atenet-router=agentgateway
137137
env:
138138
- name: POD_NAME
@@ -205,6 +205,7 @@ metadata:
205205
namespace: {{ .Release.Namespace }}
206206
spec:
207207
type: ClusterIP
208+
ipFamilyPolicy: PreferDualStack
208209
selector:
209210
app: atenet-egress
210211
ports:

charts/substrate/templates/atenet-router.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ metadata:
316316
namespace: {{ .Release.Namespace }}
317317
spec:
318318
type: ClusterIP
319+
ipFamilyPolicy: PreferDualStack
319320
selector:
320321
app: atenet-router
321322
ports:

charts/substrate/templates/postgres.yaml

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,55 @@ data:
2424
postgresql.conf: |
2525
listen_addresses = '*'
2626
ssl = on
27-
ssl_cert_file = '/run/tls/credential-bundle.pem'
28-
ssl_key_file = '/run/tls/credential-bundle.pem'
27+
ssl_cert_file = '/run/servicedns.podcert.ate.dev/credential-bundle.pem'
28+
ssl_key_file = '/run/servicedns.podcert.ate.dev/credential-bundle.pem'
2929
ssl_ca_file = '/run/podidentity.podcert.ate.dev/trust-bundle.pem'
3030
hba_file = '/etc/postgresql/pg_hba.conf'
3131
pg_hba.conf: |
32+
# Local socket access is limited to processes in this pod and is used by
33+
# health checks, the workload's idempotent database bootstrap, and the
34+
# tls-reloader sidecar's configuration reloads.
3235
local all all trust
36+
# PostgreSQL verifies client certificates against the pod-identity CA. It
37+
# does not need its own serving CA because it never verifies its server certificate.
3338
hostssl all all all trust clientcert=verify-ca
39+
reload-tls.sh: |
40+
# PostgreSQL opens ssl_cert_file, ssl_key_file and ssl_ca_file at startup
41+
# and on SIGHUP, and nowhere else. The kubelet replaces the projected pod
42+
# certificate in place about 30 minutes before it expires, so without this
43+
# loop the server keeps presenting the certificate it booted with until it
44+
# expires about a day later and every client stops trusting it.
45+
set -eu
46+
47+
# As PID 1 this shell only sees SIGTERM if a handler is installed, and only
48+
# acts on it between commands, so the sleep below runs in the background
49+
# and is waited on. Without both halves the pod takes the full termination
50+
# grace period to go away.
51+
trap 'exit 0' TERM INT
52+
53+
CERT=/run/servicedns.podcert.ate.dev/credential-bundle.pem
54+
CA=/run/podidentity.podcert.ate.dev/trust-bundle.pem
55+
56+
# Comfortably inside the 30m headroom (notAfter - beginRefreshAt) that
57+
# cmd/podcertcontroller/internal/servicednssigner/servicednssigner.go
58+
# leaves; hashing two small files costs nothing.
59+
INTERVAL=60
60+
61+
reloaded=""
62+
while true; do
63+
current="$(sha256sum "${CERT}" "${CA}")"
64+
# Reloading fails until the server is accepting connections, which is
65+
# where every pod starts out, so only record a hash once it has worked.
66+
# Starting empty also means a restart of this container costs one
67+
# redundant reload rather than a missed one.
68+
if [ "${current}" != "${reloaded}" ] \
69+
&& psql -U postgres -d postgres -Atc 'SELECT pg_reload_conf()' >/dev/null 2>&1; then
70+
reloaded="${current}"
71+
echo "$(date -u +%FT%TZ) reloaded TLS configuration"
72+
fi
73+
sleep "${INTERVAL}" &
74+
wait $!
75+
done
3476
---
3577
apiVersion: v1
3678
kind: Service
@@ -62,23 +104,46 @@ spec:
62104
labels:
63105
app: {{ $name }}
64106
spec:
107+
securityContext:
108+
# Group ownership of the projected certificate below, and of the data
109+
# volume so that a freshly provisioned one is writable. OnRootMismatch
110+
# keeps the kubelet from walking the data directory on every start,
111+
# which would leave PGDATA group-writable and postgres refusing to run.
112+
fsGroup: 70
113+
fsGroupChangePolicy: OnRootMismatch
114+
# PostgreSQL re-reads its TLS files only on SIGHUP, so this sidecar
115+
# reloads the server whenever the kubelet rotates the projected pod
116+
# certificate. fsGroup is also what makes that projection readable: the
117+
# kubelet writes it root-owned for as long as the pod's containers do not
118+
# all agree on one non-root user, and grants the fsGroup group access,
119+
# landing the key at root:postgres 0640, the only shared mode PostgreSQL
120+
# accepts. Pinning runAsUser on the postgres container would make the key
121+
# postgres-owned and group-readable, which it rejects.
122+
# See https://www.postgresql.org/docs/current/ssl-tcp.html#SSL-SETUP
65123
initContainers:
66-
- name: fix-tls-perms
124+
- name: tls-reloader
125+
restartPolicy: Always
67126
image: {{ .Values.images.postgres }}
68127
securityContext:
69128
runAsUser: 70
70129
command:
71130
- /bin/sh
72-
- -c
73-
- |
74-
set -e
75-
cp /run/servicedns.podcert.ate.dev/credential-bundle.pem /run/tls/credential-bundle.pem
76-
chmod 600 /run/tls/credential-bundle.pem
131+
- /etc/postgresql/reload-tls.sh
77132
volumeMounts:
133+
- name: config
134+
mountPath: /etc/postgresql
78135
- name: servicedns
79136
mountPath: /run/servicedns.podcert.ate.dev
80-
- name: tls
81-
mountPath: /run/tls
137+
readOnly: true
138+
- name: podidentity-ca
139+
mountPath: /run/podidentity.podcert.ate.dev
140+
readOnly: true
141+
- name: socket
142+
mountPath: /var/run/postgresql
143+
resources:
144+
requests:
145+
cpu: 10m
146+
memory: 32Mi
82147
containers:
83148
- name: postgres
84149
image: {{ .Values.images.postgres }}
@@ -120,11 +185,14 @@ spec:
120185
volumeMounts:
121186
- name: config
122187
mountPath: /etc/postgresql
123-
- name: tls
124-
mountPath: /run/tls
188+
- name: servicedns
189+
mountPath: /run/servicedns.podcert.ate.dev
190+
readOnly: true
125191
- name: podidentity-ca
126192
mountPath: /run/podidentity.podcert.ate.dev
127193
readOnly: true
194+
- name: socket
195+
mountPath: /var/run/postgresql
128196
- name: data
129197
mountPath: /var/lib/postgresql/data
130198
resources:
@@ -135,12 +203,17 @@ spec:
135203
name: {{ $name }}-config
136204
- name: servicedns
137205
projected:
206+
# 0600 plus the group read that fsGroup adds is the 0640 above.
207+
defaultMode: 0600
138208
sources:
139209
- podCertificate:
140210
signerName: servicedns.podcert.ate.dev/identity
141211
keyType: ECDSAP256
142212
credentialBundlePath: credential-bundle.pem
143-
- name: tls
213+
# The unix socket directory, shared so the sidecar can ask the running
214+
# server to reload. The image defaults both the server and its clients to
215+
# this path, so nothing else has to know about it.
216+
- name: socket
144217
emptyDir: {}
145218
- name: podidentity-ca
146219
projected:

cmd/ateapi/internal/controlapi/workflow_lock_test.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@ import (
2020
"testing"
2121
"time"
2222

23-
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store/ateredis"
23+
"github.com/agent-substrate/substrate/cmd/ateapi/internal/store"
2424
"github.com/agent-substrate/substrate/internal/resources"
25-
"github.com/alicebob/miniredis/v2"
26-
"github.com/redis/go-redis/v9"
2725
)
2826

27+
type lockStore struct{ store.Interface }
28+
29+
func (lockStore) AcquireLock(ctx context.Context, _ string) (*store.Lock, error) {
30+
return store.NewLock(ctx, func() {}), nil
31+
}
32+
2933
func TestAcquireActorLockWorkflowDeadline(t *testing.T) {
30-
mr := miniredis.RunT(t)
31-
rdb := redis.NewClusterClient(&redis.ClusterOptions{Addrs: []string{mr.Addr()}})
32-
t.Cleanup(func() { _ = rdb.Close() })
33-
w := &ActorWorkflow{store: ateredis.NewPersistence(rdb), workflowDeadline: 20 * time.Millisecond}
34+
w := &ActorWorkflow{store: lockStore{}, workflowDeadline: 20 * time.Millisecond}
3435

3536
ctx, lock, err := w.acquireActorLock(context.Background(), resources.ActorRef{Atespace: "space", Name: "actor"})
3637
if err != nil {

0 commit comments

Comments
 (0)