Skip to content

Commit c77c370

Browse files
committed
feat: add spiffe integration in master and worker
Signed-off-by: TessaIO <ahmedgrati1999@gmail.com>
1 parent 6a10142 commit c77c370

File tree

7 files changed

+421
-2
lines changed

7 files changed

+421
-2
lines changed

cmd/nfd-master/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ func main() {
7373
args.Overrides.ResyncPeriod = overrides.ResyncPeriod
7474
case "nfd-api-parallelism":
7575
args.Overrides.NfdApiParallelism = overrides.NfdApiParallelism
76+
case "enable-spiffe":
77+
args.Overrides.EnableSpiffe = overrides.EnableSpiffe
7678
}
7779
})
7880

@@ -140,6 +142,8 @@ func initFlags(flagset *flag.FlagSet) (*master.Args, *master.ConfigOverrideArgs)
140142
flagset.Var(overrides.ResyncPeriod, "resync-period", "Specify the NFD API controller resync period.")
141143
overrides.NfdApiParallelism = flagset.Int("nfd-api-parallelism", 10, "Defines the maximum number of goroutines responsible of updating nodes. "+
142144
"Can be used for the throttling mechanism.")
145+
overrides.EnableSpiffe = flagset.Bool("enable-spiffe", false,
146+
"Enables the Spiffe signature verification of created CRDs. This is still an EXPERIMENTAL feature.")
143147

144148
return args, overrides
145149
}

cmd/nfd-worker/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ func parseArgs(flags *flag.FlagSet, osArgs ...string) *worker.Args {
9393
args.Overrides.LabelSources = overrides.LabelSources
9494
case "no-owner-refs":
9595
args.Overrides.NoOwnerRefs = overrides.NoOwnerRefs
96+
case "enable-spiffe":
97+
args.Overrides.EnableSpiffe = overrides.EnableSpiffe
9698
}
9799
})
98100

@@ -131,6 +133,8 @@ func initFlags(flagset *flag.FlagSet) (*worker.Args, *worker.ConfigOverrideArgs)
131133
flagset.Var(overrides.LabelSources, "label-sources",
132134
"Comma separated list of label sources. Special value 'all' enables all sources. "+
133135
"Prefix the source name with '-' to disable it.")
136+
overrides.EnableSpiffe = flagset.Bool("enable-spiffe", false,
137+
"Enables the Spiffe signature verification of created CRDs. This is still an EXPERIMENTAL feature.")
134138

135139
return args, overrides
136140
}

go.sum

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,27 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/Z
123123
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
124124
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
125125
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
126+
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
127+
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
128+
github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg=
129+
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
130+
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
131+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
132+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
133+
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
134+
github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
135+
github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM=
136+
github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM=
137+
github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c=
138+
github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo=
139+
github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY=
140+
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
141+
github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
142+
github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI=
143+
github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw=
144+
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
145+
github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4=
146+
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
126147
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
127148
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
128149
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@@ -262,6 +283,7 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS
262283
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
263284
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
264285
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
286+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
265287
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
266288
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
267289
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

