Copied sample files
This commit is contained in:
parent
6621cea919
commit
66ac741c7d
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
FROM golang:1.21-alpine3.18 AS build_deps
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
WORKDIR /workspace
|
||||||
|
|
||||||
|
COPY go.mod .
|
||||||
|
COPY go.sum .
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
FROM build_deps AS build
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 go build -o webhook -ldflags '-w -extldflags "-static"' .
|
||||||
|
|
||||||
|
FROM alpine:3.18
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates
|
||||||
|
|
||||||
|
COPY --from=build /workspace/webhook /usr/local/bin/webhook
|
||||||
|
|
||||||
|
ENTRYPOINT ["webhook"]
|
||||||
45
Makefile
Normal file
45
Makefile
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
GO ?= $(shell which go)
|
||||||
|
OS ?= $(shell $(GO) env GOOS)
|
||||||
|
ARCH ?= $(shell $(GO) env GOARCH)
|
||||||
|
|
||||||
|
IMAGE_NAME := "webhook"
|
||||||
|
IMAGE_TAG := "latest"
|
||||||
|
|
||||||
|
OUT := $(shell pwd)/_out
|
||||||
|
|
||||||
|
KUBEBUILDER_VERSION=1.28.0
|
||||||
|
|
||||||
|
HELM_FILES := $(shell find deploy/sthome-webhook)
|
||||||
|
|
||||||
|
test: _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/etcd _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kube-apiserver _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kubectl
|
||||||
|
TEST_ASSET_ETCD=_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/etcd \
|
||||||
|
TEST_ASSET_KUBE_APISERVER=_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kube-apiserver \
|
||||||
|
TEST_ASSET_KUBECTL=_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kubectl \
|
||||||
|
$(GO) test -v .
|
||||||
|
|
||||||
|
_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH).tar.gz: | _test
|
||||||
|
curl -fsSL https://go.kubebuilder.io/test-tools/$(KUBEBUILDER_VERSION)/$(OS)/$(ARCH) -o $@
|
||||||
|
|
||||||
|
_test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/etcd _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kube-apiserver _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)/kubectl: _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH).tar.gz | _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH)
|
||||||
|
tar xfO $< kubebuilder/bin/$(notdir $@) > $@ && chmod +x $@
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -r _test $(OUT)
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
docker build -t "$(IMAGE_NAME):$(IMAGE_TAG)" .
|
||||||
|
|
||||||
|
.PHONY: rendered-manifest.yaml
|
||||||
|
rendered-manifest.yaml: $(OUT)/rendered-manifest.yaml
|
||||||
|
|
||||||
|
$(OUT)/rendered-manifest.yaml: $(HELM_FILES) | $(OUT)
|
||||||
|
helm template \
|
||||||
|
--name sthome-webhook \
|
||||||
|
--set image.repository=$(IMAGE_NAME) \
|
||||||
|
--set image.tag=$(IMAGE_TAG) \
|
||||||
|
deploy/sthome-webhook > $@
|
||||||
|
|
||||||
|
_test $(OUT) _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH):
|
||||||
|
mkdir -p $@
|
||||||
21
deploy/example-webhook/.helmignore
Normal file
21
deploy/example-webhook/.helmignore
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# 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
|
||||||
|
*~
|
||||||
|
# Various IDEs
|
||||||
|
.project
|
||||||
|
.idea/
|
||||||
|
*.tmproj
|
||||||
5
deploy/example-webhook/Chart.yaml
Normal file
5
deploy/example-webhook/Chart.yaml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
appVersion: "1.0"
|
||||||
|
description: A Helm chart for Kubernetes
|
||||||
|
name: example-webhook
|
||||||
|
version: 0.1.0
|
||||||
0
deploy/example-webhook/templates/NOTES.txt
Normal file
0
deploy/example-webhook/templates/NOTES.txt
Normal file
48
deploy/example-webhook/templates/_helpers.tpl
Normal file
48
deploy/example-webhook/templates/_helpers.tpl
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{{/* vim: set filetype=mustache: */}}
|
||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "example-webhook.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- 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 "example-webhook.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 "example-webhook.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "example-webhook.selfSignedIssuer" -}}
|
||||||
|
{{ printf "%s-selfsign" (include "example-webhook.fullname" .) }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "example-webhook.rootCAIssuer" -}}
|
||||||
|
{{ printf "%s-ca" (include "example-webhook.fullname" .) }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "example-webhook.rootCACertificate" -}}
|
||||||
|
{{ printf "%s-ca" (include "example-webhook.fullname" .) }}
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- define "example-webhook.servingCertificate" -}}
|
||||||
|
{{ printf "%s-webhook-tls" (include "example-webhook.fullname" .) }}
|
||||||
|
{{- end -}}
|
||||||
19
deploy/example-webhook/templates/apiservice.yaml
Normal file
19
deploy/example-webhook/templates/apiservice.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: apiregistration.k8s.io/v1
|
||||||
|
kind: APIService
|
||||||
|
metadata:
|
||||||
|
name: v1alpha1.{{ .Values.groupName }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/inject-ca-from: "{{ .Release.Namespace }}/{{ include "example-webhook.servingCertificate" . }}"
|
||||||
|
spec:
|
||||||
|
group: {{ .Values.groupName }}
|
||||||
|
groupPriorityMinimum: 1000
|
||||||
|
versionPriority: 15
|
||||||
|
service:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
version: v1alpha1
|
||||||
69
deploy/example-webhook/templates/deployment.yaml
Normal file
69
deploy/example-webhook/templates/deployment.yaml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
spec:
|
||||||
|
serviceAccountName: {{ include "example-webhook.fullname" . }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
args:
|
||||||
|
- --tls-cert-file=/tls/tls.crt
|
||||||
|
- --tls-private-key-file=/tls/tls.key
|
||||||
|
env:
|
||||||
|
- name: GROUP_NAME
|
||||||
|
value: {{ .Values.groupName | quote }}
|
||||||
|
ports:
|
||||||
|
- name: https
|
||||||
|
containerPort: 443
|
||||||
|
protocol: TCP
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
scheme: HTTPS
|
||||||
|
path: /healthz
|
||||||
|
port: https
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
scheme: HTTPS
|
||||||
|
path: /healthz
|
||||||
|
port: https
|
||||||
|
volumeMounts:
|
||||||
|
- name: certs
|
||||||
|
mountPath: /tls
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
{{ toYaml .Values.resources | indent 12 }}
|
||||||
|
volumes:
|
||||||
|
- name: certs
|
||||||
|
secret:
|
||||||
|
secretName: {{ include "example-webhook.servingCertificate" . }}
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{ toYaml . | indent 8 }}
|
||||||
|
{{- end }}
|
||||||
76
deploy/example-webhook/templates/pki.yaml
Normal file
76
deploy/example-webhook/templates/pki.yaml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
# Create a selfsigned Issuer, in order to create a root CA certificate for
|
||||||
|
# signing webhook serving certificates
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Issuer
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.selfSignedIssuer" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
selfSigned: {}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Generate a CA Certificate used to sign certificates for the webhook
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.rootCACertificate" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
secretName: {{ include "example-webhook.rootCACertificate" . }}
|
||||||
|
duration: 43800h # 5y
|
||||||
|
issuerRef:
|
||||||
|
name: {{ include "example-webhook.selfSignedIssuer" . }}
|
||||||
|
commonName: "ca.example-webhook.cert-manager"
|
||||||
|
isCA: true
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create an Issuer that uses the above generated CA certificate to issue certs
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Issuer
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.rootCAIssuer" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
ca:
|
||||||
|
secretName: {{ include "example-webhook.rootCACertificate" . }}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Finally, generate a serving certificate for the webhook to use
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.servingCertificate" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
secretName: {{ include "example-webhook.servingCertificate" . }}
|
||||||
|
duration: 8760h # 1y
|
||||||
|
issuerRef:
|
||||||
|
name: {{ include "example-webhook.rootCAIssuer" . }}
|
||||||
|
dnsNames:
|
||||||
|
- {{ include "example-webhook.fullname" . }}
|
||||||
|
- {{ include "example-webhook.fullname" . }}.{{ .Release.Namespace }}
|
||||||
|
- {{ include "example-webhook.fullname" . }}.{{ .Release.Namespace }}.svc
|
||||||
91
deploy/example-webhook/templates/rbac.yaml
Normal file
91
deploy/example-webhook/templates/rbac.yaml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
---
|
||||||
|
# Grant the webhook permission to read the ConfigMap containing the Kubernetes
|
||||||
|
# apiserver's requestheader-ca-certificate.
|
||||||
|
# This ConfigMap is automatically created by the Kubernetes apiserver.
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}:webhook-authentication-reader
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: extension-apiserver-authentication-reader
|
||||||
|
subjects:
|
||||||
|
- apiGroup: ""
|
||||||
|
kind: ServiceAccount
|
||||||
|
name: {{ include "example-webhook.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
---
|
||||||
|
# apiserver gets the auth-delegator role to delegate auth decisions to
|
||||||
|
# the core apiserver
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}:auth-delegator
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: system:auth-delegator
|
||||||
|
subjects:
|
||||||
|
- apiGroup: ""
|
||||||
|
kind: ServiceAccount
|
||||||
|
name: {{ include "example-webhook.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
---
|
||||||
|
# Grant cert-manager permission to validate using our apiserver
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}:domain-solver
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- {{ .Values.groupName }}
|
||||||
|
resources:
|
||||||
|
- '*'
|
||||||
|
verbs:
|
||||||
|
- 'create'
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}:domain-solver
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: {{ include "example-webhook.fullname" . }}:domain-solver
|
||||||
|
subjects:
|
||||||
|
- apiGroup: ""
|
||||||
|
kind: ServiceAccount
|
||||||
|
name: {{ .Values.certManager.serviceAccountName }}
|
||||||
|
namespace: {{ .Values.certManager.namespace }}
|
||||||
20
deploy/example-webhook/templates/service.yaml
Normal file
20
deploy/example-webhook/templates/service.yaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "example-webhook.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace | quote }}
|
||||||
|
labels:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
chart: {{ include "example-webhook.chart" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
|
heritage: {{ .Release.Service }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: https
|
||||||
|
protocol: TCP
|
||||||
|
name: https
|
||||||
|
selector:
|
||||||
|
app: {{ include "example-webhook.name" . }}
|
||||||
|
release: {{ .Release.Name }}
|
||||||
43
deploy/example-webhook/values.yaml
Normal file
43
deploy/example-webhook/values.yaml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# The GroupName here is used to identify your company or business unit that
|
||||||
|
# created this webhook.
|
||||||
|
# For example, this may be "acme.mycompany.com".
|
||||||
|
# This name will need to be referenced in each Issuer's `webhook` stanza to
|
||||||
|
# inform cert-manager of where to send ChallengePayload resources in order to
|
||||||
|
# solve the DNS01 challenge.
|
||||||
|
# This group name should be **unique**, hence using your own company's domain
|
||||||
|
# here is recommended.
|
||||||
|
groupName: acme.mycompany.com
|
||||||
|
|
||||||
|
certManager:
|
||||||
|
namespace: cert-manager
|
||||||
|
serviceAccountName: cert-manager
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: mycompany/webhook-image
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
nameOverride: ""
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 443
|
||||||
|
|
||||||
|
resources: {}
|
||||||
|
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||||
|
# choice for the user. This also increases chances charts run on environments with little
|
||||||
|
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||||
|
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||||
|
# limits:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
# requests:
|
||||||
|
# cpu: 100m
|
||||||
|
# memory: 128Mi
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
113
go.mod
Normal file
113
go.mod
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
module github.com/cert-manager/webhook-example
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cert-manager/cert-manager v1.12.6
|
||||||
|
github.com/miekg/dns v1.1.50
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
k8s.io/apiextensions-apiserver v0.27.2
|
||||||
|
k8s.io/client-go v0.27.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||||
|
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||||
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/coreos/go-semver v0.3.0 // indirect
|
||||||
|
github.com/coreos/go-systemd/v22 v22.4.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
|
||||||
|
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
|
||||||
|
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
|
github.com/go-logr/logr v1.2.4 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-logr/zapr v1.2.4 // indirect
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
|
github.com/go-openapi/jsonreference v0.20.1 // indirect
|
||||||
|
github.com/go-openapi/swag v0.22.3 // indirect
|
||||||
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/google/cel-go v0.12.6 // indirect
|
||||||
|
github.com/google/gnostic v0.6.9 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.12 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.15.1 // indirect
|
||||||
|
github.com/prometheus/client_model v0.4.0 // indirect
|
||||||
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.9.0 // indirect
|
||||||
|
github.com/spf13/cobra v1.7.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stoewer/go-strcase v1.2.0 // indirect
|
||||||
|
go.etcd.io/etcd/api/v3 v3.5.7 // indirect
|
||||||
|
go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
|
||||||
|
go.etcd.io/etcd/client/v3 v3.5.7 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0 // indirect
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
|
golang.org/x/mod v0.10.0 // indirect
|
||||||
|
golang.org/x/net v0.18.0 // indirect
|
||||||
|
golang.org/x/oauth2 v0.10.0 // indirect
|
||||||
|
golang.org/x/sync v0.3.0 // indirect
|
||||||
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
|
golang.org/x/term v0.15.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
golang.org/x/time v0.3.0 // indirect
|
||||||
|
golang.org/x/tools v0.9.1 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
|
||||||
|
google.golang.org/grpc v1.58.3 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
k8s.io/api v0.27.2 // indirect
|
||||||
|
k8s.io/apimachinery v0.27.2 // indirect
|
||||||
|
k8s.io/apiserver v0.27.2 // indirect
|
||||||
|
k8s.io/component-base v0.27.2 // indirect
|
||||||
|
k8s.io/klog/v2 v2.100.1 // indirect
|
||||||
|
k8s.io/kms v0.27.2 // indirect
|
||||||
|
k8s.io/kube-aggregator v0.27.2 // indirect
|
||||||
|
k8s.io/kube-openapi v0.0.0-20230515203736-54b630e78af5 // indirect
|
||||||
|
k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
|
||||||
|
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 // indirect
|
||||||
|
sigs.k8s.io/controller-runtime v0.15.0 // indirect
|
||||||
|
sigs.k8s.io/gateway-api v0.7.0 // indirect
|
||||||
|
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||||
|
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||||
|
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||||
|
)
|
||||||
22
main.go
Normal file
22
main.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/jetstack/cert-manager/pkg/acme/webhook/cmd"
|
||||||
|
|
||||||
|
"github.com/stuurmcp/cert-manager-webhook-sthome/pkg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GroupName is the name under which the webhook will be available
|
||||||
|
var GroupName = os.Getenv("GROUP_NAME")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if GroupName == "" {
|
||||||
|
panic("GROUP_NAME must be specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.RunWebhookServer(GroupName,
|
||||||
|
&dns.ProviderSolver{},
|
||||||
|
)
|
||||||
|
}
|
||||||
41
main_test.go
Normal file
41
main_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
acmetest "github.com/cert-manager/cert-manager/test/acme"
|
||||||
|
|
||||||
|
"github.com/cert-manager/webhook-sthome/sthome"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
zone = os.Getenv("TEST_ZONE_NAME")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunsSuite(t *testing.T) {
|
||||||
|
// The manifest path should contain a file named config.json that is a
|
||||||
|
// snippet of valid configuration that should be included on the
|
||||||
|
// ChallengeRequest passed as part of the test cases.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Uncomment the below fixture when implementing your custom DNS provider
|
||||||
|
//fixture := acmetest.NewFixture(&customDNSProviderSolver{},
|
||||||
|
// acmetest.SetResolvedZone(zone),
|
||||||
|
// acmetest.SetAllowAmbientCredentials(false),
|
||||||
|
// acmetest.SetManifestPath("testdata/my-custom-solver"),
|
||||||
|
// acmetest.SetBinariesPath("_test/kubebuilder/bin"),
|
||||||
|
//)
|
||||||
|
solver := sthome.New("59351")
|
||||||
|
fixture := acmetest.NewFixture(solver,
|
||||||
|
acmetest.SetResolvedZone("sthome.net."),
|
||||||
|
acmetest.SetManifestPath("testdata/my-custom-solver"),
|
||||||
|
acmetest.SetDNSServer("127.0.0.1:59351"),
|
||||||
|
acmetest.SetUseAuthoritative(false),
|
||||||
|
)
|
||||||
|
//need to uncomment and RunConformance delete runBasic and runExtended once https://github.com/cert-manager/cert-manager/pull/4835 is merged
|
||||||
|
//fixture.RunConformance(t)
|
||||||
|
fixture.RunBasic(t)
|
||||||
|
fixture.RunExtended(t)
|
||||||
|
|
||||||
|
}
|
||||||
69
pkg/dns/dns.go
Normal file
69
pkg/dns/dns.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e *sthomeSolver) handleDNSRequest(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.SetReply(req)
|
||||||
|
switch req.Opcode {
|
||||||
|
case dns.OpcodeQuery:
|
||||||
|
for _, q := range msg.Question {
|
||||||
|
if err := e.addDNSAnswer(q, msg, req); err != nil {
|
||||||
|
msg.SetRcode(req, dns.RcodeServerFailure)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteMsg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *sthomeSolver) addDNSAnswer(q dns.Question, msg *dns.Msg, req *dns.Msg) error {
|
||||||
|
switch q.Qtype {
|
||||||
|
// Always return loopback for any A query
|
||||||
|
case dns.TypeA:
|
||||||
|
rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN A 127.0.0.1", q.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Answer = append(msg.Answer, rr)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// TXT records are the only important record for ACME dns-01 challenges
|
||||||
|
case dns.TypeTXT:
|
||||||
|
e.RLock()
|
||||||
|
record, found := e.txtRecords[q.Name]
|
||||||
|
e.RUnlock()
|
||||||
|
if !found {
|
||||||
|
msg.SetRcode(req, dns.RcodeNameError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN TXT %s", q.Name, record))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Answer = append(msg.Answer, rr)
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// NS and SOA are for authoritative lookups, return obviously invalid data
|
||||||
|
case dns.TypeNS:
|
||||||
|
rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN NS ns.example-acme-webook.invalid.", q.Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Answer = append(msg.Answer, rr)
|
||||||
|
return nil
|
||||||
|
case dns.TypeSOA:
|
||||||
|
rr, err := dns.NewRR(fmt.Sprintf("%s 5 IN SOA %s 20 5 5 5 5", "ns.example-acme-webook.invalid.", "ns.example-acme-webook.invalid."))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
msg.Answer = append(msg.Answer, rr)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unimplemented record type %v", q.Qtype)
|
||||||
|
}
|
||||||
|
}
|
||||||
130
pkg/dns/solver.go
Normal file
130
pkg/dns/solver.go
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
v1alpha1 "github.com/cert-manager/cert-manager/tree/master/pkg/acme/webhook/apis/acme/v1alpha1" //"github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
providerName = "sthome"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProviderSolver is the struct implementing the webhook.Solver interface
|
||||||
|
// for sthome DNS
|
||||||
|
type ProviderSolver struct {
|
||||||
|
client kubernetes.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name is used as the name for this DNS solver when referencing it on the ACME
|
||||||
|
// Issuer resource
|
||||||
|
func (p *ProviderSolver) Name() string {
|
||||||
|
return providerName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present is responsible for actually presenting the DNS record with the
|
||||||
|
// DNS provider.
|
||||||
|
// This method should tolerate being called multiple times with the same value.
|
||||||
|
// cert-manager itself will later perform a self check to ensure that the
|
||||||
|
// solver has correctly configured the DNS provider.
|
||||||
|
func (p *ProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
|
||||||
|
domainAPI, err := p.getDomainAPI(ch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &domain.UpdateDNSZoneRecordsRequest{
|
||||||
|
DNSZone: strings.TrimSuffix(ch.ResolvedZone, "."),
|
||||||
|
Changes: []*domain.RecordChange{
|
||||||
|
{
|
||||||
|
Set: &domain.RecordChangeSet{
|
||||||
|
IDFields: &domain.RecordIdentifier{
|
||||||
|
Name: strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), "."),
|
||||||
|
Type: domain.RecordTypeTXT,
|
||||||
|
Data: StringPtr(strconv.Quote(ch.Key)),
|
||||||
|
},
|
||||||
|
Records: []*domain.Record{
|
||||||
|
{
|
||||||
|
Name: strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), "."),
|
||||||
|
Data: strconv.Quote(ch.Key),
|
||||||
|
Type: domain.RecordTypeTXT,
|
||||||
|
TTL: 60,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = domainAPI.UpdateDNSZoneRecords(request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update DNS zone records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp should delete the relevant TXT record from the DNS provider console.
|
||||||
|
// If multiple TXT records exist with the same record name (e.g.
|
||||||
|
// _acme-challenge.example.com) then **only** the record with the same `key`
|
||||||
|
// value provided on the ChallengeRequest should be cleaned up.
|
||||||
|
// This is in order to facilitate multiple DNS validations for the same domain
|
||||||
|
// concurrently.
|
||||||
|
func (p *ProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
|
||||||
|
domainAPI, err := p.getDomainAPI(ch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &domain.UpdateDNSZoneRecordsRequest{
|
||||||
|
DNSZone: strings.TrimSuffix(ch.ResolvedZone, "."),
|
||||||
|
Changes: []*domain.RecordChange{
|
||||||
|
{
|
||||||
|
Delete: &domain.RecordChangeDelete{
|
||||||
|
IDFields: &domain.RecordIdentifier{
|
||||||
|
Name: strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), "."),
|
||||||
|
Data: StringPtr(strconv.Quote(ch.Key)),
|
||||||
|
Type: domain.RecordTypeTXT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = domainAPI.UpdateDNSZoneRecords(request)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update DNS zone records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize will be called when the webhook first starts.
|
||||||
|
// This method can be used to instantiate the webhook, i.e. initialising
|
||||||
|
// connections or warming up caches.
|
||||||
|
// Typically, the kubeClientConfig parameter is used to build a Kubernetes
|
||||||
|
// client that can be used to fetch resources from the Kubernetes API, e.g.
|
||||||
|
// Secret resources containing credentials used to authenticate with DNS
|
||||||
|
// provider accounts.
|
||||||
|
// The stopCh can be used to handle early termination of the webhook, in cases
|
||||||
|
// where a SIGTERM or similar signal is sent to the webhook process.
|
||||||
|
func (p *ProviderSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
|
||||||
|
|
||||||
|
cl, err := kubernetes.NewForConfig(kubeClientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get kubernetes client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p.client = cl
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringPtr returns a pointer to the string value provided
|
||||||
|
func StringPtr(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
68
pkg/dns/sthome.go
Normal file
68
pkg/dns/sthome.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// package example contains a self-contained example of a webhook that passes the cert-manager
|
||||||
|
// DNS conformance tests
|
||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/cert-manager/cert-manager/pkg/acme/webhook"
|
||||||
|
acme "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sthomeSolver struct {
|
||||||
|
name string
|
||||||
|
server *dns.Server
|
||||||
|
txtRecords map[string]string
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *sthomeSolver) Name() string {
|
||||||
|
return e.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *sthomeSolver) Present(ch *acme.ChallengeRequest) error {
|
||||||
|
e.Lock()
|
||||||
|
e.txtRecords[ch.ResolvedFQDN] = ch.Key
|
||||||
|
e.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *sthomeSolver) CleanUp(ch *acme.ChallengeRequest) error {
|
||||||
|
e.Lock()
|
||||||
|
delete(e.txtRecords, ch.ResolvedFQDN)
|
||||||
|
e.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *sthomeSolver) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
|
||||||
|
go func(done <-chan struct{}) {
|
||||||
|
<-done
|
||||||
|
if err := e.server.Shutdown(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
}
|
||||||
|
}(stopCh)
|
||||||
|
go func() {
|
||||||
|
if err := e.server.ListenAndServe(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(port string) webhook.Solver {
|
||||||
|
e := &sthomeSolver{
|
||||||
|
name: "example",
|
||||||
|
txtRecords: make(map[string]string),
|
||||||
|
}
|
||||||
|
e.server = &dns.Server{
|
||||||
|
Addr: ":" + port,
|
||||||
|
Net: "udp",
|
||||||
|
Handler: dns.HandlerFunc(e.handleDNSRequest),
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
96
pkg/dns/sthome_test.go
Normal file
96
pkg/dns/sthome_test.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package dns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
acme "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExampleSolver_Name(t *testing.T) {
|
||||||
|
port, _ := rand.Int(rand.Reader, big.NewInt(50000))
|
||||||
|
port = port.Add(port, big.NewInt(15534))
|
||||||
|
solver := New(port.String())
|
||||||
|
assert.Equal(t, "example", solver.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleSolver_Initialize(t *testing.T) {
|
||||||
|
port, _ := rand.Int(rand.Reader, big.NewInt(50000))
|
||||||
|
port = port.Add(port, big.NewInt(15534))
|
||||||
|
solver := New(port.String())
|
||||||
|
done := make(chan struct{})
|
||||||
|
err := solver.Initialize(nil, done)
|
||||||
|
assert.NoError(t, err, "Expected Initialize not to error")
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExampleSolver_Present_Cleanup(t *testing.T) {
|
||||||
|
port, _ := rand.Int(rand.Reader, big.NewInt(50000))
|
||||||
|
port = port.Add(port, big.NewInt(15534))
|
||||||
|
solver := New(port.String())
|
||||||
|
done := make(chan struct{})
|
||||||
|
err := solver.Initialize(nil, done)
|
||||||
|
assert.NoError(t, err, "Expected Initialize not to error")
|
||||||
|
|
||||||
|
validTestData := []struct {
|
||||||
|
hostname string
|
||||||
|
record string
|
||||||
|
}{
|
||||||
|
{"test1.example.com.", "testkey1"},
|
||||||
|
{"test2.example.com.", "testkey2"},
|
||||||
|
{"test3.example.com.", "testkey3"},
|
||||||
|
}
|
||||||
|
for _, test := range validTestData {
|
||||||
|
err := solver.Present(&acme.ChallengeRequest{
|
||||||
|
Action: acme.ChallengeActionPresent,
|
||||||
|
Type: "dns-01",
|
||||||
|
ResolvedFQDN: test.hostname,
|
||||||
|
Key: test.record,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "Unexpected error while presenting %v", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve test data
|
||||||
|
for _, test := range validTestData {
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.Id = dns.Id()
|
||||||
|
msg.RecursionDesired = true
|
||||||
|
msg.Question = make([]dns.Question, 1)
|
||||||
|
msg.Question[0] = dns.Question{dns.Fqdn(test.hostname), dns.TypeTXT, dns.ClassINET}
|
||||||
|
in, err := dns.Exchange(msg, "127.0.0.1:"+port.String())
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Presented record %s not resolvable", test.hostname)
|
||||||
|
assert.Len(t, in.Answer, 1, "RR response is of incorrect length")
|
||||||
|
assert.Equal(t, []string{test.record}, in.Answer[0].(*dns.TXT).Txt, "TXT record returned did not match presented record")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup test data
|
||||||
|
for _, test := range validTestData {
|
||||||
|
err := solver.CleanUp(&acme.ChallengeRequest{
|
||||||
|
Action: acme.ChallengeActionCleanUp,
|
||||||
|
Type: "dns-01",
|
||||||
|
ResolvedFQDN: test.hostname,
|
||||||
|
Key: test.record,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err, "Unexpected error while cleaning up %v", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve test data
|
||||||
|
for _, test := range validTestData {
|
||||||
|
msg := new(dns.Msg)
|
||||||
|
msg.Id = dns.Id()
|
||||||
|
msg.RecursionDesired = true
|
||||||
|
msg.Question = make([]dns.Question, 1)
|
||||||
|
msg.Question[0] = dns.Question{dns.Fqdn(test.hostname), dns.TypeTXT, dns.ClassINET}
|
||||||
|
in, err := dns.Exchange(msg, "127.0.0.1:"+port.String())
|
||||||
|
|
||||||
|
assert.NoError(t, err, "Presented record %s not resolvable", test.hostname)
|
||||||
|
assert.Len(t, in.Answer, 0, "RR response is of incorrect length")
|
||||||
|
assert.Equal(t, dns.RcodeNameError, in.Rcode, "Expexted NXDOMAIN")
|
||||||
|
}
|
||||||
|
|
||||||
|
close(done)
|
||||||
|
}
|
||||||
35
pkg/util/version.go
Normal file
35
pkg/util/version.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are set during build time via -ldflags
|
||||||
|
var (
|
||||||
|
version = "0.0.1+dev"
|
||||||
|
gitCommit string
|
||||||
|
buildDate string
|
||||||
|
)
|
||||||
|
|
||||||
|
// VersionInfo represents the current running version
|
||||||
|
type VersionInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
GitCommit string `json:"gitCommit"`
|
||||||
|
BuildDate string `json:"buildDate"`
|
||||||
|
GoVersion string `json:"goVersion"`
|
||||||
|
Compiler string `json:"compiler"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns the current running version
|
||||||
|
func GetVersion() VersionInfo {
|
||||||
|
return VersionInfo{
|
||||||
|
Version: version,
|
||||||
|
GitCommit: gitCommit,
|
||||||
|
BuildDate: buildDate,
|
||||||
|
GoVersion: runtime.Version(),
|
||||||
|
Compiler: runtime.Compiler,
|
||||||
|
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
|
||||||
|
}
|
||||||
|
}
|
||||||
3
testdata/my-custom-solver/README.md
vendored
Normal file
3
testdata/my-custom-solver/README.md
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Solver testdata directory
|
||||||
|
|
||||||
|
TODO
|
||||||
1
testdata/my-custom-solver/config.json
vendored
Normal file
1
testdata/my-custom-solver/config.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
Loading…
Reference in New Issue
Block a user