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