pkg/nfd-master/nfd-master.go

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ import (
5151
"sigs.k8s.io/yaml"
5252

5353
nfdclientset "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
54+
klogutils "sigs.k8s.io/node-feature-discovery/pkg/utils/klog"
55+
spiffe "sigs.k8s.io/node-feature-discovery/pkg/utils/spiffe"
56+
57+
taintutils "k8s.io/kubernetes/pkg/util/taints"
58+
"sigs.k8s.io/yaml"
59+
60+
"sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
5461
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
5562
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/nodefeaturerule"
5663
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
@@ -60,6 +67,9 @@ import (
6067
"sigs.k8s.io/node-feature-discovery/pkg/version"
6168
)
6269

70+
// SocketPath specifies Spiffe Socket Path
71+
const SocketPath = "unix:///run/spire/sockets/agent.sock"
72+
6373
// Labels are a Kubernetes representation of discovered features.
6474
type Labels map[string]string
6575

@@ -92,6 +102,7 @@ type NFDConfig struct {
92102
NfdApiParallelism int
93103
Klog klogutils.KlogConfigOpts
94104
Restrictions Restrictions
105+
EnableSpiffe bool
95106
}
96107

97108
// LeaderElectionConfig contains the configuration for leader election
@@ -110,6 +121,7 @@ type ConfigOverrideArgs struct {
110121
NoPublish *bool
111122
ResyncPeriod *utils.DurationVal
112123
NfdApiParallelism *int
124+
EnableSpiffe *bool
113125
}
114126

115127
// Args holds command line arguments
@@ -149,7 +161,8 @@ type nfdMaster struct {
149161
nfdClient nfdclientset.Interface
150162
updaterPool *updaterPool
151163
deniedNs
152-
config *NFDConfig
164+
config *NFDConfig
165+
spiffeClient *spiffe.SpiffeClient
153166
}
154167

155168
// NewNfdMaster creates a new NfdMaster server instance.
@@ -206,6 +219,12 @@ func NewNfdMaster(opts ...NfdMasterOption) (NfdMaster, error) {
206219

207220
nfd.updaterPool = newUpdaterPool(nfd)
208221

222+
spiffeClient, err := spiffe.NewSpiffeClient(SocketPath)
223+
if err != nil {
224+
return nfd, err
225+
}
226+
nfd.spiffeClient = spiffeClient
227+
209228
return nfd, nil
210229
}
211230

@@ -247,14 +266,15 @@ func newDefaultConfig() *NFDConfig {
247266
RetryPeriod: utils.DurationVal{Duration: time.Duration(2) * time.Second},
248267
RenewDeadline: utils.DurationVal{Duration: time.Duration(10) * time.Second},
249268
},
250-
Klog: make(map[string]string),
251269
Restrictions: Restrictions{
252270
DisableLabels: false,
253271
DisableExtendedResources: false,
254272
DisableAnnotations: false,
255273
AllowOverwrite: true,
256274
DenyNodeFeatureLabels: false,
257275
},
276+
Klog: make(map[string]string),
277+
EnableSpiffe: false,
258278
}
259279
}
260280

@@ -677,6 +697,55 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(cli k8sclient.Interface, node *corev1.No
677697
return fmt.Errorf("failed to merge NodeFeature objects for node %q: %w", node.Name, err)
678698
}
679699

