diff --git a/deployment/components/master-config/nfd-master.conf.example b/deployment/components/master-config/nfd-master.conf.example index 8aa0cb29ce..114531f421 100644 --- a/deployment/components/master-config/nfd-master.conf.example +++ b/deployment/components/master-config/nfd-master.conf.example @@ -6,6 +6,7 @@ # enableTaints: false # labelWhiteList: "foo" # resyncPeriod: "2h" +# enableSpiffe: true # klog: # addDirHeader: false # alsologtostderr: false diff --git a/deployment/components/worker-config/nfd-worker.conf.example b/deployment/components/worker-config/nfd-worker.conf.example index 9a3bfd85b7..ef1e8cdcfa 100644 --- a/deployment/components/worker-config/nfd-worker.conf.example +++ b/deployment/components/worker-config/nfd-worker.conf.example @@ -2,6 +2,7 @@ # labelWhiteList: # noPublish: false # sleepInterval: 60s +# enableSpiffe: true # featureSources: [all] # labelSources: [all] # klog: diff --git a/deployment/helm/node-feature-discovery/templates/master.yaml b/deployment/helm/node-feature-discovery/templates/master.yaml index bb1f24938b..caba5d3f9b 100644 --- a/deployment/helm/node-feature-discovery/templates/master.yaml +++ b/deployment/helm/node-feature-discovery/templates/master.yaml @@ -115,12 +115,20 @@ spec: - "-feature-gates={{ $key }}={{ $value }}" {{- end }} - "-metrics={{ .Values.master.metricsPort | default "8081" }}" + {{- if .Values.spiffe.enable }} + - "-enable-spiffe" + {{- end }} volumeMounts: {{- if .Values.tls.enable }} - name: nfd-master-cert mountPath: "/etc/kubernetes/node-feature-discovery/certs" readOnly: true {{- end }} + {{- if .Values.spiffe.enable }} + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: true + {{- end }} - name: nfd-master-conf mountPath: "/etc/kubernetes/node-feature-discovery" readOnly: true @@ -130,6 +138,12 @@ spec: secret: secretName: nfd-master-cert {{- end }} + {{- if .Values.spiffe.enable }} + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: Directory + {{- end }} - name: nfd-master-conf configMap: name: {{ include "node-feature-discovery.fullname" . }}-master-conf diff --git a/deployment/helm/node-feature-discovery/templates/spire-agent-cluster-role.yaml b/deployment/helm/node-feature-discovery/templates/spire-agent-cluster-role.yaml new file mode 100644 index 0000000000..9115dac5c6 --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-agent-cluster-role.yaml @@ -0,0 +1,26 @@ +{{- if .Values.spiffe.enable }} +# Required cluster role to allow spire-agent to query k8s API server +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-agent-cluster-role +rules: +- apiGroups: [""] + resources: ["pods","nodes","nodes/proxy"] + verbs: ["get"] + +--- +# Binds above cluster role to spire-agent service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-agent-cluster-role-binding +subjects: +- kind: ServiceAccount + name: spire-agent + namespace: {{ include "node-feature-discovery.namespace" . }} +roleRef: + kind: ClusterRole + name: spire-agent-cluster-role + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-agent-configmap.yaml b/deployment/helm/node-feature-discovery/templates/spire-agent-configmap.yaml new file mode 100644 index 0000000000..bc7e634563 --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-agent-configmap.yaml @@ -0,0 +1,45 @@ +{{- if .Values.spiffe.enable }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-agent +data: + agent.conf: | + agent { + data_dir = "/run/spire" + log_level = "DEBUG" + server_address = "spire-server" + server_port = "8081" + socket_path = "/run/spire/sockets/agent.sock" + trust_bundle_path = "/run/spire/bundle/bundle.crt" + trust_domain = "nfd.com" + } + plugins { + NodeAttestor "k8s_sat" { + plugin_data { + cluster = "nfd" + } + } + KeyManager "memory" { + plugin_data { + } + } + WorkloadAttestor "k8s" { + plugin_data { + skip_kubelet_verification = true + node_name_env = "MY_NODE_NAME" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } + } + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-agent-daemonset.yaml b/deployment/helm/node-feature-discovery/templates/spire-agent-daemonset.yaml new file mode 100644 index 0000000000..37ff0994a2 --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-agent-daemonset.yaml @@ -0,0 +1,57 @@ +{{- if .Values.spiffe.enable }} +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: spire-agent + labels: + app: spire-agent +spec: + selector: + matchLabels: + app: spire-agent + template: + metadata: + labels: + app: spire-agent + spec: + hostPID: true + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: spire-agent + initContainers: + - name: init + # This is a small image with wait-for-it, choose whatever image + # you prefer that waits for a service to be up. This image is built + # from https://github.com/lqhl/wait-for-it + image: cgr.dev/chainguard/wait-for-it + args: ["-t", "30", "spire-server:8081"] + containers: + - name: spire-agent + image: ghcr.io/spiffe/spire-agent:1.5.1 + args: ["-config", "/run/spire/config/agent.conf"] + env: + - name: MY_NODE_NAME + valueFrom: + fieldRef: + fieldPath: status.podIP + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-bundle + mountPath: /run/spire/bundle + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: false + volumes: + - name: spire-config + configMap: + name: spire-agent + - name: spire-bundle + configMap: + name: spire-bundle + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: DirectoryOrCreate +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-agent-service-account.yaml b/deployment/helm/node-feature-discovery/templates/spire-agent-service-account.yaml new file mode 100644 index 0000000000..9dc8afa57c --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-agent-service-account.yaml @@ -0,0 +1,6 @@ +{{- if .Values.spiffe.enable }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spire-agent +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-bundle-configmap.yaml b/deployment/helm/node-feature-discovery/templates/spire-bundle-configmap.yaml new file mode 100644 index 0000000000..3b3179b909 --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-bundle-configmap.yaml @@ -0,0 +1,6 @@ +{{- if .Values.spiffe.enable }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-bundle +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-server-cluster-role.yaml b/deployment/helm/node-feature-discovery/templates/spire-server-cluster-role.yaml new file mode 100644 index 0000000000..59163ffc9d --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-server-cluster-role.yaml @@ -0,0 +1,50 @@ +{{- if .Values.spiffe.enable }} +# Role (namespace scoped) to be able to push certificate bundles to a configmap +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-configmap-role +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["patch", "get", "list"] +--- +# Binds above role to spire-server service account +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-configmap-role-binding + namespace: {{ include "node-feature-discovery.namespace" . }} +subjects: +- kind: ServiceAccount + name: spire-server + namespace: {{ include "node-feature-discovery.namespace" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: spire-server-configmap-role +--- +# ClusterRole to allow spire-server node attestor to query Token Review API +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-trust-role +rules: +- apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: ["create"] +--- +# Binds above cluster role to spire-server service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-trust-role-binding +subjects: +- kind: ServiceAccount + name: spire-server + namespace: {{ include "node-feature-discovery.namespace" . }} +roleRef: + kind: ClusterRole + name: spire-server-trust-role + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-server-configmap.yaml b/deployment/helm/node-feature-discovery/templates/spire-server-configmap.yaml new file mode 100644 index 0000000000..09720d01fa --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-server-configmap.yaml @@ -0,0 +1,58 @@ +{{- if .Values.spiffe.enable }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-server +data: + server.conf: | + server { + bind_address = "0.0.0.0" + bind_port = "8081" + socket_path = "/tmp/spire-server/private/api.sock" + trust_domain = "nfd.com" + data_dir = "/run/spire/data" + log_level = "DEBUG" + #AWS requires the use of RSA. EC cryptography is not supported + ca_key_type = "rsa-2048" + ca_subject = { + country = ["US"], + organization = ["SPIFFE"], + common_name = "nfd.com", + } + } + plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/run/spire/data/datastore.sqlite3" + } + } + NodeAttestor "k8s_sat" { + plugin_data { + clusters = { + "nfd" = { + use_token_review_api_validation = true + service_account_allow_list = ["{{ include "node-feature-discovery.namespace" . }}:spire-agent"] + } + } + } + } + KeyManager "disk" { + plugin_data { + keys_path = "/run/spire/data/keys.json" + } + } + Notifier "k8sbundle" { + plugin_data { + namespace = "{{ include "node-feature-discovery.namespace" . }}" + } + } + } + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-server-service-account.yaml b/deployment/helm/node-feature-discovery/templates/spire-server-service-account.yaml new file mode 100644 index 0000000000..f7924166e0 --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-server-service-account.yaml @@ -0,0 +1,6 @@ +{{- if .Values.spiffe.enable }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spire-server +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-server-service.yaml b/deployment/helm/node-feature-discovery/templates/spire-server-service.yaml new file mode 100644 index 0000000000..fbe64cb304 --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-server-service.yaml @@ -0,0 +1,15 @@ +{{- if .Values.spiffe.enable }} +apiVersion: v1 +kind: Service +metadata: + name: spire-server +spec: + type: NodePort + ports: + - name: grpc + port: 8081 + targetPort: 8081 + protocol: TCP + selector: + app: spire-server +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/spire-server-statefulset.yaml b/deployment/helm/node-feature-discovery/templates/spire-server-statefulset.yaml new file mode 100644 index 0000000000..7e9200d08a --- /dev/null +++ b/deployment/helm/node-feature-discovery/templates/spire-server-statefulset.yaml @@ -0,0 +1,62 @@ +{{- if .Values.spiffe.enable }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: spire-server + labels: + app: spire-server +spec: + replicas: 1 + selector: + matchLabels: + app: spire-server + serviceName: spire-server + template: + metadata: + labels: + app: spire-server + spec: + serviceAccountName: spire-server + containers: + - name: spire-server + image: ghcr.io/spiffe/spire-server:1.5.1 + args: + - -config + - /run/spire/config/server.conf + ports: + - containerPort: 8081 + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-data + mountPath: /run/spire/data + readOnly: false + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: spire-config + configMap: + name: spire-server + volumeClaimTemplates: + - metadata: + name: spire-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +{{- end }} diff --git a/deployment/helm/node-feature-discovery/templates/worker.yaml b/deployment/helm/node-feature-discovery/templates/worker.yaml index 364259cacc..be74d70541 100644 --- a/deployment/helm/node-feature-discovery/templates/worker.yaml +++ b/deployment/helm/node-feature-discovery/templates/worker.yaml @@ -85,10 +85,18 @@ spec: - "-feature-gates={{ $key }}={{ $value }}" {{- end }} - "-metrics={{ .Values.worker.metricsPort | default "8081"}}" + {{- if .Values.spiffe.enable }} + - "-enable-spiffe" + {{- end }} ports: - name: metrics containerPort: {{ .Values.worker.metricsPort | default "8081"}} volumeMounts: + {{- if .Values.spiffe.enable }} + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: true + {{- end }} - name: host-boot mountPath: "/host-boot" readOnly: true @@ -127,6 +135,12 @@ spec: readOnly: true {{- end }} volumes: + {{- if .Values.spiffe.enable }} + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: Directory + {{- end }} - name: host-boot hostPath: path: "/boot" diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml index 22ac96541e..e491025737 100644 --- a/deployment/helm/node-feature-discovery/values.yaml +++ b/deployment/helm/node-feature-discovery/values.yaml @@ -15,6 +15,9 @@ featureGates: priorityClassName: "" +spiffe: + enable: true + master: enable: true config: ### @@ -26,6 +29,7 @@ master: # enableTaints: false # labelWhiteList: "foo" # resyncPeriod: "2h" + # enableSpiffe: true # klog: # addDirHeader: false # alsologtostderr: false @@ -139,6 +143,7 @@ worker: # labelWhiteList: # noPublish: false # sleepInterval: 60s + # enableSpiffe: true # featureSources: [all] # labelSources: [all] # klog: diff --git a/deployment/overlays/spiffe/kustomization.yaml b/deployment/overlays/spiffe/kustomization.yaml new file mode 100644 index 0000000000..035abe0ee7 --- /dev/null +++ b/deployment/overlays/spiffe/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: node-feature-discovery + +resources: +- spire-agent-cluster-role.yaml +- spire-agent-configmap.yaml +- spire-agent-daemonset.yaml +- spire-agent-service-account.yaml +- spire-bundle-configmap.yaml +- spire-server-cluster-role.yaml +- spire-server-configmap.yaml +- spire-server-service-account.yaml +- spire-server-service.yaml +- spire-server-statefulset.yaml diff --git a/deployment/overlays/spiffe/namespace.yaml b/deployment/overlays/spiffe/namespace.yaml new file mode 100644 index 0000000000..8e54de2e34 --- /dev/null +++ b/deployment/overlays/spiffe/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: node-feature-discovery diff --git a/deployment/overlays/spiffe/spire-agent-cluster-role.yaml b/deployment/overlays/spiffe/spire-agent-cluster-role.yaml new file mode 100644 index 0000000000..f72815997a --- /dev/null +++ b/deployment/overlays/spiffe/spire-agent-cluster-role.yaml @@ -0,0 +1,24 @@ +# Required cluster role to allow spire-agent to query k8s API server +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-agent-cluster-role +rules: +- apiGroups: [""] + resources: ["pods","nodes","nodes/proxy"] + verbs: ["get"] + +--- +# Binds above cluster role to spire-agent service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-agent-cluster-role-binding +subjects: +- kind: ServiceAccount + name: spire-agent + namespace: {{ include "node-feature-discovery.namespace" . }} +roleRef: + kind: ClusterRole + name: spire-agent-cluster-role + apiGroup: rbac.authorization.k8s.io diff --git a/deployment/overlays/spiffe/spire-agent-configmap.yaml b/deployment/overlays/spiffe/spire-agent-configmap.yaml new file mode 100644 index 0000000000..e5b9e53f0d --- /dev/null +++ b/deployment/overlays/spiffe/spire-agent-configmap.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-agent +data: + agent.conf: | + agent { + data_dir = "/run/spire" + log_level = "DEBUG" + server_address = "spire-server" + server_port = "8081" + socket_path = "/run/spire/sockets/agent.sock" + trust_bundle_path = "/run/spire/bundle/bundle.crt" + trust_domain = "nfd.com" + } + plugins { + NodeAttestor "k8s_sat" { + plugin_data { + cluster = "nfd" + } + } + KeyManager "memory" { + plugin_data { + } + } + WorkloadAttestor "k8s" { + plugin_data { + skip_kubelet_verification = true + node_name_env = "MY_NODE_NAME" + } + } + WorkloadAttestor "unix" { + plugin_data { + } + } + } + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } diff --git a/deployment/overlays/spiffe/spire-agent-daemonset.yaml b/deployment/overlays/spiffe/spire-agent-daemonset.yaml new file mode 100644 index 0000000000..831cd8a86d --- /dev/null +++ b/deployment/overlays/spiffe/spire-agent-daemonset.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: spire-agent + labels: + app: spire-agent +spec: + selector: + matchLabels: + app: spire-agent + template: + metadata: + labels: + app: spire-agent + spec: + hostPID: true + hostNetwork: true + dnsPolicy: ClusterFirstWithHostNet + serviceAccountName: spire-agent + initContainers: + - name: init + # This is a small image with wait-for-it, choose whatever image + # you prefer that waits for a service to be up. This image is built + # from https://github.com/lqhl/wait-for-it + image: cgr.dev/chainguard/wait-for-it + args: ["-t", "30", "spire-server:8081"] + containers: + - name: spire-agent + image: ghcr.io/spiffe/spire-agent:1.5.1 + args: ["-config", "/run/spire/config/agent.conf"] + env: + - name: MY_NODE_NAME + valueFrom: + fieldRef: + fieldPath: status.podIP + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-bundle + mountPath: /run/spire/bundle + - name: spire-agent-socket + mountPath: /run/spire/sockets + readOnly: false + volumes: + - name: spire-config + configMap: + name: spire-agent + - name: spire-bundle + configMap: + name: spire-bundle + - name: spire-agent-socket + hostPath: + path: /run/spire/sockets + type: DirectoryOrCreate diff --git a/deployment/overlays/spiffe/spire-agent-service-account.yaml b/deployment/overlays/spiffe/spire-agent-service-account.yaml new file mode 100644 index 0000000000..36a0e4dd00 --- /dev/null +++ b/deployment/overlays/spiffe/spire-agent-service-account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spire-agent diff --git a/deployment/overlays/spiffe/spire-bundle-configmap.yaml b/deployment/overlays/spiffe/spire-bundle-configmap.yaml new file mode 100644 index 0000000000..7ad63a5a89 --- /dev/null +++ b/deployment/overlays/spiffe/spire-bundle-configmap.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-bundle diff --git a/deployment/overlays/spiffe/spire-server-cluster-role.yaml b/deployment/overlays/spiffe/spire-server-cluster-role.yaml new file mode 100644 index 0000000000..100b1169ed --- /dev/null +++ b/deployment/overlays/spiffe/spire-server-cluster-role.yaml @@ -0,0 +1,48 @@ +# Role (namespace scoped) to be able to push certificate bundles to a configmap +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-configmap-role +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["patch", "get", "list"] +--- +# Binds above role to spire-server service account +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-configmap-role-binding + namespace: {{ include "node-feature-discovery.namespace" . }} +subjects: +- kind: ServiceAccount + name: spire-server + namespace: {{ include "node-feature-discovery.namespace" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: spire-server-configmap-role +--- +# ClusterRole to allow spire-server node attestor to query Token Review API +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-trust-role +rules: +- apiGroups: ["authentication.k8s.io"] + resources: ["tokenreviews"] + verbs: ["create"] +--- +# Binds above cluster role to spire-server service account +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: spire-server-trust-role-binding +subjects: +- kind: ServiceAccount + name: spire-server + namespace: {{ include "node-feature-discovery.namespace" . }} +roleRef: + kind: ClusterRole + name: spire-server-trust-role + apiGroup: rbac.authorization.k8s.io diff --git a/deployment/overlays/spiffe/spire-server-configmap.yaml b/deployment/overlays/spiffe/spire-server-configmap.yaml new file mode 100644 index 0000000000..ff5c6d77f2 --- /dev/null +++ b/deployment/overlays/spiffe/spire-server-configmap.yaml @@ -0,0 +1,56 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: spire-server +data: + server.conf: | + server { + bind_address = "0.0.0.0" + bind_port = "8081" + socket_path = "/tmp/spire-server/private/api.sock" + trust_domain = "nfd.com" + data_dir = "/run/spire/data" + log_level = "DEBUG" + #AWS requires the use of RSA. EC cryptography is not supported + ca_key_type = "rsa-2048" + ca_subject = { + country = ["US"], + organization = ["SPIFFE"], + common_name = "nfd.com", + } + } + plugins { + DataStore "sql" { + plugin_data { + database_type = "sqlite3" + connection_string = "/run/spire/data/datastore.sqlite3" + } + } + NodeAttestor "k8s_sat" { + plugin_data { + clusters = { + "nfd" = { + use_token_review_api_validation = true + service_account_allow_list = ["{{ include "node-feature-discovery.namespace" . }}:spire-agent"] + } + } + } + } + KeyManager "disk" { + plugin_data { + keys_path = "/run/spire/data/keys.json" + } + } + Notifier "k8sbundle" { + plugin_data { + namespace = "{{ include "node-feature-discovery.namespace" . }}" + } + } + } + health_checks { + listener_enabled = true + bind_address = "0.0.0.0" + bind_port = "8080" + live_path = "/live" + ready_path = "/ready" + } diff --git a/deployment/overlays/spiffe/spire-server-service-account.yaml b/deployment/overlays/spiffe/spire-server-service-account.yaml new file mode 100644 index 0000000000..acf9944260 --- /dev/null +++ b/deployment/overlays/spiffe/spire-server-service-account.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: spire-server diff --git a/deployment/overlays/spiffe/spire-server-service.yaml b/deployment/overlays/spiffe/spire-server-service.yaml new file mode 100644 index 0000000000..cf9c1f067e --- /dev/null +++ b/deployment/overlays/spiffe/spire-server-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: spire-server +spec: + type: NodePort + ports: + - name: grpc + port: 8081 + targetPort: 8081 + protocol: TCP + selector: + app: spire-server diff --git a/deployment/overlays/spiffe/spire-server-statefulset.yaml b/deployment/overlays/spiffe/spire-server-statefulset.yaml new file mode 100644 index 0000000000..e6fd227214 --- /dev/null +++ b/deployment/overlays/spiffe/spire-server-statefulset.yaml @@ -0,0 +1,60 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: spire-server + labels: + app: spire-server +spec: + replicas: 1 + selector: + matchLabels: + app: spire-server + serviceName: spire-server + template: + metadata: + labels: + app: spire-server + spec: + serviceAccountName: spire-server + containers: + - name: spire-server + image: ghcr.io/spiffe/spire-server:1.5.1 + args: + - -config + - /run/spire/config/server.conf + ports: + - containerPort: 8081 + volumeMounts: + - name: spire-config + mountPath: /run/spire/config + readOnly: true + - name: spire-data + mountPath: /run/spire/data + readOnly: false + livenessProbe: + httpGet: + path: /live + port: 8080 + failureThreshold: 2 + initialDelaySeconds: 15 + periodSeconds: 60 + timeoutSeconds: 3 + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: spire-config + configMap: + name: spire-server + volumeClaimTemplates: + - metadata: + name: spire-data + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go index c66b0a3284..a5cbe449e9 100644 --- a/pkg/nfd-master/nfd-master.go +++ b/pkg/nfd-master/nfd-master.go @@ -1478,7 +1478,13 @@ 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) } diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-worker/nfd-worker.go index fb225d56c4..2765dbf49e 100644 --- a/pkg/nfd-worker/nfd-worker.go +++ b/pkg/nfd-worker/nfd-worker.go @@ -864,7 +864,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) diff --git a/pkg/utils/spiffe/spiffe.go b/pkg/utils/spiffe/spiffe.go index 542f4bde17..06a1755987 100644 --- a/pkg/utils/spiffe/spiffe.go +++ b/pkg/utils/spiffe/spiffe.go @@ -25,8 +25,16 @@ 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" @@ -44,7 +52,7 @@ 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 @@ -70,7 +78,7 @@ func SignData(data interface{}, privateKey crypto.Signer) ([]byte, error) { } } -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 diff --git a/pkg/utils/spiffe/spiffe_test.go b/pkg/utils/spiffe/spiffe_test.go index 8fc9f17e2d..10880e2263 100644 --- a/pkg/utils/spiffe/spiffe_test.go +++ b/pkg/utils/spiffe/spiffe_test.go @@ -76,15 +76,23 @@ 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 { @@ -113,7 +121,15 @@ 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) } }