From 3fecd1b5f4d38b956f5be5de1d224da656d9932a Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Thu, 31 Aug 2023 09:43:05 +0100 Subject: [PATCH] feat: Add audit policy patch (#126) --- .pre-commit-config.yaml | 1 + charts/capi-runtime-extensions/README.md | 1 + charts/capi-runtime-extensions/values.yaml | 2 + docs/content/audit-policy.md | 29 ++++ internal/runtimehooks/webhooks/server.go | 8 +- pkg/capi/clustertopology/patches/generator.go | 41 +++++ .../patches/selectors/selectors.go | 48 ++++++ .../embedded/apiserver-audit-policy.yaml | 161 ++++++++++++++++++ pkg/handlers/auditpolicy/inject.go | 135 +++++++++++++++ pkg/handlers/auditpolicy/inject_test.go | 136 +++++++++++++++ pkg/handlers/httpproxy/inject.go | 57 +------ .../httpproxy/systemd_proxy_config.go | 12 +- 12 files changed, 574 insertions(+), 57 deletions(-) create mode 100644 docs/content/audit-policy.md create mode 100644 pkg/capi/clustertopology/patches/generator.go create mode 100644 pkg/capi/clustertopology/patches/selectors/selectors.go create mode 100644 pkg/handlers/auditpolicy/embedded/apiserver-audit-policy.yaml create mode 100644 pkg/handlers/auditpolicy/inject.go create mode 100644 pkg/handlers/auditpolicy/inject_test.go diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb4e9d150..61ee4dfbd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -101,6 +101,7 @@ repos: name: License headers - YAML and Makefiles stages: [commit] files: (^Makefile|\.(ya?ml|mk))$ + exclude: ^pkg/handlers/.+/embedded/.+\.ya?ml$ args: - --license-filepath - hack/license-header.txt diff --git a/charts/capi-runtime-extensions/README.md b/charts/capi-runtime-extensions/README.md index 32834dc6b..9d07222df 100644 --- a/charts/capi-runtime-extensions/README.md +++ b/charts/capi-runtime-extensions/README.md @@ -31,6 +31,7 @@ A Helm chart for capi-runtime-extensions | controllers.enableLeaderElection | bool | `false` | | | deployment.replicas | int | `1` | | | env | object | `{}` | | +| handlers.AuditPolicyPatch.enabled | bool | `true` | | | handlers.CalicoCNI.defaultInstallationConfigMaps.DockerCluster.configMap.content | string | `""` | | | handlers.CalicoCNI.defaultInstallationConfigMaps.DockerCluster.configMap.name | string | `"calico-cni-installation-dockercluster"` | | | handlers.CalicoCNI.defaultInstallationConfigMaps.DockerCluster.create | bool | `true` | | diff --git a/charts/capi-runtime-extensions/values.yaml b/charts/capi-runtime-extensions/values.yaml index a00acc543..2d9102953 100644 --- a/charts/capi-runtime-extensions/values.yaml +++ b/charts/capi-runtime-extensions/values.yaml @@ -19,6 +19,8 @@ handlers: enabled: true HTTPProxyPatch: enabled: true + AuditPolicyPatch: + enabled: true deployment: replicas: 1 diff --git a/docs/content/audit-policy.md b/docs/content/audit-policy.md new file mode 100644 index 000000000..4a6d32f4b --- /dev/null +++ b/docs/content/audit-policy.md @@ -0,0 +1,29 @@ +--- +title: "Audit policy" +--- + +Kubernetes auditing provides a security-relevant, chronological set of records documenting the sequence of actions in a +cluster. The cluster audits the activities generated by users, by applications that use the Kubernetes API, and by the +control plane itself. The `auditpolicypatch` external patch will generate appropriate configuration for the Kubernetes +control plane. + +To enable the audit policy enable the `auditpolicypatch` external patch on `ClusterClass`. + +```yaml +apiVersion: cluster.x-k8s.io/v1beta1 +kind: ClusterClass +metadata: + name: +spec: + patches: + - name: audit-policy + external: + generateExtension: "auditpolicypatch." +``` + +Applying this configuration will result in new bootstrap files on the `KubeadmControlPlaneTemplate`. + +This hook is enabled by default, and can be explicitly disabled by omitting the `AuditPolicyPatch` hook from the +`--runtimehooks.enabled-handlers` flag. + +If deploying via Helm, then this can be disabled by setting `handlers.AuditPolicyPatch.enabled=false`. diff --git a/internal/runtimehooks/webhooks/server.go b/internal/runtimehooks/webhooks/server.go index b4449e369..d2d5f3196 100644 --- a/internal/runtimehooks/webhooks/server.go +++ b/internal/runtimehooks/webhooks/server.go @@ -64,7 +64,13 @@ func (s *Server) AddFlags(prefix string, fs *pflag.FlagSet) { fs.StringSliceVar( &s.enabledHandlers, prefix+".enabled-handlers", - []string{"ServiceLoadBalancerGC", "CalicoCNI", "HTTPProxyPatch", "HTTPProxyVars"}, + []string{ + "ServiceLoadBalancerGC", + "CalicoCNI", + "HTTPProxyPatch", + "HTTPProxyVars", + "AuditPolicyPatch", + }, "list of all enabled handlers", ) diff --git a/pkg/capi/clustertopology/patches/generator.go b/pkg/capi/clustertopology/patches/generator.go new file mode 100644 index 000000000..9c8790e0f --- /dev/null +++ b/pkg/capi/clustertopology/patches/generator.go @@ -0,0 +1,41 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package patches + +import ( + "fmt" + + "github.com/go-logr/logr" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches/matchers" +) + +func Generate[T runtime.Object]( + obj runtime.Object, + vars map[string]apiextensionsv1.JSON, + holderRef *runtimehooksv1.HolderReference, + patchSelector clusterv1.PatchSelector, + log logr.Logger, + mutFn func(T) error, +) error { + typed, ok := obj.(T) + if !ok { + log.V(5).WithValues( + "objType", fmt.Sprintf("%T", obj), + "expectedType", fmt.Sprintf("%T", *new(T)), + ).Info("not matching type") + return nil + } + + if !matchers.MatchesSelector(patchSelector, obj, holderRef, vars) { + log.WithValues("selector", patchSelector).Info("not matching selector") + return nil + } + + return mutFn(typed) +} diff --git a/pkg/capi/clustertopology/patches/selectors/selectors.go b/pkg/capi/clustertopology/patches/selectors/selectors.go new file mode 100644 index 000000000..41260ed6d --- /dev/null +++ b/pkg/capi/clustertopology/patches/selectors/selectors.go @@ -0,0 +1,48 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package selectors + +import ( + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" + bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" + controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" +) + +func ControlPlane() clusterv1.PatchSelector { + return clusterv1.PatchSelector{ + APIVersion: controlplanev1.GroupVersion.String(), + Kind: "KubeadmControlPlaneTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + ControlPlane: true, + }, + } +} + +func DefaultWorkerSelector() clusterv1.PatchSelector { + return clusterv1.PatchSelector{ + APIVersion: bootstrapv1.GroupVersion.String(), + Kind: "KubeadmConfigTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ + Names: []string{ + "default-worker", + }, + }, + }, + } +} + +func AllWorkersSelector() clusterv1.PatchSelector { + return clusterv1.PatchSelector{ + APIVersion: bootstrapv1.GroupVersion.String(), + Kind: "KubeadmConfigTemplate", + MatchResources: clusterv1.PatchSelectorMatch{ + MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ + Names: []string{ + "*", + }, + }, + }, + } +} diff --git a/pkg/handlers/auditpolicy/embedded/apiserver-audit-policy.yaml b/pkg/handlers/auditpolicy/embedded/apiserver-audit-policy.yaml new file mode 100644 index 000000000..81330e150 --- /dev/null +++ b/pkg/handlers/auditpolicy/embedded/apiserver-audit-policy.yaml @@ -0,0 +1,161 @@ +# Taken from https://github.com/kubernetes/kubernetes/blob/master/cluster/gce/gci/configure-helper.sh +# Recommended in Kubernetes docs +apiVersion: audit.k8s.io/v1 +kind: Policy +rules: +# The following requests were manually identified as high-volume and low-risk, +# so drop them. +- level: None + users: ["system:kube-proxy"] + verbs: ["watch"] + resources: + - group: "" # core + resources: ["endpoints", "services", "services/status"] +- level: None + # Ingress controller reads 'configmaps/ingress-uid' through the unsecured port. + # TODO(#46983): Change this to the ingress controller service account. + users: ["system:unsecured"] + namespaces: ["kube-system"] + verbs: ["get"] + resources: + - group: "" # core + resources: ["configmaps"] +- level: None + users: ["kubelet"] # legacy kubelet identity + verbs: ["get"] + resources: + - group: "" # core + resources: ["nodes", "nodes/status"] +- level: None + userGroups: ["system:nodes"] + verbs: ["get"] + resources: + - group: "" # core + resources: ["nodes", "nodes/status"] +- level: None + users: + - system:kube-controller-manager + - system:kube-scheduler + - system:serviceaccount:kube-system:endpoint-controller + verbs: ["get", "update"] + namespaces: ["kube-system"] + resources: + - group: "" # core + resources: ["endpoints"] +- level: None + users: ["system:apiserver"] + verbs: ["get"] + resources: + - group: "" # core + resources: ["namespaces", "namespaces/status", "namespaces/finalize"] +- level: None + users: ["cluster-autoscaler"] + verbs: ["get", "update"] + namespaces: ["kube-system"] + resources: + - group: "" # core + resources: ["configmaps", "endpoints"] +# Don't log HPA fetching metrics. +- level: None + users: + - system:kube-controller-manager + verbs: ["get", "list"] + resources: + - group: "metrics.k8s.io" +# Don't log these read-only URLs. +- level: None + nonResourceURLs: + - /healthz* + - /version + - /swagger* +# Don't log events requests. +- level: None + resources: + - group: "" # core + resources: ["events"] +# node and pod status calls from nodes are high-volume and can be large, don't log responses for expected updates from nodes +- level: Request + users: ["kubelet", "system:node-problem-detector", "system:serviceaccount:kube-system:node-problem-detector"] + verbs: ["update","patch"] + resources: + - group: "" # core + resources: ["nodes/status", "pods/status"] + omitStages: + - "RequestReceived" +- level: Request + userGroups: ["system:nodes"] + verbs: ["update","patch"] + resources: + - group: "" # core + resources: ["nodes/status", "pods/status"] + omitStages: + - "RequestReceived" +# deletecollection calls can be large, don't log responses for expected namespace deletions +- level: Request + users: ["system:serviceaccount:kube-system:namespace-controller"] + verbs: ["deletecollection"] + omitStages: + - "RequestReceived" +# Secrets, ConfigMaps, and TokenReviews can contain sensitive & binary data, +# so only log at the Metadata level. +- level: Metadata + resources: + - group: "" # core + resources: ["secrets", "configmaps"] + - group: authentication.k8s.io + resources: ["tokenreviews"] + omitStages: + - "RequestReceived" +# Get responses can be large; skip them. +- level: Request + verbs: ["get", "list", "watch"] + resources: + - group: "" # core + - group: "admissionregistration.k8s.io" + - group: "apiextensions.k8s.io" + - group: "apiregistration.k8s.io" + - group: "apps" + - group: "authentication.k8s.io" + - group: "authorization.k8s.io" + - group: "autoscaling" + - group: "batch" + - group: "certificates.k8s.io" + - group: "extensions" + - group: "metrics.k8s.io" + - group: "networking.k8s.io" + - group: "node.k8s.io" + - group: "policy" + - group: "rbac.authorization.k8s.io" + - group: "scheduling.k8s.io" + - group: "settings.k8s.io" + - group: "storage.k8s.io" + omitStages: + - "RequestReceived" +# Default level for known APIs +- level: RequestResponse + resources: + - group: "" # core + - group: "admissionregistration.k8s.io" + - group: "apiextensions.k8s.io" + - group: "apiregistration.k8s.io" + - group: "apps" + - group: "authentication.k8s.io" + - group: "authorization.k8s.io" + - group: "autoscaling" + - group: "batch" + - group: "certificates.k8s.io" + - group: "extensions" + - group: "metrics.k8s.io" + - group: "networking.k8s.io" + - group: "node.k8s.io" + - group: "policy" + - group: "rbac.authorization.k8s.io" + - group: "scheduling.k8s.io" + - group: "settings.k8s.io" + - group: "storage.k8s.io" + omitStages: + - "RequestReceived" +# Default level for all other requests. +- level: Metadata + omitStages: + - "RequestReceived" diff --git a/pkg/handlers/auditpolicy/inject.go b/pkg/handlers/auditpolicy/inject.go new file mode 100644 index 000000000..340ea6cc1 --- /dev/null +++ b/pkg/handlers/auditpolicy/inject.go @@ -0,0 +1,135 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package auditpolicy + +import ( + "context" + _ "embed" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/types" + bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" + controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + "sigs.k8s.io/cluster-api/exp/runtime/topologymutation" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches" + "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches/selectors" + "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers" +) + +const ( + // HandlerNamePatch is the name of the inject handler. + HandlerNamePatch = "AuditPolicy" +) + +type auditPolicyPatchHandler struct { + decoder runtime.Decoder +} + +var ( + _ handlers.NamedHandler = &auditPolicyPatchHandler{} + _ handlers.GeneratePatchesMutationHandler = &auditPolicyPatchHandler{} + + //go:embed embedded/apiserver-audit-policy.yaml + auditPolicy string +) + +const auditPolicyPath = "/etc/kubernetes/audit-policy/apiserver-audit-policy.yaml" + +func NewPatch() *auditPolicyPatchHandler { + scheme := runtime.NewScheme() + _ = bootstrapv1.AddToScheme(scheme) + _ = controlplanev1.AddToScheme(scheme) + return &auditPolicyPatchHandler{ + decoder: serializer.NewCodecFactory(scheme).UniversalDecoder( + controlplanev1.GroupVersion, + bootstrapv1.GroupVersion, + ), + } +} + +func (h *auditPolicyPatchHandler) Name() string { + return HandlerNamePatch +} + +func (h *auditPolicyPatchHandler) GeneratePatches( + ctx context.Context, + req *runtimehooksv1.GeneratePatchesRequest, + resp *runtimehooksv1.GeneratePatchesResponse, +) { + topologymutation.WalkTemplates( + ctx, + h.decoder, + req, + resp, + func( + ctx context.Context, + obj runtime.Object, + vars map[string]apiextensionsv1.JSON, + holderRef runtimehooksv1.HolderReference, + ) error { + log := ctrl.LoggerFrom(ctx).WithValues( + "holderRef", holderRef, + ) + + return patches.Generate( + obj, vars, &holderRef, selectors.ControlPlane(), log, + func(obj *controlplanev1.KubeadmControlPlaneTemplate) error { + log.WithValues("namespacedName", types.NamespacedName{ + Name: obj.Name, + Namespace: obj.Namespace, + }).Info("adding files and updating API server extra args in kubeadm config spec") + + obj.Spec.Template.Spec.KubeadmConfigSpec.Files = append( + obj.Spec.Template.Spec.KubeadmConfigSpec.Files, + bootstrapv1.File{ + Path: auditPolicyPath, + Permissions: "0600", + Content: auditPolicy, + }, + ) + + if obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration == nil { + obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration = &bootstrapv1.ClusterConfiguration{} + } + apiServer := &obj.Spec.Template.Spec.KubeadmConfigSpec.ClusterConfiguration.APIServer + if apiServer.ExtraArgs == nil { + apiServer.ExtraArgs = make(map[string]string, 5) + } + + apiServer.ExtraArgs["audit-log-path"] = "/var/log/audit/kube-apiserver-audit.log" + apiServer.ExtraArgs["audit-log-maxage"] = "30" + apiServer.ExtraArgs["audit-log-maxbackup"] = "10" + apiServer.ExtraArgs["audit-log-maxsize"] = "100" + apiServer.ExtraArgs["audit-policy-file"] = auditPolicyPath + + if apiServer.ExtraVolumes == nil { + apiServer.ExtraVolumes = make([]bootstrapv1.HostPathMount, 0, 2) + } + + apiServer.ExtraVolumes = append( + apiServer.ExtraVolumes, + bootstrapv1.HostPathMount{ + Name: "audit-policy", + HostPath: "/etc/kubernetes/audit-policy/", + MountPath: "/etc/kubernetes/audit-policy/", + ReadOnly: true, + }, + bootstrapv1.HostPathMount{ + Name: "audit-logs", + HostPath: "/var/log/kubernetes/audit", + MountPath: "/var/log/audit/", + }, + ) + + return nil + }, + ) + }, + ) +} diff --git a/pkg/handlers/auditpolicy/inject_test.go b/pkg/handlers/auditpolicy/inject_test.go new file mode 100644 index 000000000..f204c021a --- /dev/null +++ b/pkg/handlers/auditpolicy/inject_test.go @@ -0,0 +1,136 @@ +// Copyright 2023 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package auditpolicy_test + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + "gomodules.xyz/jsonpatch/v2" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" + runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" + + "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers/auditpolicy" +) + +func TestGeneratePatches(t *testing.T) { + g := NewWithT(t) + h := auditpolicy.NewPatch() + req := &runtimehooksv1.GeneratePatchesRequest{} + resp := &runtimehooksv1.GeneratePatchesResponse{} + h.GeneratePatches(context.Background(), req, resp) + g.Expect(resp.Status).To(Equal(runtimehooksv1.ResponseStatusSuccess)) +} + +func TestGeneratePatches_KubeadmControlPlaneTemplate(t *testing.T) { + g := NewWithT(t) + h := auditpolicy.NewPatch() + req := &runtimehooksv1.GeneratePatchesRequest{ + Items: []runtimehooksv1.GeneratePatchesRequestItem{ + requestItem( + "1", + &controlplanev1.KubeadmControlPlaneTemplate{ + TypeMeta: v1.TypeMeta{ + Kind: "KubeadmControlPlaneTemplate", + APIVersion: controlplanev1.GroupVersion.String(), + }, + }, + &runtimehooksv1.HolderReference{ + Kind: "Cluster", + FieldPath: "spec.controlPlaneRef", + }, + ), + }, + } + resp := &runtimehooksv1.GeneratePatchesResponse{} + h.GeneratePatches(context.Background(), req, resp) + g.Expect(resp.Status).To(Equal(runtimehooksv1.ResponseStatusSuccess)) + g.Expect(resp.Items).To(ContainElement(MatchFields(IgnoreExtras, Fields{ + "UID": Equal(types.UID("1")), + "PatchType": Equal(runtimehooksv1.JSONPatchType), + "Patch": WithTransform( + func(data []byte) ([]jsonpatch.Operation, error) { + operations := []jsonpatch.Operation{} + if err := json.Unmarshal(data, &operations); err != nil { + return nil, err + } + return operations, nil + }, + ConsistOf( + MatchAllFields(Fields{ + "Operation": Equal("add"), + "Path": Equal("/spec/template/spec/kubeadmConfigSpec/files"), + "Value": HaveLen(1), + }), + jsonpatch.NewOperation( + "add", + "/spec/template/spec/kubeadmConfigSpec/clusterConfiguration", + map[string]interface{}{ + "scheduler": map[string]interface{}{}, + "apiServer": map[string]interface{}{ + "extraArgs": map[string]interface{}{ + "audit-log-maxbackup": "10", + "audit-log-maxsize": "100", + "audit-log-path": "/var/log/audit/kube-apiserver-audit.log", + "audit-policy-file": "/etc/kubernetes/audit-policy/apiserver-audit-policy.yaml", + "audit-log-maxage": "30", + }, + "extraVolumes": []interface{}{ + map[string]interface{}{ + "hostPath": "/etc/kubernetes/audit-policy/", + "mountPath": "/etc/kubernetes/audit-policy/", + "name": "audit-policy", + "readOnly": true, + }, + map[string]interface{}{ + "name": "audit-logs", + "hostPath": "/var/log/kubernetes/audit", + "mountPath": "/var/log/audit/", + }, + }, + }, + "controllerManager": map[string]interface{}{}, + "dns": map[string]interface{}{}, + "etcd": map[string]interface{}{}, + "networking": map[string]interface{}{}, + }, + ), + ), + ), + }))) +} + +func toJSON(v any) []byte { + data, err := json.Marshal(v) + if err != nil { + panic(err) + } + compacted := &bytes.Buffer{} + if err := json.Compact(compacted, data); err != nil { + panic(err) + } + return compacted.Bytes() +} + +// requestItem returns a GeneratePatchesRequestItem with the given uid, variables and object. +func requestItem( + uid string, + object any, + holderRef *runtimehooksv1.HolderReference, +) runtimehooksv1.GeneratePatchesRequestItem { + return runtimehooksv1.GeneratePatchesRequestItem{ + UID: types.UID(uid), + Object: runtime.RawExtension{ + Raw: toJSON(object), + }, + HolderReference: *holderRef, + } +} diff --git a/pkg/handlers/httpproxy/inject.go b/pkg/handlers/httpproxy/inject.go index 6adc255b5..758d786e5 100644 --- a/pkg/handlers/httpproxy/inject.go +++ b/pkg/handlers/httpproxy/inject.go @@ -5,21 +5,19 @@ package httpproxy import ( "context" - "fmt" - "github.com/go-logr/logr" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/types" - clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1" "sigs.k8s.io/cluster-api/exp/runtime/topologymutation" ctrl "sigs.k8s.io/controller-runtime" - "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches/matchers" + "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches" + "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/patches/selectors" "github.com/d2iq-labs/capi-runtime-extensions/pkg/capi/clustertopology/variables" "github.com/d2iq-labs/capi-runtime-extensions/pkg/handlers" ) @@ -88,15 +86,8 @@ func (h *httpProxyPatchHandler) GeneratePatches( log = log.WithValues("httpProxyVariable", httpProxyVariable) - controlPlaneSelector := clusterv1.PatchSelector{ - APIVersion: controlplanev1.GroupVersion.String(), - Kind: "KubeadmControlPlaneTemplate", - MatchResources: clusterv1.PatchSelectorMatch{ - ControlPlane: true, - }, - } - if err := generatePatch( - obj, vars, &holderRef, controlPlaneSelector, log, + if err := patches.Generate( + obj, vars, &holderRef, selectors.ControlPlane(), log, func(obj *controlplanev1.KubeadmControlPlaneTemplate) error { log.WithValues("namespacedName", types.NamespacedName{ Name: obj.Name, @@ -111,19 +102,8 @@ func (h *httpProxyPatchHandler) GeneratePatches( return err } - defaultWorkerSelector := clusterv1.PatchSelector{ - APIVersion: bootstrapv1.GroupVersion.String(), - Kind: "KubeadmConfigTemplate", - MatchResources: clusterv1.PatchSelectorMatch{ - MachineDeploymentClass: &clusterv1.PatchSelectorMatchMachineDeploymentClass{ - Names: []string{ - "default-worker", - }, - }, - }, - } - if err := generatePatch( - obj, vars, &holderRef, defaultWorkerSelector, log, + if err := patches.Generate( + obj, vars, &holderRef, selectors.AllWorkersSelector(), log, func(obj *bootstrapv1.KubeadmConfigTemplate) error { log.WithValues("namespacedName", types.NamespacedName{ Name: obj.Name, @@ -142,28 +122,3 @@ func (h *httpProxyPatchHandler) GeneratePatches( }, ) } - -func generatePatch[T runtime.Object]( - obj runtime.Object, - vars map[string]apiextensionsv1.JSON, - holderRef *runtimehooksv1.HolderReference, - patchSelector clusterv1.PatchSelector, - log logr.Logger, - mutFn func(T) error, -) error { - typed, ok := obj.(T) - if !ok { - log.V(5).WithValues( - "objType", fmt.Sprintf("%T", obj), - "expectedType", fmt.Sprintf("%T", *new(T)), - ).Info("not matching type") - return nil - } - - if !matchers.MatchesSelector(patchSelector, obj, holderRef, vars) { - log.WithValues("selector", patchSelector).Info("not matching selector") - return nil - } - - return mutFn(typed) -} diff --git a/pkg/handlers/httpproxy/systemd_proxy_config.go b/pkg/handlers/httpproxy/systemd_proxy_config.go index 8d43e8fbc..a6054793a 100644 --- a/pkg/handlers/httpproxy/systemd_proxy_config.go +++ b/pkg/handlers/httpproxy/systemd_proxy_config.go @@ -5,7 +5,7 @@ package httpproxy import ( "bytes" - "embed" + _ "embed" "strings" "text/template" @@ -13,10 +13,12 @@ import ( ) var ( - //go:embed templates - sources embed.FS + //go:embed templates/systemd.conf.tmpl + systemdProxyTemplateContents string - templates *template.Template = template.Must(template.ParseFS(sources, "templates/systemd.conf.tmpl")).Templates()[0] + systemdProxyTemplate *template.Template = template.Must( + template.New("systemd.conf.tmpl").Parse(systemdProxyTemplateContents), + ) systemdUnitPaths = []string{ "/etc/systemd/system/containerd.service.d/http-proxy.conf", @@ -40,7 +42,7 @@ func generateSystemdFiles(vars HTTPProxyVariables) []bootstrapv1.File { } var buf bytes.Buffer - err := templates.ExecuteTemplate(&buf, "systemd.conf.tmpl", tplVars) + err := systemdProxyTemplate.Execute(&buf, tplVars) if err != nil { panic(err) // This should be impossible with the simple template we have. }