Use cert-manager as webhook cert signer

This commit is contained in:
pvito
2022-08-11 15:12:41 +02:00
parent a52592033f
commit 2f3f5e78b5
12 changed files with 25 additions and 322 deletions

View File

@@ -15,7 +15,7 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.0.26
version: 0.0.27
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to

View File

@@ -1,142 +0,0 @@
#!/bin/bash
set -e
usage() {
cat <<EOF
Generate certificate suitable for use with an imagepullsecret-injector webhook service.
This script uses k8s' CertificateSigningRequest API to a generate a
certificate signed by k8s CA suitable for use with imagepullsecret-injector webhook
services. This requires permissions to create and approve CSR. See
https://kubernetes.io/docs/tasks/tls/managing-tls-in-a-cluster for
detailed explanation and additional instructions.
The server key/cert k8s CA cert are stored in a k8s secret.
usage: ${0} [OPTIONS]
The following flags are required.
--service Service name of webhook.
--namespace Namespace where webhook service and secret reside.
--secret Secret name for CA certificate and server certificate/key pair.
EOF
exit 1
}
while [[ $# -gt 0 ]]; do
case ${1} in
--service)
service="$2"
shift
;;
--secret)
secret="$2"
shift
;;
--namespace)
namespace="$2"
shift
;;
*)
usage
;;
esac
shift
done
[ -z "${service}" ] && service=ips-injector-svc
[ -z "${secret}" ] && secret=imagepullsecret-injector-webhook-certs
[ -z "${namespace}" ] && namespace=imagepullsecret-injector
if [ ! -x "$(command -v openssl)" ]; then
echo "openssl not found"
exit 1
fi
csrName=${service}.${namespace}
tmpdir=$(mktemp -d)
echo "Creating certs in tmpdir ${tmpdir}"
cat <<EOF >> "${tmpdir}"/csr.conf
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${service}
DNS.2 = ${service}.${namespace}
DNS.3 = ${service}.${namespace}.svc
EOF
openssl genrsa -out "${tmpdir}"/server-key.pem 2048
openssl req -new -key "${tmpdir}"/server-key.pem -subj "/O=system:nodes/CN=system:node:${service}.${namespace}.svc" -out "${tmpdir}"/server.csr -config "${tmpdir}"/csr.conf
# clean-up any previously created CSR for our service. Ignore errors if not present.
echo "Deleting old CertificateSigningRequests"
kubectl delete csr ${csrName} 2>/dev/null || true
echo "Creating new CertificateSigningRequests"
# create server cert/key CSR and send to k8s API
jq -n --arg request "$(< "${tmpdir}"/server.csr base64)" \
--arg namespace "$namespace" \
--arg csrName "$csrName" '{
apiVersion: "certificates.k8s.io/v1",
kind: "CertificateSigningRequest",
metadata: {
name: $csrName,
namespace: $namespace
},
spec: {
signerName: "kubernetes.io/kubelet-serving",
groups: ["system:authenticated"],
request: $request,
usages: [
"digital signature",
"key encipherment",
"server auth"
]
}
}' | kubectl create -f -
# verify CSR has been created
while true; do
if kubectl get csr ${csrName}; then
break
else
sleep 1
fi
done
echo "Approving CertificateSigningRequests"
# approve and fetch the signed certificate
kubectl certificate approve ${csrName}
echo "Fetching certificate from approved CertificateSigningRequests"
# verify certificate has been signed
for _ in $(seq 10); do
serverCert=$(kubectl get csr ${csrName} -o jsonpath='{.status.certificate}')
if [[ ${serverCert} != '' ]]; then
break
fi
sleep 1
done
if [[ ${serverCert} == '' ]]; then
echo "ERROR: After approving csr ${csrName}, the signed certificate did not appear on the resource. Giving up after 10 attempts." >&2
exit 1
fi
echo "${serverCert}" | openssl base64 -d -A -out "${tmpdir}"/server-cert.pem
echo "Creating secret $secret based on the retrieved certificate"
# create the secret with CA cert and server cert/key
kubectl create secret generic ${secret} \
--from-file=key.pem="${tmpdir}"/server-key.pem \
--from-file=cert.pem="${tmpdir}"/server-cert.pem \
--dry-run=client -o yaml |
kubectl -n ${namespace} apply -f -

