Skip to content

Commit 7635aee

Browse files
committed
Optimize how we check runtime KubeletConfig
we propose to store the runtime KubeletConfig in a ConfigMap per node when a node scan is launched. Then, we mount the ConfigMap to the scanner pod to scan for it.
1 parent b2b9212 commit 7635aee

22 files changed

+528
-323
lines changed

CHANGELOG.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ Versioning](https://semver.org/spec/v2.0.0.html).
1313

1414
### Fixes
1515

16-
-
16+
- Optimize how we check the KubeletConfig rule, we now store the runtime KubeletConfig
17+
in a ConfigMap per node when a node scan is launched. Then, we mount the ConfigMap to
18+
the scanner pod to scan for it. Hold on to applying remediation until all scans are
19+
done in the suite.
20+
This fixes issues when comparing the KubeletConfig for each node.
21+
This also fixes "/api/v1/nodes/NODE_NAME/proxy/configz" warning message in the log.
22+
[OCPBUGS-11037](https://issues.redhat.com/browse/OCPBUGS-11037)
1723

1824
### Internal Changes
1925

@@ -25,7 +31,9 @@ Versioning](https://semver.org/spec/v2.0.0.html).
2531

2632
### Removals
2733

28-
-
34+
- We have reverted commit 9cbf874, which is a fix for OCPBUGS-3864, the fix
35+
is not needed anymore since the issue is fixed when we switched back to
36+
the old way remediate the KubeletConfig.
2937

3038
### Security
3139

bundle/manifests/compliance-operator.clusterserviceversion.yaml

+18-1
Original file line numberDiff line numberDiff line change
@@ -1093,11 +1093,19 @@ spec:
10931093
- apiGroups:
10941094
- ""
10951095
resources:
1096-
- nodes
10971096
- namespaces
10981097
verbs:
10991098
- list
11001099
- watch
1100+
- apiGroups:
1101+
- ""
1102+
resources:
1103+
- nodes
1104+
- nodes/proxy
1105+
verbs:
1106+
- list
1107+
- watch
1108+
- get
11011109
- apiGroups:
11021110
- machineconfiguration.openshift.io
11031111
resources:
@@ -1347,6 +1355,15 @@ spec:
13471355
- update
13481356
- delete
13491357
- deletecollection
1358+
- apiGroups:
1359+
- ""
1360+
resources:
1361+
- nodes
1362+
- nodes/proxy
1363+
verbs:
1364+
- get
1365+
- list
1366+
- watch
13501367
- apiGroups:
13511368
- ""
13521369
resources:

cmd/manager/operator.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ func RunOperator(cmd *cobra.Command, args []string) {
289289
}
290290

291291
// Setup all Controllers
292-
if err := controller.AddToManager(mgr, met, si); err != nil {
292+
if err := controller.AddToManager(mgr, met, si, kubeClient); err != nil {
293293
setupLog.Error(err, "")
294294
os.Exit(1)
295295
}

config/rbac/operator_cluster_role.yaml

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,19 @@ rules:
66
- apiGroups:
77
- ""
88
resources:
9-
- nodes # We need to list the nodes to be able to selectively scan
109
- namespaces # We need this to get the range
1110
verbs:
1211
- list
1312
- watch
13+
- apiGroups:
14+
- ""
15+
resources:
16+
- nodes # We need to list the nodes to be able to selectively scan
17+
- nodes/proxy # We need to be able to get runtime kubeletconfig from the nodes
18+
verbs:
19+
- list
20+
- watch
21+
- get
1422
- apiGroups:
1523
- machineconfiguration.openshift.io
1624
resources:

config/rbac/operator_role.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ rules:
3030
- update # The Suite controller annotates configMaps
3131
- delete # Needed for result cleanup on re-scanning
3232
- deletecollection # Needed for result cleanup on re-scanning
33+
- apiGroups:
34+
- ""
35+
resources:
36+
- nodes
37+
- nodes/proxy
38+
verbs:
39+
- get
40+
- list
41+
- watch
3342
- apiGroups:
3443
- ""
3544
resources:

main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ package main
1818

1919
import (
2020
"fmt"
21-
"github.com/ComplianceAsCode/compliance-operator/cmd/manager"
2221
"os"
2322

23+
"github.com/ComplianceAsCode/compliance-operator/cmd/manager"
24+
2425
"github.com/spf13/cobra"
2526
)
2627

pkg/apis/compliance/v1alpha1/compliancescan_types.go

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ const ComplianceScanLabel = "compliance.openshift.io/scan-name"
2929
// ScriptLabel defines that the object is a script for a scan object
3030
const ScriptLabel = "complianceoperator.openshift.io/scan-script"
3131

32+
// KubeletConfigLabel defines that the object is a fetched KubeletConfig for a scan object
33+
const KubeletConfigLabel = "complianceoperator.openshift.io/scan-kubeletconfig"
34+
3235
// ResultLabel defines that the object is a result of a scan
3336
const ResultLabel = "complianceoperator.openshift.io/scan-result"
3437

pkg/apis/compliance/v1alpha1/scansettingbinding_types.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ type ScanSettingBinding struct {
2020
metav1.TypeMeta `json:",inline"`
2121
metav1.ObjectMeta `json:"metadata,omitempty"`
2222

23-
Spec ScanSettingBindingSpec `json:"spec,omitempty"`
24-
Profiles []NamedObjectReference `json:"profiles,omitempty"`
25-
// +kubebuilder:default={"name":"default","kind": "ScanSetting", "apiGroup": "compliance.openshift.io/v1alpha1"}
26-
SettingsRef *NamedObjectReference `json:"settingsRef,omitempty"`
23+
Spec ScanSettingBindingSpec `json:"spec,omitempty"`
24+
Profiles []NamedObjectReference `json:"profiles,omitempty"`
25+
// +kubebuilder:default={"name":"default","kind": "ScanSetting", "apiGroup": "compliance.openshift.io/v1alpha1"}
26+
SettingsRef *NamedObjectReference `json:"settingsRef,omitempty"`
2727
// +optional
2828
Status ScanSettingBindingStatus `json:"status,omitempty"`
2929
}

pkg/controller/complianceremediation/complianceremediation_controller.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2222
"k8s.io/apimachinery/pkg/runtime"
2323
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/client-go/kubernetes"
2425
"k8s.io/client-go/tools/record"
2526
ctrl "sigs.k8s.io/controller-runtime"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -47,7 +48,7 @@ func (r *ReconcileComplianceRemediation) SetupWithManager(mgr ctrl.Manager) erro
4748

4849
// Add creates a new ComplianceRemediation Controller and adds it to the Manager. The Manager will set fields on the Controller
4950
// and Start it when the Manager is Started.
50-
func Add(mgr manager.Manager, met *metrics.Metrics, _ utils.CtlplaneSchedulingInfo) error {
51+
func Add(mgr manager.Manager, met *metrics.Metrics, _ utils.CtlplaneSchedulingInfo, _ *kubernetes.Clientset) error {
5152
return add(mgr, newReconciler(mgr, met))
5253
}
5354

pkg/controller/compliancescan/compliancescan_controller.go

+132-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
goerrors "errors"
66
"fmt"
7+
"io/ioutil"
78
"math"
89
"strings"
910
"time"
@@ -17,8 +18,10 @@ import (
1718
"k8s.io/apimachinery/pkg/api/errors"
1819
"k8s.io/apimachinery/pkg/api/resource"
1920
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/labels"
2022
"k8s.io/apimachinery/pkg/runtime"
2123
"k8s.io/apimachinery/pkg/types"
24+
"k8s.io/client-go/kubernetes"
2225
"k8s.io/client-go/tools/record"
2326
ctrl "sigs.k8s.io/controller-runtime"
2427
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -59,14 +62,15 @@ func (r *ReconcileComplianceScan) SetupWithManager(mgr ctrl.Manager) error {
5962

6063
// Add creates a new ComplianceScan Controller and adds it to the Manager. The Manager will set fields on the Controller
6164
// and Start it when the Manager is Started.
62-
func Add(mgr manager.Manager, met *metrics.Metrics, si utils.CtlplaneSchedulingInfo) error {
63-
return add(mgr, newReconciler(mgr, met, si))
65+
func Add(mgr manager.Manager, met *metrics.Metrics, si utils.CtlplaneSchedulingInfo, kubeClient *kubernetes.Clientset) error {
66+
return add(mgr, newReconciler(mgr, met, si, kubeClient))
6467
}
6568

6669
// newReconciler returns a new reconcile.Reconciler
67-
func newReconciler(mgr manager.Manager, met *metrics.Metrics, si utils.CtlplaneSchedulingInfo) reconcile.Reconciler {
70+
func newReconciler(mgr manager.Manager, met *metrics.Metrics, si utils.CtlplaneSchedulingInfo, kubeClient *kubernetes.Clientset) reconcile.Reconciler {
6871
return &ReconcileComplianceScan{
6972
Client: mgr.GetClient(),
73+
ClientSet: kubeClient,
7074
Scheme: mgr.GetScheme(),
7175
Recorder: mgr.GetEventRecorderFor("scanctrl"),
7276
Metrics: met,
@@ -89,10 +93,11 @@ var _ reconcile.Reconciler = &ReconcileComplianceScan{}
8993
type ReconcileComplianceScan struct {
9094
// This Client, initialized using mgr.Client() above, is a split Client
9195
// that reads objects from the cache and writes to the apiserver
92-
Client client.Client
93-
Scheme *runtime.Scheme
94-
Recorder record.EventRecorder
95-
Metrics *metrics.Metrics
96+
Client client.Client
97+
ClientSet *kubernetes.Clientset
98+
Scheme *runtime.Scheme
99+
Recorder record.EventRecorder
100+
Metrics *metrics.Metrics
96101
// helps us schedule platform scans on the nodes labeled for the
97102
// compliance operator's control plane
98103
schedulingInfo utils.CtlplaneSchedulingInfo
@@ -104,6 +109,7 @@ type ReconcileComplianceScan struct {
104109
//+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,persistentvolumes,verbs=watch,create,get,list,delete
105110
//+kubebuilder:rbac:groups="",resources=pods,configmaps,events,verbs=create,get,list,watch,patch,update,delete,deletecollection
106111
//+kubebuilder:rbac:groups="",resources=secrets,verbs=create,get,list,update,watch,delete
112+
//+kubebuilder:rbac:groups="",resources=nodes,nodes/proxy,verbs=get,list,watch
107113
//+kubebuilder:rbac:groups=apps,resources=replicasets,deployments,verbs=get,list,watch,create,update,delete
108114
//+kubebuilder:rbac:groups=compliance.openshift.io,resources=compliancescans,verbs=create,watch,patch,get,list
109115
//+kubebuilder:rbac:groups=compliance.openshift.io,resources=*,verbs=*
@@ -275,7 +281,6 @@ func (r *ReconcileComplianceScan) validate(instance *compv1alpha1.ComplianceScan
275281

276282
func (r *ReconcileComplianceScan) phasePendingHandler(instance *compv1alpha1.ComplianceScan, logger logr.Logger) (reconcile.Result, error) {
277283
logger.Info("Phase: Pending")
278-
279284
// Remove annotation if needed
280285
if instance.NeedsRescan() {
281286
instanceCopy := instance.DeepCopy()
@@ -350,6 +355,11 @@ func (r *ReconcileComplianceScan) phaseLaunchingHandler(h scanTypeHandler, logge
350355
return reconcile.Result{}, err
351356
}
352357

358+
if err = r.handleRuntimeKubeletConfig(scan, logger); err != nil {
359+
logger.Error(err, "Cannot handle runtime kubelet config")
360+
return reconcile.Result{}, err
361+
}
362+
353363
if err = h.createScanWorkload(); err != nil {
354364
if !common.IsRetriable(err) {
355365
// Surface non-retriable errors to the CR
@@ -380,6 +390,101 @@ func (r *ReconcileComplianceScan) phaseLaunchingHandler(h scanTypeHandler, logge
380390
return reconcile.Result{}, nil
381391
}
382392

393+
// getRuntimeKubeletConfig gets the runtime kubeletconfig for a node
394+
// by fetching /api/v1/nodes/$nodeName/proxy/configz endpoint use the kubeClientset
395+
// and store it in a configmap for each node
396+
func (r *ReconcileComplianceScan) getRuntimeKubeletConfig(nodeName string) (string, error) {
397+
// get the runtime kubeletconfig
398+
kubeletConfigIO, err := r.ClientSet.CoreV1().RESTClient().Get().RequestURI("/api/v1/nodes/" + nodeName + "/proxy/configz").Stream(context.TODO())
399+
if err != nil {
400+
return "", err
401+
}
402+
defer kubeletConfigIO.Close()
403+
kubeletConfig, err := ioutil.ReadAll(kubeletConfigIO)
404+
if err != nil {
405+
return "", err
406+
}
407+
// kubeletConfig is a byte array, we need to convert it to string
408+
kubeletConfigStr := string(kubeletConfig)
409+
return kubeletConfigStr, nil
410+
}
411+
412+
func (r *ReconcileComplianceScan) createKubeletConfigCM(instance *compv1alpha1.ComplianceScan, node *corev1.Node) error {
413+
kubeletConfig, err := r.getRuntimeKubeletConfig(node.Name)
414+
if err != nil {
415+
return fmt.Errorf("cannot get the runtime kubelet config for node %s: %v", node.Name, err)
416+
}
417+
trueP := true
418+
// create a configmap for the node
419+
kubeletConfigCM := &corev1.ConfigMap{
420+
ObjectMeta: metav1.ObjectMeta{
421+
Name: getKubeletCMNameForScan(instance, node),
422+
Namespace: instance.Namespace,
423+
CreationTimestamp: metav1.Time{
424+
Time: time.Now(),
425+
},
426+
Labels: map[string]string{
427+
compv1alpha1.ComplianceScanLabel: instance.Name,
428+
compv1alpha1.KubeletConfigLabel: "",
429+
},
430+
},
431+
Data: map[string]string{
432+
KubeletConfigMapName: kubeletConfig,
433+
},
434+
Immutable: &trueP,
435+
}
436+
err = r.Client.Create(context.TODO(), kubeletConfigCM)
437+
if err != nil {
438+
return err
439+
}
440+
return nil
441+
}
442+
443+
func (r *ReconcileComplianceScan) getNodesForScan(instance *compv1alpha1.ComplianceScan) (corev1.NodeList, error) {
444+
nodes := corev1.NodeList{}
445+
nodeScanSelector := map[string]string{"kubernetes.io/os": "linux"}
446+
listOpts := client.ListOptions{
447+
LabelSelector: labels.SelectorFromSet(labels.Merge(instance.Spec.NodeSelector, nodeScanSelector)),
448+
}
449+
450+
if err := r.Client.List(context.TODO(), &nodes, &listOpts); err != nil {
451+
return nodes, err
452+
}
453+
return nodes, nil
454+
}
455+
456+
func (r *ReconcileComplianceScan) handleRuntimeKubeletConfig(instance *compv1alpha1.ComplianceScan, logger logr.Logger) error {
457+
// only handle node scans
458+
if instance.Spec.ScanType != compv1alpha1.ScanTypeNode {
459+
return nil
460+
}
461+
nodes, err := r.getNodesForScan(instance)
462+
if err != nil {
463+
logger.Error(err, "Cannot get the nodes for the scan")
464+
return err
465+
}
466+
for _, node := range nodes.Items {
467+
// check if the configmap already exists for the node
468+
kubeletConfigCM := &corev1.ConfigMap{}
469+
err := r.Client.Get(context.TODO(), types.NamespacedName{Name: getKubeletCMNameForScan(instance, &node), Namespace: instance.Namespace}, kubeletConfigCM)
470+
if err != nil {
471+
if !errors.IsNotFound(err) {
472+
logger.Error(err, "Error getting the configmap for the runtime kubeletconfig", "node", node.Name)
473+
return err
474+
}
475+
// create a configmap for the node
476+
err = r.createKubeletConfigCM(instance, &node)
477+
logger.Info("Created a new configmap for the node", "configmap", kubeletConfigCM.Name, "node", node.Name)
478+
if err != nil {
479+
logger.Error(err, "Error creating the configmap for the runtime kubeletconfig", "node", node.Name)
480+
return err
481+
}
482+
continue
483+
}
484+
}
485+
return nil
486+
}
487+
383488
func (r *ReconcileComplianceScan) phaseRunningHandler(h scanTypeHandler, logger logr.Logger) (reconcile.Result, error) {
384489
logger.Info("Phase: Running")
385490

@@ -575,6 +680,7 @@ func (r *ReconcileComplianceScan) phaseDoneHandler(h scanTypeHandler, instance *
575680
logger.Error(err, "Cannot delete aggregator")
576681
return reconcile.Result{}, err
577682
}
683+
578684
}
579685

580686
// We need to remove resources before doing a re-scan
@@ -605,6 +711,11 @@ func (r *ReconcileComplianceScan) phaseDoneHandler(h scanTypeHandler, instance *
605711
return reconcile.Result{}, err
606712
}
607713

714+
if err := r.deleteKubeletConfigConfigMaps(instance, logger); err != nil {
715+
logger.Error(err, "Cannot delete KubeletConfig ConfigMaps")
716+
return reconcile.Result{}, err
717+
}
718+
608719
if instance.NeedsRescan() {
609720
if err = r.deleteResultConfigMaps(instance, logger); err != nil {
610721
logger.Error(err, "Cannot delete result ConfigMaps")
@@ -752,6 +863,19 @@ func (r *ReconcileComplianceScan) deleteResultConfigMaps(instance *compv1alpha1.
752863
return nil
753864
}
754865

866+
func (r *ReconcileComplianceScan) deleteKubeletConfigConfigMaps(instance *compv1alpha1.ComplianceScan, logger logr.Logger) error {
867+
inNs := client.InNamespace(common.GetComplianceOperatorNamespace())
868+
withLabel := client.MatchingLabels{
869+
compv1alpha1.ComplianceScanLabel: instance.Name,
870+
compv1alpha1.KubeletConfigLabel: "",
871+
}
872+
err := r.Client.DeleteAllOf(context.Background(), &corev1.ConfigMap{}, inNs, withLabel)
873+
if err != nil {
874+
return err
875+
}
876+
return nil
877+
}
878+
755879
// returns true if the pod is still running, false otherwise
756880
func isPodRunningInNode(r *ReconcileComplianceScan, scanInstance *compv1alpha1.ComplianceScan, node *corev1.Node, timeout time.Duration, logger logr.Logger) (bool, error) {
757881
podName := getPodForNodeName(scanInstance.Name, node.Name)

0 commit comments

Comments
 (0)