Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a mutating webhook to mount secrets #857

Merged
merged 16 commits into from
Mar 6, 2023
1 change: 1 addition & 0 deletions CHANGELOG/CHANGELOG-1.6.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ When cutting a new release, update the `unreleased` heading to the tag being gen
* [BUGFIX] [#854](https://github.com/k8ssandra/k8ssandra-operator/issues/854) Use Patch() to update K8ssandraTask status.
* [CHANGE] [#887](https://github.com/k8ssandra/k8ssandra-operator/issues/887) Fix CVE-2022-32149.
* [CHANGE] [#891](https://github.com/k8ssandra/k8ssandra-operator/issues/848) Update golang.org/x/net to fix several CVEs.
* [FEATURE] [#605](https://github.com/k8ssandra/k8ssandra-operator/issues/598) Create a mutating webhook for the internal secrets provider
5 changes: 5 additions & 0 deletions config/components/single-namespace/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ replacements:
kind: ValidatingWebhookConfiguration
fieldPaths:
- webhooks.0.clientConfig.service.namespace
- select:
name: k8ssandra-operator-mutating-webhook-configuration
kind: MutatingWebhookConfiguration
fieldPaths:
- webhooks.0.clientConfig.service.namespace
patchesStrategicMerge:
- |-
apiVersion: v1
Expand Down
14 changes: 7 additions & 7 deletions config/default/webhookcainjection_patch.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# This patch add annotation to admission webhook config and
# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.
# apiVersion: admissionregistration.k8s.io/v1
# kind: MutatingWebhookConfiguration
# metadata:
# name: mutating-webhook-configuration
# annotations:
# cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
# ---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: k8ssandra-operator-mutating-webhook-configuration
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE_K8S)/$(CERTIFICATE_NAME_K8S)
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
Expand Down
21 changes: 21 additions & 0 deletions config/webhook/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,24 @@ patchesJson6902:
- op: replace
path: /webhooks/0/clientConfig/service/name
value: k8ssandra-operator-webhook-service

# adding the objectSelector prevents the bootstrapping problem
# where the mutation request for the operator pod would be
# sent before the operator pod is created
patchesJson6902:
sseidman marked this conversation as resolved.
Show resolved Hide resolved
- target:
group: admissionregistration.k8s.io
version: v1
name: k8ssandra-operator-mutating-webhook-configuration
kind: MutatingWebhookConfiguration
patch: |-
- op: replace
path: /webhooks/0/clientConfig/service/name
value: k8ssandra-operator-webhook-service
- op: add
path: /webhooks/0/objectSelector
value:
matchExpressions:
- key: control-plane
operator: NotIn
values: ["k8ssandra-operator"]
14 changes: 7 additions & 7 deletions config/webhook/kustomizeconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ nameReference:
- kind: Service
version: v1
fieldSpecs:
# - kind: MutatingWebhookConfiguration
# group: admissionregistration.k8s.io
# path: webhooks/clientConfig/service/name
- kind: MutatingWebhookConfiguration
group: admissionregistration.k8s.io
path: webhooks/clientConfig/service/name
- kind: ValidatingWebhookConfiguration
group: admissionregistration.k8s.io
path: webhooks/clientConfig/service/name

namespace:
# - kind: MutatingWebhookConfiguration
# group: admissionregistration.k8s.io
# path: webhooks/clientConfig/service/namespace
# create: true
- kind: MutatingWebhookConfiguration
group: admissionregistration.k8s.io
path: webhooks/clientConfig/service/namespace
create: true
- kind: ValidatingWebhookConfiguration
group: admissionregistration.k8s.io
path: webhooks/clientConfig/service/namespace
Expand Down
27 changes: 27 additions & 0 deletions config/webhook/manifests.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
creationTimestamp: null
name: mutating-webhook-configuration
webhooks:
- admissionReviewVersions:
- v1
clientConfig:
service:
name: webhook-service
namespace: system
path: /mutate-v1-pod-secrets-inject
failurePolicy: Fail
name: mpod.kb.io
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
- UPDATE
resources:
- pods
sideEffects: None
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
creationTimestamp: null
Expand Down
25 changes: 23 additions & 2 deletions controllers/medusa/controllers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

cassdcapi "github.com/k8ssandra/cass-operator/apis/cassandra/v1beta1"
k8ssandractrl "github.com/k8ssandra/k8ssandra-operator/controllers/k8ssandra"
secretswebhook "github.com/k8ssandra/k8ssandra-operator/controllers/secrets-webhook"
"github.com/k8ssandra/k8ssandra-operator/pkg/clientcache"
"github.com/k8ssandra/k8ssandra-operator/pkg/config"
"k8s.io/client-go/kubernetes/scheme"
Expand Down Expand Up @@ -81,7 +82,15 @@ func setupMedusaBackupTestEnv(t *testing.T, ctx context.Context) *testutils.Mult
}

for _, env := range testEnv.GetDataPlaneEnvTests() {
dataPlaneMgr, err := ctrl.NewManager(env.Config, ctrl.Options{Scheme: scheme.Scheme})
dataPlaneMgr, err := ctrl.NewManager(
env.Config,
ctrl.Options{
Scheme: scheme.Scheme,
Host: env.WebhookInstallOptions.LocalServingHost,
Port: env.WebhookInstallOptions.LocalServingPort,
CertDir: env.WebhookInstallOptions.LocalServingCertDir,
},
)
if err != nil {
return err
}
Expand All @@ -94,6 +103,8 @@ func setupMedusaBackupTestEnv(t *testing.T, ctx context.Context) *testutils.Mult
if err != nil {
return err
}
secretswebhook.SetupSecretsInjectorWebhook(dataPlaneMgr)

go func() {
err := dataPlaneMgr.Start(ctx)
if err != nil {
Expand Down Expand Up @@ -214,7 +225,15 @@ func setupMedusaTaskTestEnv(t *testing.T, ctx context.Context) *testutils.MultiC
}

for _, env := range testEnv.GetDataPlaneEnvTests() {
dataPlaneMgr, err := ctrl.NewManager(env.Config, ctrl.Options{Scheme: scheme.Scheme})
dataPlaneMgr, err := ctrl.NewManager(
env.Config,
ctrl.Options{
Scheme: scheme.Scheme,
Host: env.WebhookInstallOptions.LocalServingHost,
Port: env.WebhookInstallOptions.LocalServingPort,
CertDir: env.WebhookInstallOptions.LocalServingCertDir,
},
)
if err != nil {
return err
}
Expand All @@ -233,6 +252,8 @@ func setupMedusaTaskTestEnv(t *testing.T, ctx context.Context) *testutils.MultiC
Scheme: scheme.Scheme,
ClientFactory: medusaClientFactory,
}).SetupWithManager(dataPlaneMgr)
secretswebhook.SetupSecretsInjectorWebhook(dataPlaneMgr)

if err != nil {
return err
}
Expand Down
151 changes: 151 additions & 0 deletions controllers/secrets-webhook/secretswebhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package secrets_webhook

import (
"context"
"encoding/json"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"net/http"

"github.com/k8ssandra/k8ssandra-operator/pkg/utils"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
log "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)

// +kubebuilder:webhook:path=/mutate-v1-pod-secrets-inject,mutating=true,failurePolicy=fail,groups="",resources=pods,verbs=create;update,versions=v1,name=mpod.kb.io,admissionReviewVersions=v1,sideEffects=None

func SetupSecretsInjectorWebhook(mgr ctrl.Manager) {
sseidman marked this conversation as resolved.
Show resolved Hide resolved
mgr.GetWebhookServer().Register("/mutate-v1-pod-secrets-inject", &webhook.Admission{Handler: &podSecretsInjector{Client: mgr.GetClient()}})
}

// podSecretsInjector is an admission handler that mutates pod manifests
// to include mechanisms for mounting secrets
type podSecretsInjector struct {
Client client.Client
decoder *admission.Decoder
}

// podSecretsInjector Implements admission.Handler.
var _ admission.Handler = &podSecretsInjector{}

// InjectDecoder injects the decoder into the podSecretsInjector
func (p *podSecretsInjector) InjectDecoder(d *admission.Decoder) error {
p.decoder = d
return nil
}

func (p *podSecretsInjector) Handle(ctx context.Context, req admission.Request) admission.Response {
logger := log.FromContext(ctx).WithValues("podSecretsInjector", req.Namespace)

pod := &corev1.Pod{}
err := p.decoder.Decode(req, pod)
if err != nil {
return admission.Errored(http.StatusBadRequest, err)
}

copy := pod.DeepCopy()

err = p.mutatePods(ctx, copy, logger)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

marshaledPod, err := json.Marshal(copy)
if err != nil {
return admission.Errored(http.StatusInternalServerError, err)
}

return admission.PatchResponseFromRaw(req.Object.Raw, marshaledPod)
}

// e.g. k8ssandra.io/inject-secret: '[{ "secretName": "test-secret", "path": "/etc/test/test-secret" }]'
const secretInjectionAnnotation = "k8ssandra.io/inject-secret"

type SecretInjection struct {
SecretName string `json:"secretName"`
Path string `json:"path"`
}

// mutatePods injects the secret mounting configuration into the pod
func (p *podSecretsInjector) mutatePods(ctx context.Context, pod *corev1.Pod, logger logr.Logger) error {
if pod.Annotations == nil {
logger.Info("no annotations exist", "podName", pod.Name, "namespace", pod.Namespace)
return nil
}

secretsStr := pod.Annotations[secretInjectionAnnotation]
if len(secretsStr) == 0 {
logger.Info("no secret annotation exists", "podName", pod.Name, "namespace", pod.Namespace)
return nil
}

var secrets []SecretInjection
if err := json.Unmarshal([]byte(secretsStr), &secrets); err != nil {
logger.Error(err, "unable to unmarhsal secrets annotation",
"annotation", secretsStr,
"podName", pod.Name,
"namespace", pod.Namespace,
)
return err
}

for _, secret := range secrets {
// get secret name from injection annotation
secretName := secret.SecretName
mountPath := secret.Path
logger.Info("creating volume and volume mount for secret",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, it would be good to include the pod name here.

"secret", secretName,
"secret path", mountPath,
"podName", pod.Name,
"namespace", pod.Namespace,
)

volume := corev1.Volume{
Name: secretName,
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: secretName,
},
},
}
injectVolume(pod, volume)

volumeMount := corev1.VolumeMount{
Name: secretName,
MountPath: mountPath,
}
injectVolumeMount(pod, volumeMount)
logger.Info("added volume and volumeMount to podSpec",
"secret", secretName,
"secret path", mountPath,
"podName", pod.Name,
"namespace", pod.Namespace,
)
}

return nil
}

// injectVolume attaches a volume to the pod spec
func injectVolume(pod *corev1.Pod, volume corev1.Volume) {
sseidman marked this conversation as resolved.
Show resolved Hide resolved
if _, found := utils.ContainsVolume(pod.Spec.Volumes, volume.Name); !found {
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
}
}

// injectVolumeMount attaches a volumeMount to all containers in the pod spec
func injectVolumeMount(pod *corev1.Pod, volumeMount corev1.VolumeMount) {
sseidman marked this conversation as resolved.
Show resolved Hide resolved
for i, container := range pod.Spec.Containers {
if utils.FindVolumeMount(&container, volumeMount.Name) == nil {
pod.Spec.Containers[i].VolumeMounts = append(container.VolumeMounts, volumeMount)
}
}
for i, container := range pod.Spec.InitContainers {
if utils.FindVolumeMount(&container, volumeMount.Name) == nil {
pod.Spec.Containers[i].VolumeMounts = append(container.VolumeMounts, volumeMount)
}
}
}
Loading