Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Add support for admission.k8s.io/v1 AdmissionReview and admissionregistration.k8s.io/v1 MutatingWebhookConfiguration (in addition to v1beta1) #49

Merged
merged 8 commits into from
May 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# Changelog for Vault Sidecar Injector

## Release v7.1.2 - 2021-XX-XX
## Release v7.2.0 - 2021-05-XX

This release comes with support for `admission.k8s.io/v1` AdmissionReview and `admissionregistration.k8s.io/v1` MutatingWebhookConfiguration on Kubernetes 1.16+. As a result, Vault Sidecar Injector now handles both v1 and v1beta1 versions of those resources.

*Note that `admission.k8s.io/v1beta1` AdmissionReview and `admissionregistration.k8s.io/v1beta1` MutatingWebhookConfiguration should not be supported (nor available) anymore on Kubernetes 1.22+*

**Changed**

- [VSI #48](https://github.com/Talend/vault-sidecar-injector/pull/48) - Minor chart updates (adjust CPU & memory for injected containers, add checks during chart install)
- [VSI #51](https://github.com/Talend/vault-sidecar-injector/pull/51) - Update base image to CentOS 7.9.2009

**Added**

- [VSI #49](https://github.com/Talend/vault-sidecar-injector/pull/49) - Add support for `admission.k8s.io/v1` AdmissionReview and `admissionregistration.k8s.io/v1` MutatingWebhookConfiguration (in addition to v1beta1)

## Release v7.1.1 - 2021-04-02

**Fixed**
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ENV TALEND_USER=talend
ENV TALEND_USERGROUP=$TALEND_USER
ENV TALEND_UID=61000

# Update CentOS (note that --security flag does not work on CentOS: https://www.caseylabs.com/centos-automatic-security-updates-do-not-work/)
# Update CentOS (note that --security flag does not work on CentOS: https://forums.centos.org/viewtopic.php?t=59369)
RUN set -x \
&& yum -y update \
&& yum clean all \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.local
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ENV TALEND_USER=talend
ENV TALEND_USERGROUP=$TALEND_USER
ENV TALEND_UID=61000

# Update CentOS (note that --security flag does not work on CentOS: https://www.caseylabs.com/centos-automatic-security-updates-do-not-work/)
# Update CentOS (note that --security flag does not work on CentOS: https://forums.centos.org/viewtopic.php?t=59369)
RUN set -x \
&& yum -y update \
&& yum clean all \
Expand Down
2 changes: 1 addition & 1 deletion VERSION_CHART
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.2.2
4.3.0
2 changes: 1 addition & 1 deletion VERSION_RELEASE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.1.2
7.2.0
2 changes: 1 addition & 1 deletion VERSION_VSI
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.1.1
7.2.0
11 changes: 11 additions & 0 deletions deploy/helm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,14 @@ Add Vault flag to skip verification of TLS certificates
-tls-skip-verify
{{- end -}}
{{- end -}}

{{/*
Return the appropriate apiVersion for MutatingWebhookConfiguration
*/}}
{{- define "mutatingwebhookconfiguration.apiversion" -}}
{{- if semverCompare ">=1.16" .Capabilities.KubeVersion.Version -}}
"admissionregistration.k8s.io/v1"
{{- else -}}
"admissionregistration.k8s.io/v1beta1"
{{- end -}}
{{- end -}}
6 changes: 5 additions & 1 deletion deploy/helm/templates/mutatingwebhookconfiguration.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: admissionregistration.k8s.io/v1beta1
apiVersion: {{ include "mutatingwebhookconfiguration.apiversion" . }}
kind: MutatingWebhookConfiguration
metadata:
name: {{ include "talend-vault-sidecar-injector.fullname" . }}
Expand All @@ -16,5 +16,9 @@ webhooks:
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
{{- if semverCompare ">=1.16" .Capabilities.KubeVersion.Version }}
admissionReviewVersions: ["v1", "v1beta1"]
sideEffects: None
{{- end }}
failurePolicy: {{ include "talend-vault-sidecar-injector.failurePolicy" .Values }}
{{ include "talend-vault-sidecar-injector.namespaceSelector" . | indent 4 }}
39 changes: 28 additions & 11 deletions pkg/k8s/k8s.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2019-2020 Talend - www.talend.com
// Copyright © 2019-2021 Talend - www.talend.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -71,6 +71,12 @@ func (k8sctl *K8SClient) CreateCertSecret(ca, cert, key []byte) error {
// Other way to get current namespace:
//ns, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")

// If secret already exists: log a warning before deleting it
if _, err := k8sctl.CoreV1().Secrets(strings.TrimSpace(string(ns))).Get(k8sctl.WebhookSecretName, metav1.GetOptions{}); err == nil {
klog.Warning("Webhook secret already exists: will be deleted then created again from new generated certificate")
k8sctl.DeleteCertSecret()
}

// Create Secret in same namespace as webhook
_, err := k8sctl.CoreV1().Secrets(strings.TrimSpace(string(ns))).Create(secret)
if err != nil {
Expand Down Expand Up @@ -111,17 +117,28 @@ func (k8sctl *K8SClient) PatchWebhookConfiguration(cacertfile string) error {
return err
}

webhookPatch := []byte(fmt.Sprintf(
`[{
"op": "add",
"path": "/webhooks/0/clientConfig/caBundle",
"value": %q
}]`, base64.StdEncoding.EncodeToString(caPEM)))

// Patch MutatingWebhookConfiguration resource with CA (should be base64-encoded PEM-encoded)
_, err = k8sctl.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Patch(
k8sctl.WebhookCfgName, types.JSONPatchType, []byte(fmt.Sprintf(
`[{
"op": "add",
"path": "/webhooks/0/clientConfig/caBundle",
"value": %q
}]`, base64.StdEncoding.EncodeToString(caPEM))))
if err != nil {
klog.Errorf("Error patching MutatingWebhookConfiguration's caBundle: %s", err)
return err
if _, err = k8sctl.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(k8sctl.WebhookCfgName, metav1.GetOptions{}); err == nil {
// v1 support
klog.Infof("Patching MutatingWebhookConfiguration v1 resource %v", k8sctl.WebhookCfgName)
if _, err = k8sctl.AdmissionregistrationV1().MutatingWebhookConfigurations().Patch(k8sctl.WebhookCfgName, types.JSONPatchType, webhookPatch); err != nil {
klog.Errorf("Error patching MutatingWebhookConfiguration's caBundle: %s", err)
return err
}
} else {
// v1beta1 support
klog.Infof("Patching MutatingWebhookConfiguration v1beta1 resource %v", k8sctl.WebhookCfgName)
if _, err = k8sctl.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Patch(k8sctl.WebhookCfgName, types.JSONPatchType, webhookPatch); err != nil {
klog.Errorf("Error patching MutatingWebhookConfiguration's caBundle: %s", err)
return err
}
}

return nil
Expand Down
89 changes: 89 additions & 0 deletions pkg/webhook/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright © 2019-2021 Talend - www.talend.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package webhook

import (
"unsafe"

admv1 "k8s.io/api/admission/v1"
admv1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)

// Note:
// =====
// These conversions come from https://github.com/jetstack/cert-manager/blob/ab0cd57dc58fd73a76fd96bd9d1402bd5ae96582/pkg/webhook/server/util/convert.go
// (which are adapted from https://github.com/kubernetes/kubernetes/blob/03d322035d2f199f2163658d94a153ed2b9de667/pkg/apis/admission/v1beta1/zz_generated.conversion.go)

func Convert_v1beta1_AdmissionReview_To_admission_AdmissionReview(in *admv1beta1.AdmissionReview, out *admv1.AdmissionReview) {
if in.Request != nil {
if out.Request == nil {
out.Request = &admv1.AdmissionRequest{}
}
in, out := &in.Request, &out.Request
*out = new(admv1.AdmissionRequest)
Convert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest(*in, *out)
} else {
out.Request = nil
}
out.Response = (*admv1.AdmissionResponse)(unsafe.Pointer(in.Response))
}

func Convert_v1beta1_AdmissionRequest_To_admission_AdmissionRequest(in *admv1beta1.AdmissionRequest, out *admv1.AdmissionRequest) {
out.UID = types.UID(in.UID)
out.Kind = in.Kind
out.Resource = in.Resource
out.SubResource = in.SubResource
out.RequestKind = (*metav1.GroupVersionKind)(unsafe.Pointer(in.RequestKind))
out.RequestResource = (*metav1.GroupVersionResource)(unsafe.Pointer(in.RequestResource))
out.RequestSubResource = in.RequestSubResource
out.Name = in.Name
out.Namespace = in.Namespace
out.Operation = admv1.Operation(in.Operation)
out.Object = in.Object
out.OldObject = in.OldObject
out.Options = in.Options
}

func Convert_admission_AdmissionReview_To_v1beta1_AdmissionReview(in *admv1.AdmissionReview, out *admv1beta1.AdmissionReview) {
if in.Request != nil {
if out.Request == nil {
out.Request = &admv1beta1.AdmissionRequest{}
}
in, out := &in.Request, &out.Request
*out = new(admv1beta1.AdmissionRequest)
Convert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest(*in, *out)
} else {
out.Request = nil
}
out.Response = (*admv1beta1.AdmissionResponse)(unsafe.Pointer(in.Response))
}

func Convert_admission_AdmissionRequest_To_v1beta1_AdmissionRequest(in *admv1.AdmissionRequest, out *admv1beta1.AdmissionRequest) {
out.UID = types.UID(in.UID)
out.Kind = in.Kind
out.Resource = in.Resource
out.SubResource = in.SubResource
out.RequestKind = (*metav1.GroupVersionKind)(unsafe.Pointer(in.RequestKind))
out.RequestResource = (*metav1.GroupVersionResource)(unsafe.Pointer(in.RequestResource))
out.RequestSubResource = in.RequestSubResource
out.Name = in.Name
out.Namespace = in.Namespace
out.Operation = admv1beta1.Operation(in.Operation)
out.Object = in.Object
out.OldObject = in.OldObject
out.Options = in.Options
}
109 changes: 109 additions & 0 deletions pkg/webhook/mutate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright © 2019-2021 Talend - www.talend.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package webhook

import (
"encoding/json"
ctx "talend/vault-sidecar-injector/pkg/context"

admv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog"
)

func (vaultInjector *VaultInjector) mutate(ar *admv1.AdmissionReview) *admv1.AdmissionResponse {
var pod corev1.Pod
var podName, podNamespace string

req := ar.Request

if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
klog.Errorf("Could not unmarshal raw object: %v", err)
return &admv1.AdmissionResponse{
UID: req.UID,
Result: &metav1.Status{
Message: err.Error(),
},
}
}

if klog.V(5) { // enabled by providing '-v=5' at least
klog.Infof("Pod=%+v", pod)
}

if pod.Name == "" {
podName = pod.GenerateName
} else {
podName = pod.Name
}

if pod.Namespace == "" {
podNamespace = metav1.NamespaceDefault
} else {
podNamespace = pod.Namespace
}

klog.Infof("AdmissionReview '%v' for '%+v', Namespace=%v Name='%v (%s/%s)' UID=%v patchOperation=%v",
ar.GroupVersionKind(), req.Kind, req.Namespace, req.Name, podNamespace, podName, req.UID, req.Operation)

// Determine whether to perform mutation
if !mutationRequired(ignoredNamespaces, vaultInjector.VaultInjectorAnnotationsFQ, &pod.ObjectMeta) {
klog.Infof("Skipping mutation for %s/%s due to policy check", podNamespace, podName)
return &admv1.AdmissionResponse{
UID: req.UID,
Allowed: true,
}
}

annotations := map[string]string{vaultInjector.VaultInjectorAnnotationsFQ[ctx.VaultInjectorAnnotationStatusKey]: ctx.VaultInjectorStatusInjected}
patchBytes, err := vaultInjector.createPatch(&pod, annotations)
if err != nil {
return &admv1.AdmissionResponse{
UID: req.UID,
Allowed: false,
Result: &metav1.Status{
Message: err.Error(),
},
}
}

klog.Infof("AdmissionResponse: patch=%v\n", string(patchBytes))
return &admv1.AdmissionResponse{
UID: req.UID,
Allowed: true,
Patch: patchBytes,
PatchType: func() *admv1.PatchType {
pt := admv1.PatchTypeJSONPatch
return &pt
}(),
}
}

// Create mutation patch for resources
func (vaultInjector *VaultInjector) createPatch(pod *corev1.Pod, annotations map[string]string) ([]byte, error) {

patchPodSpec, err := vaultInjector.updatePodSpec(pod)
if err != nil {
return nil, err
}

var patch []ctx.PatchOperation

patch = append(patch, patchPodSpec...)
patch = append(patch, updateAnnotation(pod.Annotations, annotations)...)

return json.Marshal(patch)
}
Loading