1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-31 04:04:51 +00:00

deploy: add spire manifests in helm and kustomize

Signed-off-by: TessaIO <ahmedgrati1999@gmail.com>
Signed-off-by: AhmedGrati <ahmedgrati1999@gmail.com>
This commit is contained in:
TessaIO 2024-04-14 20:42:11 +02:00 committed by AhmedGrati
parent c77c370d0d
commit 73e31a879b
14 changed files with 275 additions and 119 deletions

View file

@ -0,0 +1,6 @@
dependencies:
- name: spire
repository: https://spiffe.github.io/helm-charts-hardened/
version: 0.24.1
digest: sha256:f3b4dc973a59682bf3aa5ca9b53322f57935dd093081e82a37b8082e00becbe9
generated: "2024-12-20T16:52:40.180416+01:00"

View file

@ -13,3 +13,8 @@ keywords:
- node-labels
type: application
version: 0.2.1
dependencies:
- name: spire
version: 0.24.1
repository: https://spiffe.github.io/helm-charts-hardened/
condition: spire.enabled

View file

@ -145,11 +145,25 @@ spec:
{{- with .Values.master.extraArgs }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- if .Values.spire.enabled }}
- "-enable-spiffe"
{{- end }}
volumeMounts:
{{- if .Values.spire.enabled }}
- name: spire-agent-socket
mountPath: /run/spire/agent-sockets/api.sock
readOnly: true
{{- end }}
- name: nfd-master-conf
mountPath: "/etc/kubernetes/node-feature-discovery"
readOnly: true
volumes:
{{- if .Values.spire.enabled }}
- name: spire-agent-socket
hostPath:
path: /run/spire/agent-sockets/api.sock
type: Socket
{{- end }}
- name: nfd-master-conf
configMap:
name: {{ include "node-feature-discovery.fullname" . }}-master-conf

View file

@ -110,10 +110,18 @@ spec:
{{- with .Values.worker.extraArgs }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- if .Values.spire.enabled }}
- "-enable-spiffe"
{{- end }}
ports:
- containerPort: {{ .Values.worker.port | default "8080"}}
name: http
volumeMounts:
{{- if .Values.spire.enabled }}
- name: spire-agent-socket
mountPath: /run/spire/agent-sockets/api.sock
readOnly: true
{{- end }}
- name: host-boot
mountPath: "/host-boot"
readOnly: true
@ -144,6 +152,12 @@ spec:
mountPath: "/etc/kubernetes/node-feature-discovery"
readOnly: true
volumes:
{{- if .Values.spire.enabled }}
- name: spire-agent-socket
hostPath:
path: /run/spire/agent-sockets/api.sock
type: Socket
{{- end }}
- name: host-boot
hostPath:
path: "/boot"

View file

@ -1,9 +1,9 @@
image:
repository: gcr.io/k8s-staging-nfd/node-feature-discovery
repository: docker.io/ahmedgrati/node-feature-discovery
# This should be set to 'IfNotPresent' for released version
pullPolicy: Always
# tag, if defined will use the given image tag, else Chart.AppVersion will be used
# tag
tag: v0.18.0-devel-105-gb1d33c2b2-dirty
imagePullSecrets: []
nameOverride: ""
@ -574,3 +574,57 @@ prometheus:
enable: false
scrapeInterval: 10s
labels: {}
spire:
enabled: true
global:
spire:
clusterName: "nfd"
trustDomain: "nfd.k8s-sigs.io"
system:
name: "spire-system"
create: false
server:
name: "spire-server"
create: false
spire-agent:
nameOverride: "spire-agent"
kubeletConnectByHostname: "true"
server:
address: "nfd-spire-server.nfd"
workloadAttestors:
unix:
enabled: true
spire-server:
nameOverride: "spire-server"
controllerManager:
enabled: true
identities:
clusterStaticEntries:
node:
parentID: spiffe://nfd.k8s-sigs.io/spire/server
spiffeID: spiffe://nfd.k8s-sigs.io/root
selectors:
- k8s_psat:agent_ns:nfd
- k8s_psat:agent_sa:nfd-agent
- k8s_psat:cluster:nfd
nfd:
parentID: spiffe://nfd.k8s-sigs.io/root
spiffeID: spiffe://nfd.k8s-sigs.io/worker
selectors:
- k8s:pod-label:app.kubernetes.io/name:node-feature-discovery
caSubject:
commonName: "nfd.k8s-sigs.io"
country: "US"
organization: "SPIFFE"
upstream:
enabled: false
spiffe-csi-driver:
enabled: false
spiffe-oidc-discovery-provider:
enabled: false
tornjak-frontend:
enabled: false