700+
// Sort our objects
701+
sort.Slice(objs, func(i, j int) bool {
702+
// Objects in our nfd namespace gets into the beginning of the list
703+
if objs[i].Namespace == m.namespace && objs[j].Namespace != m.namespace {
704+
return true
705+
}
706+
if objs[i].Namespace != m.namespace && objs[j].Namespace == m.namespace {
707+
return false
708+
}
709+
// After the nfd namespace, sort objects by their name
710+
if objs[i].Name != objs[j].Name {
711+
return objs[i].Name < objs[j].Name
712+
}
713+
// Objects with the same name are sorted by their namespace
714+
return objs[i].Namespace < objs[j].Namespace
715+
})
716+
717+
// If spiffe is enabled, we should filter out the non verified NFD objects
718+
if m.config.EnableSpiffe {
719+
objs, err = m.getVerifiedNFDObjects(objs)
720+
if err != nil {
721+
return err
722+
}
723+
}
724+
725+
klog.V(1).InfoS("processing of node initiated by NodeFeature API", "nodeName", node.Name)
726+
727+
features := nfdv1alpha1.NewNodeFeatureSpec()
728+
729+
if len(objs) > 0 {
730+
// Merge in features
731+
//
732+
// NOTE: changing the rule api to support handle multiple objects instead
733+
// of merging would probably perform better with lot less data to copy.
734+
features = objs[0].Spec.DeepCopy()
735+
if m.config.AutoDefaultNs {
736+
features.Labels = addNsToMapKeys(features.Labels, nfdv1alpha1.FeatureLabelNs)
737+
}
738+
for _, o := range objs[1:] {
739+
s := o.Spec.DeepCopy()
740+
if m.config.AutoDefaultNs {
741+
s.Labels = addNsToMapKeys(s.Labels, nfdv1alpha1.FeatureLabelNs)
742+
}
743+
s.MergeInto(features)
744+
}
745+
746+
klog.V(4).InfoS("merged nodeFeatureSpecs", "newNodeFeatureSpec", utils.DelayedDumper(features))
747+
}
748+
680749
// Update node labels et al. This may also mean removing all NFD-owned
681750
// labels (et al.), for example in the case no NodeFeature objects are
682751
// present.
@@ -1187,6 +1256,9 @@ func (m *nfdMaster) configure(filepath string, overrides string) error {
11871256
if m.args.Overrides.NfdApiParallelism != nil {
11881257
c.NfdApiParallelism = *m.args.Overrides.NfdApiParallelism
11891258
}
1259+
if m.args.Overrides.EnableSpiffe != nil {
1260+
c.EnableSpiffe = *m.args.Overrides.EnableSpiffe
1261+
}
11901262

11911263
if c.NfdApiParallelism <= 0 {
11921264
return fmt.Errorf("the maximum number of concurrent labelers should be a non-zero positive number")
@@ -1387,3 +1459,27 @@ func patchNode(cli k8sclient.Interface, nodeName string, patches []utils.JsonPat
13871459
func patchNodeStatus(cli k8sclient.Interface, nodeName string, patches []utils.JsonPatch) error {
13881460
return patchNode(cli, nodeName, patches, "status")
13891461
}
1462+
1463+
func (m *nfdMaster) getVerifiedNFDObjects(objs []*v1alpha1.NodeFeature) ([]*v1alpha1.NodeFeature, error) {
1464+
verifiedObjects := []*v1alpha1.NodeFeature{}
1465+
1466+
workerPrivateKey, workerPublicKey, err := m.spiffeClient.GetWorkerKeys()
1467+
if err != nil {
1468+
return verifiedObjects, err
1469+
}
1470+
1471+
for _, obj := range objs {
1472+
isSignatureVerified, err := spiffe.VerifyDataSignature(obj.Spec, obj.Annotations["signature"], workerPrivateKey, workerPublicKey)
1473+
if err != nil {
1474+
return nil, fmt.Errorf("failed to verify NodeFeature signature: %w", err)
1475+
}
1476+
1477+
if isSignatureVerified {
1478+
klog.InfoS("NodeFeature verified", "NodeFeature name", obj.Name)
1479+
verifiedObjects = append(verifiedObjects, obj)
1480+
} else {
1481+
klog.InfoS("NodeFeature not verified, skipping...", "NodeFeature name", obj.Name)
1482+
}
1483+
}
1484+
return verifiedObjects, nil
1485+
}

pkg/nfd-worker/nfd-worker.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ limitations under the License.
1717
package nfdworker
1818

1919
import (
20+
"crypto/tls"
21+
"crypto/x509"
22+
b64 "encoding/base64"
2023
"encoding/json"
2124
"fmt"
2225
"net/http"
@@ -45,6 +48,7 @@ import (
4548
nfdclient "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
4649
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
4750
"sigs.k8s.io/node-feature-discovery/pkg/utils"
51+
spiffe "sigs.k8s.io/node-feature-discovery/pkg/utils/spiffe"
4852
"sigs.k8s.io/node-feature-discovery/pkg/version"
4953
"sigs.k8s.io/node-feature-discovery/source"
5054

@@ -62,6 +66,9 @@ import (
6266
_ "sigs.k8s.io/node-feature-discovery/source/usb"
6367
)
6468

69+
// SocketPath specifies Spiffe Socket Path
70+
const SocketPath = "unix:///run/spire/sockets/agent.sock"
71+
6572
// NfdWorker is the interface for nfd-worker daemon
6673
type NfdWorker interface {
6774
Run() error
@@ -83,6 +90,7 @@ type coreConfig struct {
8390
Sources *[]string
8491
LabelSources []string
8592
SleepInterval utils.DurationVal
93+
EnableSpiffe bool
8694
}
8795

8896
type sourcesConfig map[string]source.Config
@@ -109,6 +117,7 @@ type ConfigOverrideArgs struct {
109117
NoOwnerRefs *bool
110118
FeatureSources *utils.StringSliceVal
111119
LabelSources *utils.StringSliceVal
120+
EnableSpiffe *bool
112121
}
113122

114123
type nfdWorker struct {
@@ -122,6 +131,7 @@ type nfdWorker struct {
122131
featureSources []source.FeatureSource
123132
labelSources []source.LabelSource
124133
ownerReference []metav1.OwnerReference
134+
spiffeClient *spiffe.SpiffeClient
125135
}
126136

127137
// This ticker can represent infinite and normal intervals.
@@ -188,6 +198,12 @@ func NewNfdWorker(opts ...NfdWorkerOption) (NfdWorker, error) {
188198
nfd.k8sClient = cli
189199
}
190200

201+
spiffeClient, err := spiffe.NewSpiffeClient(SocketPath)
202+
if err != nil {
203+
return nfd, err
204+
}
205+
nfd.spiffeClient = spiffeClient
206+
191207
return nfd, nil
192208
}
193209

@@ -509,6 +525,9 @@ func (w *nfdWorker) configure(filepath string, overrides string) error {
509525
if w.args.Overrides.LabelSources != nil {
510526
c.Core.LabelSources = *w.args.Overrides.LabelSources
511527
}
528+
if w.args.Overrides.EnableSpiffe != nil {
529+
c.Core.EnableSpiffe = *w.args.Overrides.EnableSpiffe
530+
}
512531

513532
c.Core.sanitize()
514533

@@ -643,6 +662,14 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
643662
}
644663
klog.InfoS("creating NodeFeature object", "nodefeature", klog.KObj(nfr))
645664

665+
// If Spiffe is enabled, we add the signature to the annotations section
666+
if m.config.Core.EnableSpiffe {
667+
err = m.signNodeFeatureCR(nfr)
668+
if err != nil {
669+
return err
670+
}
671+
}
672+
646673
nfrCreated, err := cli.NfdV1alpha1().NodeFeatures(namespace).Create(context.TODO(), nfr, metav1.CreateOptions{})
647674
if err != nil {
648675
return fmt.Errorf("failed to create NodeFeature object %q: %w", nfr.Name, err)
@@ -661,6 +688,13 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error {
661688
Labels: labels,
662689
}
663690

691+
if m.config.Core.EnableSpiffe {
692+
err = m.signNodeFeatureCR(nfrUpdated)
693+
if err != nil {
694+
return err
695+
}
696+
}
697+
664698
if !apiequality.Semantic.DeepEqual(nfr, nfrUpdated) {
665699
klog.InfoS("updating NodeFeature object", "nodefeature", klog.KObj(nfr))
666700
nfrUpdated, err = cli.NfdV1alpha1().NodeFeatures(namespace).Update(context.TODO(), nfrUpdated, metav1.UpdateOptions{})
@@ -718,3 +752,23 @@ func (c *sourcesConfig) UnmarshalJSON(data []byte) error {
718752

719753
return nil
720754
}
755+
756+
// signNodeFeatureCR add the signature to the annotations of a given NodeFeature CR
757+
func (m *nfdWorker) signNodeFeatureCR(nfr *nfdv1alpha1.NodeFeature) error {
758+
workerPrivateKey, _, err := m.spiffeClient.GetWorkerKeys()
759+
760+
if err != nil {
761+
return fmt.Errorf("error while getting worker keys: %w", err)
762+
}
763+
764+
signature, err := spiffe.SignData(nfr.Spec, workerPrivateKey)
765+
766+
if err != nil {
767+
return fmt.Errorf("failed to sign CRD data using Spiffe: %w", err)
768+
}
769+
770+
encodedSignature := b64.StdEncoding.EncodeToString(signature)
771+
nfr.ObjectMeta.Annotations["signature"] = encodedSignature
772+
773+
return nil
774+
}

0 commit comments

Comments
 (0)