Initial commit

This commit is contained in:
Jakub Vavřík
2021-06-28 14:42:47 +02:00
commit b12356ce24
27 changed files with 1653 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
+23
View File
@@ -0,0 +1,23 @@
apiVersion: v2
name: secret-duplicator
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
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.1
# 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
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 0.0.1
@@ -0,0 +1,142 @@
#!/bin/bash
set -e
usage() {
cat <<EOF
Generate certificate suitable for use with an secret-duplicator webhook service.
This script uses k8s' CertificateSigningRequest API to a generate a
certificate signed by k8s CA suitable for use with secret-duplicator 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=descret-duplicator-webhook-certs
[ -z "${namespace}" ] && namespace=secret-duplicator
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 -w0)" \
--arg namespace "$namespace" \
--arg csrName "$csrName" '{
apiVersion: "certificates.k8s.io/v1beta1",
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 -
@@ -0,0 +1,81 @@
{{/* vim: set filetype=mustache: */}}
{{/*
Expand the name of the chart.
*/}}
{{- define "secret-duplicator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "secret-duplicator.serviceName" -}}
ips-injector-svc
{{- end }}
{{- define "secret-duplicator.certificateSecretName" -}}
{{ include "secret-duplicator.name" . }}-webhook-certs
{{- end }}
{{- define "secret-duplicator.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 -}}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "secret-duplicator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "secret-duplicator.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "secret-duplicator.labels" -}}
helm.sh/chart: {{ include "secret-duplicator.chart" . }}
{{ include "secret-duplicator.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "secret-duplicator.selectorLabels" -}}
app.kubernetes.io/name: {{ include "secret-duplicator.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "secret-duplicator.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "secret-duplicator.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: secret-duplicator-cert-gen-entrypoint
namespace: {{ .Release.Namespace }}
labels:
{{- include "secret-duplicator.labels" . | nindent 4 }}
data:
entrypoint.sh: | {{ .Files.Get "scripts/create-signed-cert.sh" | nindent 4 }}
@@ -0,0 +1,41 @@
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: "{{ .Release.Name }}-cert-gen-cron-job"
labels:
{{- include "secret-duplicator.labels" . | nindent 4 }}
spec:
schedule: '* * * * 0'
jobTemplate:
metadata:
name: "{{ .Release.Name }}"
labels:
{{- include "secret-duplicator.labels" . | nindent 8 }}
spec:
ttlSecondsAfterFinished: 30
template:
spec:
serviceAccountName: secret-duplicator-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 "secret-duplicator.serviceName" . }}"
- --namespace
- "{{ .Release.Namespace }}"
- --secret
- "{{ include "secret-duplicator.certificateSecretName" . }}"
volumeMounts:
- mountPath: "/entrypoint"
name: entrypoint
volumes:
- name: entrypoint
configMap:
name: secret-duplicator-cert-gen-entrypoint
items:
- key: entrypoint.sh
path: entrypoint.sh
mode: 0755
@@ -0,0 +1,34 @@
apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}-cert-gen-job"
labels:
{{- include "secret-duplicator.labels" . | nindent 4 }}
spec:
ttlSecondsAfterFinished: 30
template:
spec:
serviceAccountName: secret-duplicator-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 "secret-duplicator.serviceName" . }}"
- --namespace
- "{{ .Release.Namespace }}"
- --secret
- "{{ include "secret-duplicator.certificateSecretName" . }}"
volumeMounts:
- mountPath: "/entrypoint"
name: entrypoint
volumes:
- name: entrypoint
configMap:
name: secret-duplicator-cert-gen-entrypoint
items:
- key: entrypoint.sh
path: entrypoint.sh
mode: 0755
@@ -0,0 +1,69 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: secret-duplicator-cert-gen
namespace : {{ .Release.Namespace }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: secret-duplicator-cert-gen
name: secret-duplicator-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: secret-duplicator-cert-gen
labels:
{{- include "secret-duplicator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secret-duplicator-cert-gen
subjects:
- kind: ServiceAccount
name: secret-duplicator-cert-gen
namespace : {{ .Release.Namespace }}
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: tmp
namespace: {{ .Release.Namespace }}
data:
caBundle: {{ include "secret-duplicator.lookupCaBundle" . | quote }}
@@ -0,0 +1,48 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: secret-duplicator-webhook-deployment
namespace : {{ .Release.Namespace }}
labels:
app: secret-duplicator
{{- include "secret-duplicator.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
app: secret-duplicator
template:
metadata:
labels:
app: secret-duplicator
spec:
serviceAccountName: secret-duplicator
containers:
- name: secret-duplicator
image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
args:
- -alsologtostderr
- -v=4
- 2>&1
env:
- name: CONFIG_PORT
value: "8443"
- name: CONFIG_CERT_PATH
value: "/etc/webhook/certs/cert.pem"
- name: CONFIG_KEY_PATH
value: "/etc/webhook/certs/key.pem"
- name: CONFIG_EXCLUDE_NAMESPACES
value: {{ join "," .Values.secretDuplicator.excludeNamespaces | quote }}
- name: CONFIG_TARGET_SECRET_NAME
value: {{ .Values.secretDuplicator.targetSecretName | quote }}
- name: CONFIG_TARGET_SECRET_ANNOTATION
value: {{ .Values.secretDuplicator.targetSecretAnnotation | quote }}
volumeMounts:
- name: webhook-certs
mountPath: /etc/webhook/certs
readOnly: true
volumes:
- name: webhook-certs
secret:
secretName: {{ include "secret-duplicator.certificateSecretName" . }}
@@ -0,0 +1,25 @@
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: secret-duplicator-webhook-cfg
namespace: {{ .Release.Namespace }}
labels:
app: secret-duplicator
{{- include "secret-duplicator.labels" . | nindent 4 }}
webhooks:
- name: secret-duplicator.ysoftdevs.github.com
clientConfig:
service:
name: {{ include "secret-duplicator.serviceName" . }}
namespace : {{ .Release.Namespace }}
path: "/mutate"
caBundle: {{ include "secret-duplicator.lookupCaBundle" . }}
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["serviceaccounts"]
admissionReviewVersions: ["v1", "v1beta1"]
sideEffects: None
# The default "Fail" option prevents Gardener cluster to be hibernated
failurePolicy: Ignore
@@ -0,0 +1,72 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: secret-duplicator
namespace : {{ .Release.Namespace }}
labels:
{{- include "secret-duplicator.labels" . | nindent 4 }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
labels:
k8s-app: secret-duplicator
{{- include "secret-duplicator.labels" . | nindent 4 }}
name: secret-duplicator
rules:
- apiGroups:
- ""
resources:
- secrets
- serviceaccounts
verbs:
- list
- patch
- create
- get
- delete
- update
- apiGroups:
- ""
resources:
- namespaces
verbs:
- list
- get
- apiGroups:
- "certificates.k8s.io/v1"
resources:
- certificatesigningrequests
verbs:
- create
- list
- get
- apiGroups:
- "certificates.k8s.io/v1"
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: secret-duplicator
labels:
{{- include "secret-duplicator.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: secret-duplicator
subjects:
- kind: ServiceAccount
name: secret-duplicator
namespace : {{ .Release.Namespace }}
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "secret-duplicator.serviceName" . }}
namespace : {{ .Release.Namespace }}
labels:
app: secret-duplicator
{{- include "secret-duplicator.labels" . | nindent 4 }}
spec:
ports:
- port: 443
targetPort: 8443
selector: {{ include "secret-duplicator.selectorLabels" . | nindent 4 }}
+19
View File
@@ -0,0 +1,19 @@
image:
registry: ghcr.io/ysoftdevs/secret-duplicator
repository: secret-duplicator
pullPolicy: Always
# Overrides the image tag whose default is the chart appVersion.
tag: ""
certificateGeneratorImage:
registry: ghcr.io/ysoftdevs/secret-duplicator
repository: webhook-cert-generator
tag: ""
secretDuplicator:
targetSecretName: "dashboard-terminal-kube-apiserver-tls"
targetSecretAnnotation: "reflector.v1.k8s.emberstack.com/reflects=cert-manager/default-cert"
excludeNamespaces:
- kube-system
- traefik
- datadog