View File

@@ -10,18 +10,12 @@ Expand the name of the chart.
ips-injector-svc
{{- end }}
{{- define "imagepullsecret-injector.certificateSecretName" -}}
{{ include "imagepullsecret-injector.name" . }}-webhook-certs
{{- define "imagepullsecret-injector.certificateName" -}}
{{ include "imagepullsecret-injector.name" . }}-webhook-cert
{{- end }}
{{- define "imagepullsecret-injector.lookupCaBundle" -}}
{{- /* Find the name of the secret corresponding to the default SA in the default namespace */ -}}
{{- /* Equivalent to `kubectl get sa -n default default -ojsonpath='{.secrets[0].name}'` */ -}}
{{- $defaultSecretName := ((lookup "v1" "ServiceAccount" "default" "default").secrets | first).name -}}
{{- /* Fetch the ca.crt from the default secret (still base64-encoded)*/ -}}
{{- /* Equivalent to `kubectl get secret -n default $defaultSecretName -ojsonpath='{.data.ca\.crt}'` */ -}}
{{- $caBundle := get (lookup "v1" "Secret" "default" $defaultSecretName ).data "ca.crt" -}}
{{- $caBundle -}}
{{- define "imagepullsecret-injector.certificateSecretName" -}}
{{ include "imagepullsecret-injector.name" . }}-webhook-certs
{{- end }}
{{/*

View File

@@ -0,0 +1,12 @@
kind: Certificate
metadata:
name: {{ include "imagepullsecret-injector.certificateName" . }}
spec:
commonName: {{ include "imagepullsecret-injector.serviceName" . }}.{{ .Release.Namespace }}.svc
dnsNames:
- {{ include "imagepullsecret-injector.serviceName" . }}.{{ .Release.Namespace }}.svc.cluster.local
- {{ include "imagepullsecret-injector.serviceName" . }}.{{ .Release.Namespace }}.svc
issuerRef:
kind: ClusterIssuer
name: {{ .Values.clusterIssuer }}
secretName: {{ include "imagepullsecret-injector.certificateSecretName" . | quote }}

View File

@@ -1,9 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: imagepullsecret-injector-cert-gen-entrypoint
namespace: {{ .Release.Namespace }}
labels:
{{- include "imagepullsecret-injector.labels" . | nindent 4 }}
data:
entrypoint.sh: | {{ .Files.Get "scripts/create-signed-cert.sh" | nindent 4 }}

View File

@@ -1,41 +0,0 @@
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: "{{ .Release.Name }}-cert-gen-cron-job"
labels:
{{- include "imagepullsecret-injector.labels" . | nindent 4 }}
spec:
schedule: {{ .Values.certificateGenerator.cronJobSchedule }}
jobTemplate:
metadata:
name: "{{ .Release.Name }}"
labels:
{{- include "imagepullsecret-injector.labels" . | nindent 8 }}
spec:
ttlSecondsAfterFinished: 30
template:
spec:
serviceAccountName: imagepullsecret-injector-cert-gen
restartPolicy: Never
containers:
- name: pre-install-job
image: "{{ .Values.certificateGeneratorImage.registry }}/{{ .Values.certificateGeneratorImage.repository }}:{{ .Values.certificateGeneratorImage.tag | default .Chart.AppVersion }}"
command: ["/entrypoint/entrypoint.sh"]
args:
- --service
- "{{ include "imagepullsecret-injector.serviceName" . }}"
- --namespace
- "{{ .Release.Namespace }}"
- --secret
- "{{ include "imagepullsecret-injector.certificateSecretName" . }}"
volumeMounts:
- mountPath: "/entrypoint"
name: entrypoint
volumes:
- name: entrypoint
configMap:
name: imagepullsecret-injector-cert-gen-entrypoint
items:
- key: entrypoint.sh
path: entrypoint.sh
mode: 0755

View File

@@ -1,34 +0,0 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-cert-gen-job-{{ .Release.Revision }}"
labels:
{{- include "imagepullsecret-injector.labels" . | nindent 4 }}
spec:
ttlSecondsAfterFinished: 30
template:
spec:
serviceAccountName: imagepullsecret-injector-cert-gen
restartPolicy: Never
containers:
- name: pre-install-job
image: "{{ .Values.certificateGeneratorImage.registry }}/{{ .Values.certificateGeneratorImage.repository }}:{{ .Values.certificateGeneratorImage.tag | default .Chart.AppVersion }}"
command: ["/entrypoint/entrypoint.sh"]
args:
- --service
- "{{ include "imagepullsecret-injector.serviceName" . }}"
- --namespace
- "{{ .Release.Namespace }}"
- --secret
- "{{ include "imagepullsecret-injector.certificateSecretName" . }}"
volumeMounts:
- mountPath: "/entrypoint"
name: entrypoint
volumes:
- name: entrypoint
configMap:
name: imagepullsecret-injector-cert-gen-entrypoint
items:
- key: entrypoint.sh
path: entrypoint.sh
mode: 0755

View File

@@ -1,69 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: imagepullsecret-injector-cert-gen
namespace : {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: imagepullsecret-injector-cert-gen
name: imagepullsecret-injector-cert-gen
rules:
- apiGroups:
- ""
resources:
- secrets
- serviceaccounts
verbs:
- list
- patch
- create
- get
- delete
- apiGroups:
- ""
resources:
- namespaces
verbs:
- list
- get
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- create
- list
- get
- delete
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- kubernetes.io/kubelet-serving
verbs:
- approve
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: imagepullsecret-injector-cert-gen
labels:
{{- include "imagepullsecret-injector.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: imagepullsecret-injector-cert-gen
subjects:
- kind: ServiceAccount
name: imagepullsecret-injector-cert-gen
namespace : {{ .Release.Namespace }}

View File

@@ -1,7 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: tmp
namespace: {{ .Release.Namespace }}
data:
caBundle: {{ include "imagepullsecret-injector.lookupCaBundle" . | quote }}

View File

@@ -27,9 +27,9 @@ spec:
- name: CONFIG_PORT
value: "8443"
- name: CONFIG_CERT_PATH
value: "/etc/webhook/certs/cert.pem"
value: "/etc/webhook/certs/tls.crt"
- name: CONFIG_KEY_PATH
value: "/etc/webhook/certs/key.pem"
value: "/etc/webhook/certs/tls.key"
- name: CONFIG_EXCLUDE_NAMESPACES
value: {{ join "," .Values.imagepullsecretInjector.excludeNamespaces | quote }}
- name: CONFIG_SERVICE_ACCOUNTS

View File

@@ -6,6 +6,8 @@ metadata:
labels:
app: imagepullsecret-injector
{{- include "imagepullsecret-injector.labels" . | nindent 4 }}
annotations:
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "imagepullsecret-injector.certificateName" . }}
webhooks:
- name: imagepullsecret-injector.ysoftdevs.github.com
clientConfig:
@@ -13,7 +15,7 @@ webhooks:
name: {{ include "imagepullsecret-injector.serviceName" . }}
namespace : {{ .Release.Namespace }}
path: "/mutate"
caBundle: {{ include "imagepullsecret-injector.lookupCaBundle" . }}
caBundle: "Cg=="
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]

View File

@@ -5,11 +5,6 @@ image:
# Overrides the image tag whose default is the chart appVersion.
tag: ""
certificateGeneratorImage:
registry: ghcr.io/ysoftdevs/imagepullsecret-injector
repository: webhook-cert-generator
tag: ""
imagepullsecretInjector:
dockerconfigjsonRef:
secretName: acr-dockerconfigjson-source
@@ -26,4 +21,6 @@ imagepullsecretInjector:
- datadog
certificateGenerator:
cronJobSchedule: '0 0 * * 0' # At 00:00 on Sunday
cronJobSchedule: '0 0 * * 0' # At 00:00 on Sunday
clusterIssuer: "webhook-issuer"