Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ devenv.local.yaml

# pre-commit
.pre-commit-config.yaml

# Trust anchor the dpu-agent mirrors for key-less consumers (issue #355). The
# agent tests point `[forge-system] root-ca` at dev/certs/forge_root.pem, so
# running them publishes a copy beside it. Runtime artifact, never committed.
dev/certs/pub/
6 changes: 6 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

94 changes: 87 additions & 7 deletions bluefield/charts/nico-fmds/templates/daemonset.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,28 @@
{{/*
Token mode pins these paths to the dpu-agent's defaults.

The agent publishes the trust anchor and serves its socket at paths taken
from its own [forge-system] config, and DPF deploys it with no config file
at all -- so it always uses /opt/forge. It has no CLI flag for either path.
Rendering fmds against a different certsDir/rootCaFile therefore points it
at files the agent will never create: the init container waits forever for a
CA that is not coming, or the token source connects to a socket that does
not exist.

Failing here turns that silent hang into a render-time error. The overrides
remain available in cert mode, where fmds reads the credentials directly and
nothing has to agree with the agent.
*/}}
{{- if .Values.useNodeTokens }}
{{- $dir := .Values.certsDir | default "/opt/forge" }}
{{- $rootCa := .Values.rootCaFile | default "forge_root.pem" }}
{{- if ne $dir "/opt/forge" }}
{{- fail (printf "useNodeTokens requires the dpu-agent's default certsDir (/opt/forge); got %q. The agent has no way to be told otherwise, so fmds would wait for a CA that is never published." $dir) }}
{{- end }}
{{- if ne $rootCa "forge_root.pem" }}
{{- fail (printf "useNodeTokens requires the dpu-agent's default rootCaFile (forge_root.pem); got %q. The agent publishes under its own filename, so fmds would wait for a file that is never created." $rootCa) }}
{{- end }}
{{- end }}
apiVersion: apps/v1
kind: DaemonSet
metadata:
Expand Down Expand Up @@ -51,29 +76,51 @@ spec:
- /busybox/sh
- -c
- |
{{- $dir := .Values.certsDir | default "/opt/nico" }}
{{- $rootCa := .Values.rootCaFile | default "nico_root.pem" }}
{{- $dir := .Values.certsDir | default "/opt/forge" }}
{{- $rootCa := .Values.rootCaFile | default "forge_root.pem" }}
{{- if .Values.useNodeTokens }}
echo "Waiting for root CA in {{ $dir }}/pub ..."
while [ ! -f {{ $dir }}/pub/{{ $rootCa }} ]; do
sleep 5
done
echo "Root CA found, starting nico-fmds (node tokens from the dpu-agent)."
{{- else }}
echo "Waiting for certificates in {{ $dir }} ..."
while [ ! -f {{ $dir }}/{{ $rootCa }} ] || [ ! -f {{ $dir }}/machine_cert.pem ] || [ ! -f {{ $dir }}/machine_cert.key ]; do
sleep 5
done
echo "Certificates found, starting nico-fmds."
{{- end }}
volumeMounts:
{{- if .Values.useNodeTokens }}
# Token mode never needs the credentials directory, not even to
# wait: the agent publishes the trust anchor to pub/, which holds
# nothing else.
- name: nico-certs-pub
mountPath: {{ .Values.certsDir | default "/opt/forge" }}/pub
readOnly: true
{{- else }}
- name: nico-certs
mountPath: {{ .Values.certsDir | default "/opt/nico" }}
mountPath: {{ .Values.certsDir | default "/opt/forge" }}
readOnly: true
{{- end }}
containers:
- name: nico-fmds
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
args:
{{- $dir := .Values.certsDir | default "/opt/nico" }}
{{- $rootCa := .Values.rootCaFile | default "nico_root.pem" }}
{{- $dir := .Values.certsDir | default "/opt/forge" }}
{{- $rootCa := .Values.rootCaFile | default "forge_root.pem" }}
- "--grpc-address=$(POD_IP):50052"
{{- if .Values.useNodeTokens }}
- "--root-ca={{ $dir }}/pub/{{ $rootCa }}"
- "--node-token-socket={{ $dir }}/run/agent.sock"
{{- else }}
- "--root-ca={{ $dir }}/{{ $rootCa }}"
- "--client-cert={{ $dir }}/machine_cert.pem"
- "--client-key={{ $dir }}/machine_cert.key"
{{- end }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
{{- with toYaml .Values.serviceDaemonSet.resources }}
resources:
Expand Down Expand Up @@ -106,14 +153,47 @@ spec:
fieldRef:
fieldPath: metadata.namespace
volumeMounts:
{{- $dir := .Values.certsDir | default "/opt/forge" }}
{{- $rootCa := .Values.rootCaFile | default "forge_root.pem" }}
{{- if .Values.useNodeTokens }}
# Token mode: the bearer JWT is the credential, so this container
# gets the trust anchor and nothing else. pub/ holds only the CA,
# so the machine key is absent from the pod — and because this is a
# directory mount rather than a subPath, the agent's atomic
# republish of the CA reaches a running pod.
- name: nico-certs-pub
mountPath: {{ $dir }}/pub
readOnly: true
# Read-only: connecting to a unix socket does not write to the
# filesystem, and Linux exempts socket inodes from the read-only
# mount check, so connect(2) works here. The socket's own 0600 mode
# is what gates access.
- name: nico-agent-run
mountPath: {{ $dir }}/run
readOnly: true
{{- else }}
- name: nico-certs
mountPath: {{ .Values.certsDir | default "/opt/nico" }}
mountPath: {{ $dir }}
readOnly: true
{{- end }}
volumes:
{{- if .Values.useNodeTokens }}
# No credentials-directory volume at all in token mode: nothing in this
# pod is entitled to the machine key.
- name: nico-certs-pub
hostPath:
path: {{ .Values.certsDir | default "/opt/forge" }}/pub
type: DirectoryOrCreate
- name: nico-agent-run
hostPath:
path: {{ .Values.certsDir | default "/opt/forge" }}/run
type: DirectoryOrCreate
{{- else }}
- name: nico-certs
hostPath:
path: {{ .Values.certsDir | default "/opt/nico" }}
path: {{ .Values.certsDir | default "/opt/forge" }}
type: DirectoryOrCreate
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
Expand Down
190 changes: 190 additions & 0 deletions bluefield/charts/nico-fmds/tests/node_tokens_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
suite: node-auth token mode
templates:
- daemonset.yaml
tests:
# The point of token mode is that the machine private key is not reachable
# from this pod at all. The key sits beside the root CA in certsDir, and the
# container runs as UID 0, so a read-only mount of that directory would not
# be enough -- the volume must be absent entirely.
- it: should not mount the credentials directory anywhere in token mode
set:
image:
repository: test
tag: test
useNodeTokens: true
asserts:
- notContains:
path: spec.template.spec.volumes
content:
name: nico-certs
hostPath:
path: /opt/forge
type: DirectoryOrCreate
- notContains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: nico-certs
mountPath: /opt/forge
readOnly: true
- notContains:
path: spec.template.spec.initContainers[0].volumeMounts
content:
name: nico-certs
mountPath: /opt/forge
readOnly: true

# pub/ is mounted as a directory, not as a subPath of the CA file: the agent
# installs the CA by atomic rename, and a subPath mount would pin the old
# inode so a rotation never reached a running pod.
- it: should mount only the published CA directory and the agent socket
set:
image:
repository: test
tag: test
useNodeTokens: true
asserts:
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: nico-certs-pub
mountPath: /opt/forge/pub
readOnly: true
- contains:
path: spec.template.spec.containers[0].volumeMounts
content:
name: nico-agent-run
mountPath: /opt/forge/run
readOnly: true
- lengthEqual:
path: spec.template.spec.containers[0].volumeMounts
count: 2
- contains:
path: spec.template.spec.volumes
content:
name: nico-certs-pub
hostPath:
path: /opt/forge/pub
type: DirectoryOrCreate
# The socket mount is useless without the volume backing it, and a mount
# naming a volume that does not exist fails the pod at admission rather
# than here — so pin the pair, not just the mount.
- contains:
path: spec.template.spec.volumes
content:
name: nico-agent-run
hostPath:
path: /opt/forge/run
type: DirectoryOrCreate
- lengthEqual:
path: spec.template.spec.volumes
count: 2

- it: should read the CA from pub/ and authenticate with a token, not a cert
set:
image:
repository: test
tag: test
useNodeTokens: true
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: --root-ca=/opt/forge/pub/forge_root.pem
- contains:
path: spec.template.spec.containers[0].args
content: --node-token-socket=/opt/forge/run/agent.sock
- notContains:
path: spec.template.spec.containers[0].args
content: --client-cert=/opt/forge/machine_cert.pem
- notContains:
path: spec.template.spec.containers[0].args
content: --client-key=/opt/forge/machine_cert.key

# The init container gates the main one; in token mode it has no business
# waiting on the machine cert/key, which may never arrive in this pod.
- it: should wait only for the published CA in token mode
set:
image:
repository: test
tag: test
useNodeTokens: true
asserts:
- matchRegex:
path: spec.template.spec.initContainers[0].command[2]
pattern: '/opt/forge/pub/forge_root\.pem'
- notMatchRegex:
path: spec.template.spec.initContainers[0].command[2]
pattern: 'machine_cert\.key'
- contains:
path: spec.template.spec.initContainers[0].volumeMounts
content:
name: nico-certs-pub
mountPath: /opt/forge/pub
readOnly: true
- lengthEqual:
path: spec.template.spec.initContainers[0].volumeMounts
count: 1

# Token mode pins the paths to the agent's defaults, because the agent has no
# flag for them and DPF gives it no config file. Rendering fmds elsewhere
# would leave the init container waiting for a CA that is never published, so
# the chart refuses at template time rather than at 3am.
- it: should refuse a certsDir override in token mode
set:
image:
repository: test
tag: test
useNodeTokens: true
certsDir: /srv/creds
asserts:
- failedTemplate:
errorMessage: 'useNodeTokens requires the dpu-agent''s default certsDir (/opt/forge); got "/srv/creds". The agent has no way to be told otherwise, so fmds would wait for a CA that is never published.'

- it: should refuse a rootCaFile override in token mode
set:
image:
repository: test
tag: test
useNodeTokens: true
rootCaFile: site_root.pem
asserts:
- failedTemplate:
errorMessage: 'useNodeTokens requires the dpu-agent''s default rootCaFile (forge_root.pem); got "site_root.pem". The agent publishes under its own filename, so fmds would wait for a file that is never created.'

# The same overrides stay available in cert mode, where fmds reads the
# credentials itself and nothing has to agree with the agent.
- it: should still honour certsDir in cert mode
set:
image:
repository: test
tag: test
certsDir: /srv/creds
rootCaFile: site_root.pem
asserts:
- contains:
path: spec.template.spec.containers[0].args
content: --root-ca=/srv/creds/site_root.pem

# mTLS remains the default, and must not acquire the token-mode plumbing.
- it: should keep the credentials mount and no pub volume by default
set:
image:
repository: test
tag: test
asserts:
- contains:
path: spec.template.spec.volumes
content:
name: nico-certs
hostPath:
path: /opt/forge
type: DirectoryOrCreate
- notContains:
path: spec.template.spec.volumes
content:
name: nico-certs-pub
hostPath:
path: /opt/forge/pub
type: DirectoryOrCreate
- notContains:
path: spec.template.spec.containers[0].args
content: --node-token-socket=/opt/forge/run/agent.sock
Loading
Loading