View file

@ -306,3 +306,19 @@ Example:
```bash
nfd-master -resync-period=2h
```
### -enable-spiffe
the `-enable-spiffe` flag enables SPIFFE verification for the created NodeFeature
objects created by the worker. When enabled, master verifies the signature that
is put on the annotations part of the NodeFeature object, and updates
Kubernetes nodes if the signature is verified. The feature should be enabled,
after deploying SPIFFE, and you can do it through the Helm chart.
Default: false.
Example:
```bash
nfd-master -enable-spiffe
```

View file

@ -273,3 +273,19 @@ Default: 0
Comma-separated list of `pattern=N` settings for file-filtered logging.
Default: *empty*
### -enable-spiffe
the `-enable-spiffe` flag enables signing NodeFeature spec on the worker side
and puts the signature in the annotations side of the NodeFeature object.
The signature is verified afterwards by the master. The feature
should be enabled, after deploying SPIFFE, and you can do it through
the Helm chart.
Default: false.
Example:
```bash
nfd-master -enable-spiffe
```

3
go.mod
View file

@ -19,6 +19,7 @@ require (
github.com/prometheus/client_golang v1.21.0
github.com/smartystreets/goconvey v1.8.1
github.com/spf13/cobra v1.9.1
github.com/spiffe/go-spiffe/v2 v2.5.0
github.com/stretchr/testify v1.10.0
github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
@ -71,6 +72,7 @@ require (
github.com/euank/go-kmsg-parser v2.0.0+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
@ -131,6 +133,7 @@ require (
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/errs v1.4.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.16 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect
go.etcd.io/etcd/client/v3 v3.5.16 // indirect

28
go.sum
View file

@ -76,6 +76,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@ -123,27 +125,6 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/Z
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw=
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@ -276,6 +257,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -283,7 +266,6 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@ -301,6 +283,8 @@ github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chq
github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I=
go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0=

View file

@ -51,12 +51,6 @@ import (
"sigs.k8s.io/yaml"
nfdclientset "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
klogutils "sigs.k8s.io/node-feature-discovery/pkg/utils/klog"
spiffe "sigs.k8s.io/node-feature-discovery/pkg/utils/spiffe"
taintutils "k8s.io/kubernetes/pkg/util/taints"
"sigs.k8s.io/yaml"
"sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/nodefeaturerule"
@ -64,11 +58,12 @@ import (
nfdfeatures "sigs.k8s.io/node-feature-discovery/pkg/features"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
klogutils "sigs.k8s.io/node-feature-discovery/pkg/utils/klog"
spiffe "sigs.k8s.io/node-feature-discovery/pkg/utils/spiffe"
"sigs.k8s.io/node-feature-discovery/pkg/version"
)
// SocketPath specifies Spiffe Socket Path
const SocketPath = "unix:///run/spire/sockets/agent.sock"
const SocketPath = "unix:///run/spire/agent-sockets/api.sock"
// Labels are a Kubernetes representation of discovered features.
type Labels map[string]string
@ -219,12 +214,6 @@ func NewNfdMaster(opts ...NfdMasterOption) (NfdMaster, error) {
nfd.updaterPool = newUpdaterPool(nfd)
spiffeClient, err := spiffe.NewSpiffeClient(SocketPath)
if err != nil {
return nfd, err
}
nfd.spiffeClient = spiffeClient
return nfd, nil
}
@ -308,6 +297,14 @@ func (m *nfdMaster) Run() error {
}
}
if m.config.EnableSpiffe {
spiffeClient, err := spiffe.NewSpiffeClient(SocketPath)
if err != nil {
return err
}
m.spiffeClient = spiffeClient
}
httpMux := http.NewServeMux()
// Register to metrics server
@ -642,6 +639,14 @@ func (m *nfdMaster) getAndMergeNodeFeatures(nodeName string) (*nfdv1alpha1.NodeF
return filteredObjs[i].Namespace < filteredObjs[j].Namespace
})
// If spiffe is enabled, we should filter out the non verified NFD objects
if m.config.EnableSpiffe {
filteredObjs, err = m.getVerifiedNFDObjects(filteredObjs)
if err != nil {
return &nfdv1alpha1.NodeFeature{}, err
}
}
if len(filteredObjs) > 0 {
// Merge in features
//
@ -697,55 +702,6 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(cli k8sclient.Interface, node *corev1.No
return fmt.Errorf("failed to merge NodeFeature objects for node %q: %w", node.Name, err)
}
// Sort our objects
sort.Slice(objs, func(i, j int) bool {
// Objects in our nfd namespace gets into the beginning of the list
if objs[i].Namespace == m.namespace && objs[j].Namespace != m.namespace {
return true
}
if objs[i].Namespace != m.namespace && objs[j].Namespace == m.namespace {
return false
}
// After the nfd namespace, sort objects by their name
if objs[i].Name != objs[j].Name {
return objs[i].Name < objs[j].Name
}
// Objects with the same name are sorted by their namespace
return objs[i].Namespace < objs[j].Namespace
})
// If spiffe is enabled, we should filter out the non verified NFD objects
if m.config.EnableSpiffe {
objs, err = m.getVerifiedNFDObjects(objs)
if err != nil {
return err
}
}
klog.V(1).InfoS("processing of node initiated by NodeFeature API", "nodeName", node.Name)
features := nfdv1alpha1.NewNodeFeatureSpec()
if len(objs) > 0 {
// Merge in features
//
// NOTE: changing the rule api to support handle multiple objects instead
// of merging would probably perform better with lot less data to copy.
features = objs[0].Spec.DeepCopy()
if m.config.AutoDefaultNs {
features.Labels = addNsToMapKeys(features.Labels, nfdv1alpha1.FeatureLabelNs)
}
for _, o := range objs[1:] {
s := o.Spec.DeepCopy()
if m.config.AutoDefaultNs {
s.Labels = addNsToMapKeys(s.Labels, nfdv1alpha1.FeatureLabelNs)
}
s.MergeInto(features)
}
klog.V(4).InfoS("merged nodeFeatureSpecs", "newNodeFeatureSpec", utils.DelayedDumper(features))
}
// Update node labels et al. This may also mean removing all NFD-owned
// labels (et al.), for example in the case no NodeFeature objects are
// present.
@ -1469,16 +1425,22 @@ func (m *nfdMaster) getVerifiedNFDObjects(objs []*v1alpha1.NodeFeature) ([]*v1al
}
for _, obj := range objs {
isSignatureVerified, err := spiffe.VerifyDataSignature(obj.Spec, obj.Annotations["signature"], workerPrivateKey, workerPublicKey)
spiffeObj := spiffe.SpiffeObject{
Spec: obj.Spec,
Name: obj.Name,
Namespace: obj.Namespace,
Labels: obj.Labels,
}
isSignatureVerified, err := spiffe.VerifyDataSignature(spiffeObj, obj.Annotations["signature"], workerPrivateKey, workerPublicKey)
if err != nil {
return nil, fmt.Errorf("failed to verify NodeFeature signature: %w", err)
}
if isSignatureVerified {
klog.InfoS("NodeFeature verified", "NodeFeature name", obj.Name)
klog.InfoS("NodeFeature verified", "nodefeature", klog.KObj(obj))
verifiedObjects = append(verifiedObjects, obj)
} else {
klog.InfoS("NodeFeature not verified, skipping...", "NodeFeature name", obj.Name)
klog.InfoS("NodeFeature not verified, skipping...", "nodefeature", klog.KObj(obj))
}
}
return verifiedObjects, nil

View file

@ -17,8 +17,6 @@ limitations under the License.
package nfdworker
import (
"crypto/tls"
"crypto/x509"
b64 "encoding/base64"
"encoding/json"
"fmt"
@ -67,7 +65,7 @@ import (
)
// SocketPath specifies Spiffe Socket Path
const SocketPath = "unix:///run/spire/sockets/agent.sock"
const SocketPath = "unix:///run/spire/agent-sockets/api.sock"
// NfdWorker is the interface for nfd-worker daemon
type NfdWorker interface {
@ -198,12 +196,6 @@ func NewNfdWorker(opts ...NfdWorkerOption) (NfdWorker, error) {
nfd.k8sClient = cli
}
spiffeClient, err := spiffe.NewSpiffeClient(SocketPath)
if err != nil {
return nfd, err
}
nfd.spiffeClient = spiffeClient
return nfd, nil
}
@ -326,6 +318,14 @@ func (w *nfdWorker) Run() error {
httpMux.Handle("/metrics", promhttp.HandlerFor(promRegistry, promhttp.HandlerOpts{}))
registerVersion(version.Get())
if w.config.Core.EnableSpiffe {
spiffeClient, err := spiffe.NewSpiffeClient(SocketPath)
if err != nil {
return err
}
w.spiffeClient = spiffeClient
}
err = w.runFeatureDiscovery()
if err != nil {
return err
@ -654,6 +654,7 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
Annotations: map[string]string{nfdv1alpha1.WorkerVersionAnnotation: version.Get()},
Labels: map[string]string{nfdv1alpha1.NodeFeatureObjNodeNameLabel: nodename},
OwnerReferences: m.ownerReference,
Namespace: namespace,
},
Spec: nfdv1alpha1.NodeFeatureSpec{
Features: *features,
@ -664,8 +665,7 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
// If Spiffe is enabled, we add the signature to the annotations section
if m.config.Core.EnableSpiffe {
err = m.signNodeFeatureCR(nfr)
if err != nil {
if err = m.signNodeFeatureCR(nfr); err != nil {
return err
}
}
@ -761,7 +761,13 @@ func (m *nfdWorker) signNodeFeatureCR(nfr *nfdv1alpha1.NodeFeature) error {
return fmt.Errorf("error while getting worker keys: %w", err)
}
signature, err := spiffe.SignData(nfr.Spec, workerPrivateKey)
spiffeObject := spiffe.SpiffeObject{
Spec: nfr.Spec,
Name: nfr.Name,
Namespace: nfr.Namespace,
Labels: nfr.Labels,
}
signature, err := spiffe.SignData(spiffeObject, workerPrivateKey)
if err != nil {
return fmt.Errorf("failed to sign CRD data using Spiffe: %w", err)

View file

@ -3,7 +3,9 @@ Copyright 2024 The Kubernetes 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.
@ -11,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
package spiffe
import (
"context"
@ -25,15 +27,26 @@ import (
"fmt"
"github.com/spiffe/go-spiffe/v2/workloadapi"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
)
type SpiffeObject struct {
Spec nfdv1alpha1.NodeFeatureSpec
Name string
Namespace string
Labels map[string]string
}
// WorkerSpiffeID is the SpiffeID of the worker
const WorkerSpiffeID = "spiffe://nfd.com/worker"
const WorkerSpiffeID = "spiffe://nfd.k8s-sigs.io/worker"
type SpiffeClient struct {
WorkloadApiClient workloadapi.Client
}
var hash_signature_cache = map[string][]byte{}
func NewSpiffeClient(socketPath string) (*SpiffeClient, error) {
spiffeClient := SpiffeClient{}
workloadApiClient, err := workloadapi.New(context.Background(), workloadapi.WithAddr(socketPath))
@ -44,33 +57,38 @@ func NewSpiffeClient(socketPath string) (*SpiffeClient, error) {
return &spiffeClient, nil
}
func SignData(data interface{}, privateKey crypto.Signer) ([]byte, error) {
func SignData(data SpiffeObject, privateKey crypto.Signer) ([]byte, error) {
stringifyData, err := json.Marshal(data)
if err != nil {
return []byte{}, err
}
dataHash := sha256.Sum256([]byte(stringifyData))
if signature, ok := hash_signature_cache[string(dataHash[:])]; ok {
return signature, nil
}
var signedData []byte
switch t := privateKey.(type) {
case *rsa.PrivateKey:
signedData, err := rsa.SignPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), crypto.SHA256, dataHash[:])
signedData, err = rsa.SignPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), crypto.SHA256, dataHash[:])
if err != nil {
return []byte{}, err
}
return signedData, nil
case *ecdsa.PrivateKey:
signedData, err := ecdsa.SignASN1(rand.Reader, privateKey.(*ecdsa.PrivateKey), dataHash[:])
signedData, err = ecdsa.SignASN1(rand.Reader, privateKey.(*ecdsa.PrivateKey), dataHash[:])
if err != nil {
return []byte{}, err
}
return signedData, nil
default:
return nil, fmt.Errorf("unknown private key type: %v", t)
}
hash_signature_cache[string(dataHash[:])] = signedData
return signedData, nil
}
func VerifyDataSignature(data interface{}, signedData string, privateKey crypto.Signer, publicKey crypto.PublicKey) (bool, error) {
func VerifyDataSignature(data SpiffeObject, signedData string, privateKey crypto.Signer, publicKey crypto.PublicKey) (bool, error) {
stringifyData, err := json.Marshal(data)
if err != nil {
return false, err
@ -83,10 +101,6 @@ func VerifyDataSignature(data interface{}, signedData string, privateKey crypto.
dataHash := sha256.Sum256([]byte(stringifyData))
if err != nil {
return false, err
}
switch t := privateKey.(type) {
case *rsa.PrivateKey:
err = rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey), crypto.SHA256, dataHash[:], decodedSignature)
@ -112,7 +126,7 @@ func (s *SpiffeClient) GetWorkerKeys() (crypto.Signer, crypto.PublicKey, error)
for _, svid := range svids {
if svid.ID.String() == WorkerSpiffeID {
return svid.PrivateKey, svid.PrivateKey.Public, nil
return svid.PrivateKey, svid.PrivateKey.Public(), nil
}
}

View file

@ -3,7 +3,9 @@ Copyright 2024 The Kubernetes 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.
@ -11,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
package spiffe
import (
"crypto"
@ -76,20 +78,31 @@ func TestVerify(t *testing.T) {
}
for _, tt := range tc {
signedData, err := SignData(spec, tt.privateKey)
spiffeObj := SpiffeObject{
Spec: spec,
Name: "test",
Namespace: "test",
Labels: map[string]string{
"random": "test",
},
}
signedData, err := SignData(spiffeObj, tt.privateKey)
assert.NoError(t, err)
isVerified, err := VerifyDataSignature(spec, b64.StdEncoding.EncodeToString(signedData), tt.privateKey, tt.publicKey)
isVerified, err := VerifyDataSignature(spiffeObj, b64.StdEncoding.EncodeToString(signedData), tt.privateKey, tt.publicKey)
assert.NoError(t, err)
assert.True(t, isVerified)
signedData = append(signedData, "random"...)
isVerified, err = VerifyDataSignature(spec, b64.StdEncoding.EncodeToString(signedData), tt.privateKey, tt.publicKey)
isVerified, err = VerifyDataSignature(spiffeObj, b64.StdEncoding.EncodeToString(signedData), tt.privateKey, tt.publicKey)
if tt.wantErr {
assert.Error(t, err)
} else {
assert.False(t, isVerified)
}
// invalidateCache
hash_signature_cache = map[string][]byte{}
}
}
@ -113,7 +126,56 @@ func TestSignData(t *testing.T) {
}
for _, tt := range tc {
_, err := SignData(spec, tt.privateKey)
spiffeObj := SpiffeObject{
Spec: spec,
Name: "test",
Namespace: "test",
Labels: map[string]string{
"random": "test",
},
}
_, err := SignData(spiffeObj, tt.privateKey)
assert.NoError(t, err)
// invalidate cache
hash_signature_cache = map[string][]byte{}
}
}
func TestSignCached(t *testing.T) {
rsaPrivateKey, _ := mockWorkerRSAPrivateKey()
ecdsaPrivateKey, _ := mockWorkerECDSAPrivateKey()
spec := mockNFRSpec()
tc := []struct {
name string
privateKey crypto.Signer
}{
{
name: "RSA Keys",
privateKey: rsaPrivateKey,
},
{
name: "ECDSA Keys",
privateKey: ecdsaPrivateKey,
},
}
for _, tt := range tc {
spiffeObj := SpiffeObject{
Spec: spec,
Name: "test",
Namespace: "test",
Labels: map[string]string{
"random": "test",
},
}
firstSignature, err := SignData(spiffeObj, tt.privateKey)
assert.NoError(t, err)
secondSignature, err := SignData(spiffeObj, tt.privateKey)
assert.NoError(t, err)
assert.Equal(t, string(firstSignature[:]), string(secondSignature[:]))
}
}