Compare commits

..

No commits in common. "2614cd2e900a23946a93d530a53dbc0243379443" and "0d44d8937e5a2112b1993595cd456bfd82e8a07d" have entirely different histories.

20 changed files with 239 additions and 641 deletions

View File

@ -1,6 +0,0 @@
{
"terminal.explorerKind": "integrated",
"makefile.makePath": "C:\\MinGW\\msys\\1.0\\bin\\make.exe",
"makefile.makefilePath": "./Makefile",
"makefile.buildLog": "./makefile.log"
}

View File

@ -1,4 +1,4 @@
FROM golang:1.21-alpine3.19 AS build_deps
FROM golang:1.21-alpine3.18 AS build_deps
# Let scripts know we're running in Docker (useful for containerised development)
ENV RUNNING_IN_DOCKER true
@ -22,26 +22,23 @@ COPY . .
RUN CGO_ENABLED=0 go build -o webhook -ldflags '-w -extldflags "-static"' .
FROM alpine:3.19
FROM alpine:3.18
RUN apk add --no-cache ca-certificates
RUN apk add --no-cache zsh bash bind-tools coreutils krb5 tcpdump
COPY ./config/zsh.sh /root/.zshrc
RUN apk add --no-cache bash bind-tools coreutils krb5 tcpdump
COPY ./config/bash.sh /root/.bashrc
RUN chown -R root:root /root/.zshrc && \
chown -R root:root /root/.bashrc && \
/bin/zsh /root/.zshrc
#COPY ./config/krb5.conf /etc
RUN chown -R root:root /root/.bashrc && \
/bin/bash /root/.bashrc
RUN apk add --no-cache alpine-conf && \
setup-timezone -z Africa/Johannesburg
# chmod 0644 /etc/krb5.conf
ENV RUNNING_IN_DOCKER true
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ="Africa/Johannesburg"
ENV WEBROOT=/webroot
ENV ACMEDIR=/acme
ENV WORKDIR=/workdir
COPY --from=build /workspace/webhook /usr/local/bin/webhook

View File

@ -1,15 +1,9 @@
GO ?= "$(shell which go)"
GO ?= $(shell which go)
OS ?= $(shell $(GO) env GOOS)
ARCH ?= $(shell $(GO) env GOARCH)
IMAGE_NAME := cert-manager-webhook-sthome
IMAGE_SHORTNAME := sthome-webhook
IMAGE_NAMESPACE := ix-$(IMAGE_SHORTNAME)2
DEPLOY_DIR := deploy/$(IMAGE_SHORTNAME)
IMAGE_TAG = "0.0.3"
SHARED_DIR := //truenas.sthome.net/Shared_data/Chris/clusterissuer/charts
CHART_FILE := $(SHARED_DIR)/$(IMAGE_NAME)-
IMAGE_TAG = "0.0.3"
TMP = \Temp\gotemp
export TMP
@ -18,7 +12,7 @@ OUT := $(shell pwd)/_out
KUBEBUILDER_VERSION=1.28.0
HELM_FILES := $(shell find "$(DEPLOY_DIR)")
HELM_FILES := $(shell find deploy/sthome-webhook)
dependencies:
go mod tidy
@ -29,6 +23,11 @@ bin/buildversion.exe: cmd/buildversion.go
go build -o bin/buildversion.exe cmd/buildversion.go
set TMP="C:\Users\Chris\AppData\Local\Temp"
#bin/cert-manager-webhook-sthome.exe: dependencies version.txt
# set TMP="C:\Temp\gotemp"
# go build -o bin/cert-manager-webhook-sthome.exe
# set TMP="C:\Users\Chris\AppData\Local\Temp"
version.txt: bin/buildversion.exe dependencies
bin/buildversion.exe
@ -49,23 +48,21 @@ clean:
rm -r _test $(OUT)
.PHONY: build
build: dependencies bin/buildversion.exe version.txt
build: rendered-manifest.yaml dependencies bin/buildversion.exe version.txt
docker build --pull --rm -f "Dockerfile" -t "stuurmcp/$(IMAGE_NAME):latest" -t "stuurmcp/$(IMAGE_NAME):$(shell head -n 1 version.txt)" "."
docker image push "docker.io/stuurmcp/$(IMAGE_NAME):$(shell head -n 1 version.txt)"
helm package -n $(IMAGE_NAMESPACE) --version $(shell head -n 1 version.txt) $(DEPLOY_DIR) -d $(SHARED_DIR)/
docker image push "docker.io/stuurmcp/$(IMAGE_NAME):latest"
.PHONY: chart
chart: $(CHART_FILE).tgz
$(CHART_FILE).tgz: $(HELM_FILES)
helm package -n $(IMAGE_NAMESPACE) --version $(shell head -n 1 version.txt) $(DEPLOY_DIR) -d $(SHARED_DIR)/
helm package deploy/sthome-webhook -d //truenas/Shared_data/Chris/clusterissuer/charts/
.PHONY: rendered-manifest.yaml
rendered-manifest.yaml: $(OUT)/rendered-manifest.yaml
$(OUT)/rendered-manifest.yaml: $(HELM_FILES) | $(OUT)
helm template $(IMAGE_SHORTNAME) -n $(IMAGE_NAMESPACE) --set image.repository=$(IMAGE_NAME) --set image.tag=$(shell head -n 1 version.txt) $(DEPLOY_DIR) > $@
helm template \
sthome-webhook -n ix-cert-manager\
--set image.repository=$(IMAGE_NAME) \
--set image.tag=$(shell head -n 1 version.txt) \
deploy/sthome-webhook > $@
_test $(OUT) _test/kubebuilder-$(KUBEBUILDER_VERSION)-$(OS)-$(ARCH):
mkdir -p $@

View File

@ -19,7 +19,6 @@ alias ll='ls -laFo'
alias l='ls -l'
alias g='egrep -i'
alias dnsupd=/acme/updatedns.sh
alias acmesh=/acme/acmeauth.sh
#
# Some more alias to avoid making mistakes:
# alias rm='rm -i'

View File

@ -26,7 +26,6 @@ alias ll='ls -laFo'
alias l='ls -l'
alias g='egrep -i'
alias dnsupd=/acme/updatedns.sh
alias acmesh=/acme/acmeauth.sh
# # be paranoid
# alias cp='cp -ip'

View File

@ -1,5 +1,5 @@
apiVersion: v1
appVersion: v0.0.5-alpha.113
appVersion: v0.0.5-alpha.24
description: Cert-Manager webhook for sthome
name: sthome-webhook
version: 0.0.5-alpha.113
version: 0.0.5-alpha.24

View File

@ -31,13 +31,6 @@ spec:
- --tls-cert-file=/tls/tls.crt
- --tls-private-key-file=/tls/tls.key
- --secure-port=8443
{{- range $key, $value := .Values.extraArgs }}
{{- if $value }}
- --{{ $key }}={{ $value }}
{{- else }}
- --{{ $key }}
{{- end }}
{{- end }}
env:
- name: GROUP_NAME
value: {{ .Values.groupName | quote }}
@ -62,6 +55,9 @@ spec:
- name: scriptdir
mountPath: /acme
readOnly: false
- name: workdir
mountPath: /workdir
readOnly: false
- name: webroot
mountPath: /webroot
readOnly: false
@ -74,6 +70,9 @@ spec:
- name: scriptdir
hostPath:
path: {{ .Values.host.scriptdir }}
- name: workdir
hostPath:
path: {{ .Values.host.workdir }}
- name: webroot
hostPath:
path: {{ .Values.host.webrootdir }}
@ -88,13 +87,5 @@ spec:
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.dnsPolicy }}
dnsPolicy:
{{ toYaml . | indent 8 }}
{{- end }}
{{- with .Values.dnsConfig }}
dnsConfig:
{{ toYaml . | indent 8 }}
{{- end }}

View File

@ -31,7 +31,7 @@ clusterIssuer:
image:
repository: stuurmcp/cert-manager-webhook-sthome
#repository: wstat.sthome.net:5000/cert-manager-webhook-sthome
tag: 0.0.5-alpha.113
tag: 0.0.5-alpha.24
#pullPolicy should be IfNotPresent. Set to Always for testing purposes
pullPolicy: IfNotPresent
@ -41,24 +41,6 @@ imageCredentials:
nameOverride: ""
fullnameOverride: ""
dnsPolicy: None
dnsConfig:
nameservers:
- 172.17.0.10
- 10.0.0.15
- 192.168.2.1
searches:
- ix-sthome-webhook2.svc.cluster.local
- svc.cluster.local
- cluster.local
- sthome.lan
- sthome.net
options:
- name: ndots
value: "5"
replicaCount: 1
pki:
@ -66,11 +48,10 @@ pki:
servingCertificateDuration: 8760h # 1y
host:
workdir: /mnt/stpool1/scripts/acme/cert-manager-webhook-sthome
scriptdir: /mnt/stpool1/scripts/acme
webrootdir: /mnt/stpool1/apps/static-web-server
extraArgs:
secret:
accessKey: ""
secretKey: ""

5
go.mod
View File

@ -37,8 +37,6 @@ require (
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
@ -49,8 +47,6 @@ require (
golang.org/x/mod v0.14.0 // indirect
google.golang.org/api v0.154.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/jcmturner/goidentity.v3 v3.0.0 // indirect
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 // indirect
)
require (
@ -86,7 +82,6 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jcmturner/gokrb5 v8.4.4+incompatible
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

10
go.sum
View File

@ -171,16 +171,10 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/gokrb5 v8.4.4+incompatible h1:aX4yX9Lwq0U7yurW6pzRH5JJYDwK0hWIPBTTWfWBOLQ=
github.com/jcmturner/gokrb5 v8.4.4+incompatible/go.mod h1:0Q5eFyVvYsEsZ8xl1A/jUqhXvxUp/X9ELrJm+zieq5E=
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@ -448,10 +442,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

91
main.go
View File

@ -5,6 +5,14 @@ import (
"os"
"strings"
/*
"fmt"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/klog/v2"
"github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
"github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util"
*/
"github.com/cert-manager/cert-manager/pkg/acme/webhook/cmd"
dns "github.com/stuurmcp/cert-manager-webhook-sthome/pkg/dns"
)
@ -24,5 +32,88 @@ func main() {
}
cmd.RunWebhookServer(GroupName,
&dns.LocalDNSProviderSolver{},
//&dns.SthomeSolver{},
)
}
/*
type LocalDNSProviderSolver struct {
client kubernetes.Interface
}
func (p *LocalDNSProviderSolver) Name() string {
return dns.ProviderName + "loc"
}
func (loc *LocalDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
cfg, err := dns.LoadConfig(ch.Config)
if err != nil {
return err
}
klog.Infof("CZ: Presenting record for %s, type: %s, uid: %s, key: %s, ns: %s, fqdn: %s, zone: %s, allowambcred: %t, cfg.secret: %s, cfg.email: %s, cfg.allowz: %s",
ch.DNSName,
ch.UID,
ch.Type,
ch.Key,
ch.ResourceNamespace,
ch.ResolvedFQDN,
ch.ResolvedZone,
ch.AllowAmbientCredentials,
cfg.APIKeySecretRef.Name,
cfg.Email,
strings.Join(cfg.AllowedZones, ","),
)
// TODO: convert shell script to golang
localip := dns.GetOutboundIP(dns.Dnsserver_net)
success, _ := dns.Execute(
dns.Shell,
dns.AcmeAuthCmd,
"set",
ch.DNSName,
ch.ResolvedFQDN,
ch.Key,
"-l",
localip,
"-v",
)
klog.Infof("Execute set TXT returned success: %t", success)
return nil
}
func (loc *LocalDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
//domainName := extractDomainName(ch.ResolvedZone)
// TODO: add code that deletes a record from the DNS provider's console
localip := dns.GetOutboundIP(dns.Dnsserver_net)
success, _ := dns.Execute(
dns.Shell,
dns.AcmeAuthCmd,
"unset",
ch.DNSName,
ch.ResolvedFQDN,
ch.Key,
"-l",
localip,
"-v",
)
klog.Infof("Execute unset TXT returned success: %t", success)
return nil
}
func (loc *LocalDNSProviderSolver) 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)
}
loc.client = cl
klog.InfoS("CZ: Successfully initialised kubernetes client!")
return nil
}
func extractDomainName(zone string) string {
authZone, err := util.FindZoneByFqdn(zone, util.RecursiveNameservers)
if err != nil {
klog.Errorf("could not get zone by fqdn %v", err)
return zone
}
return util.UnFqdn(authZone)
}
*/

View File

@ -1,126 +0,0 @@
package auth
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"sync"
cred "github.com/jcmturner/gokrb5/credentials"
)
const (
kinitCmd = "kinit"
kvnoCmd = "kvno"
klistCmd = "klist"
spn = "DNS/upd.sthome.net@STHOME.LAN"
)
type output struct {
buf *bytes.Buffer
lines []string
*sync.Mutex
}
func newOutput() *output {
return &output{
buf: &bytes.Buffer{},
lines: []string{},
Mutex: &sync.Mutex{},
}
}
func (rw *output) Write(p []byte) (int, error) {
rw.Lock()
defer rw.Unlock()
return rw.buf.Write(p)
}
func (rw *output) Lines() []string {
rw.Lock()
defer rw.Unlock()
s := bufio.NewScanner(rw.buf)
for s.Scan() {
rw.lines = append(rw.lines, s.Text())
}
return rw.lines
}
func login() error {
file, err := os.Create("/etc/krb5.conf")
if err != nil {
return fmt.Errorf("cannot open krb5.conf: %v", err)
}
defer file.Close()
fmt.Fprintf(file, krb5conf)
cmd := exec.Command(kinitCmd, dnsupdateuser)
stdinR, stdinW := io.Pipe()
stderrR, stderrW := io.Pipe()
cmd.Stdin = stdinR
cmd.Stderr = stderrW
err = cmd.Start()
if err != nil {
return fmt.Errorf("could not start %s command: %v", kinitCmd, err)
}
go func() {
io.WriteString(stdinW, "passwordvalue")
stdinW.Close()
}()
errBuf := new(bytes.Buffer)
go func() {
io.Copy(errBuf, stderrR)
stderrR.Close()
}()
err = cmd.Wait()
if err != nil {
return fmt.Errorf("%s did not run successfully: %v stderr: %s", kinitCmd, err, string(errBuf.Bytes()))
}
return nil
}
func getServiceTkt() error {
cmd := exec.Command(kvnoCmd, spn)
err := cmd.Start()
if err != nil {
return fmt.Errorf("could not start %s command: %v", kvnoCmd, err)
}
err = cmd.Wait()
if err != nil {
return fmt.Errorf("%s did not run successfully: %v", kvnoCmd, err)
}
return nil
}
func klist() ([]string, error) {
cmd := exec.Command(klistCmd, "-Aef")
stdout := newOutput()
cmd.Stdout = stdout
err := cmd.Start()
if err != nil {
return []string{}, fmt.Errorf("could not start %s command: %v", klistCmd, err)
}
err = cmd.Wait()
if err != nil {
return []string{}, fmt.Errorf("%s did not run successfully: %v", klistCmd, err)
}
return stdout.Lines(), nil
}
func loadCCache() (*cred.CCache, error) {
usr, _ := user.Current()
cpath := "/tmp/krb5cc_" + usr.Uid
return cred.LoadCCache(cpath)
}

View File

@ -1,43 +0,0 @@
package auth
const (
dnsupdateuser = "dns_updater@STHOME.LAN"
krb5conf = `[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log
[libdefaults]
# specifies the default realm that needs to be picked up for authentication
default_realm = STHOME.LAN
# set following to true, if you're specified dns' instead of IP addresses under [realms]
dns_lookup_realm = false
# specifies whether DNS SRV records should be used to locate the KDCs and other servers for a realm
dns_lookup_kdc = false
# this is a mandatory flag as we need to obtain forwardable tickets from the KDC
forward = true
# specifies if initial tickets will be forwardable by default, if allowed by the KDC
forwardable = true
ticket_lifetime = 24h
renew_lifetime = 7d
rdns = false
default_ccache_name = KEYRING:persistent:%{uid}
pkinit_dh_min_bits = 1024
# specifies that short hostnames should be canonicalized to fully-qualified hostnames
dns_canonicalize_hostname = true
[realms]
# Realm configuration with different possible way to be resolved
STHOME.LAN = {
admin_server = 192.168.2.1:749
kdc = 192.168.2.1:88
kdc = 192.168.2.4:88
}
[domain_realm]
sthome.lan = STHOME.LAN
.sthome.lan = STHOME.LAN
sthome.net = STHOME.LAN
.sthome.net = STHOME.LAN
`
)

View File

@ -9,10 +9,13 @@ import (
)
const (
ProviderName = "sthome"
SthomeAccessKeyEnv = "STHOME_ACCESS_KEY"
SthomeSecretKeyEnv = "STHOME_SECRET_KEY"
ProviderName = "sthome"
Shell = "/bin/bash"
AcmeAuthCmd = "/acme/acmeauth.sh"
Dnsserver_net = "10.0.0.15"
Dnsserver_lan = "192.168.2.1"
Hostserver_net = "truenas.sthome.net"

27
pkg/dns/shell.go Normal file
View File

@ -0,0 +1,27 @@
package dns
import (
"bytes"
"os/exec"
"k8s.io/klog/v2"
)
func Execute(shell string, arg ...string) (bool, error) {
var outb, errb bytes.Buffer
cmd := exec.Command(shell, arg...)
cmd.Stdout = &outb
cmd.Stderr = &errb
err := cmd.Run()
klog.Infof("out:\n%s\n", outb.String())
if err != nil {
klog.Errorf("Script returned error:\nerr:\n")
klog.Errorf("%s\n============\n", err)
return false, err
}
if errb.String() != "" {
klog.Errorf("stderr:\n%s============\n", errb.String())
}
klog.Infof("Script returned success\n")
return true, nil
}

View File

@ -9,7 +9,7 @@ import (
"k8s.io/klog/v2"
"github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
sh "github.com/stuurmcp/cert-manager-webhook-sthome/pkg/shell"
//"github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util"
"github.com/stuurmcp/cert-manager-webhook-sthome/pkg/util"
)
@ -50,16 +50,62 @@ func (loc *LocalDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error
if err != nil {
return err
}
sh.SetupWorkdir()
/*
provider, cfg, err := loc.init(ch.Config, ch.ResourceNamespace)
if err != nil {
return fmt.Errorf("failed initializing sthome provider: %v", err)
}
if !cfg.IsAllowedZone(ch.ResolvedZone) {
return fmt.Errorf("zone %s may not be edited per config (allowed zones are %v)", ch.ResolvedZone, cfg.AllowedZones)
}
ctx := context.Background()
records, err := loc.getExistingRecords(ctx, provider, ch.ResolvedZone, ch.ResolvedFQDN)
if err != nil {
return fmt.Errorf("failed loading existing records for %s in domain %s: %v", ch.ResolvedFQDN, ch.ResolvedZone, err)
}
// Add the record, only if it doesn't exist already
content := quote(ch.Key)
if _, ok := findRecord(records, content); !ok {
disabled := false
records = append(records, sthome.Record{Disabled: &disabled, Content: &content})
}
// TODO: do something more useful with the decoded configuration
klog.Infof("CZ: Presenting record for %s, type: %s, uid: %s, key: %s, ns: %s, fqdn: %s, zone: %s, allowambcred: %t, cfg.secret: %s, cfg.email: %s, cfg.allowz: %s",
ch.DNSName,
ch.UID,
ch.Type,
ch.Key,
ch.ResourceNamespace,
ch.ResolvedFQDN,
ch.ResolvedZone,
ch.AllowAmbientCredentials,
cfg.APIKeySecretRef.Name,
cfg.Email,
strings.Join(cfg.AllowedZones, ","),
)
*/
// TODO: convert shell script to golang
//localip := GetOutboundIP(Dnsserver_net)
success, err := sh.SetChallenge(ch)
klog.Infof("Present: Execute set TXT returned success: %t\n", success)
err2 := loc.Check(ch.DNSName, ch.Key)
if err2 != nil {
klog.Infof("Present: Check prop failed: %s", err2)
err = loc.Check(ch.DNSName, ch.Key)
if err != nil {
klog.Infof("Check for propagation failed: %s", err)
}
return err
success, _ := Execute(
Shell,
AcmeAuthCmd,
"set",
ch.DNSName,
ch.ResolvedFQDN,
ch.Key,
"-l",
"\"\"", //localip,
//"-v",
)
klog.Infof("Execute set TXT returned success: %t\n", success)
return nil
}
// CleanUp should delete the relevant TXT record from the DNS provider console.
@ -71,9 +117,19 @@ func (loc *LocalDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error
func (loc *LocalDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
//domainName := extractDomainName(ch.ResolvedZone)
//localip := GetOutboundIP(Dnsserver_net)
success, err := sh.UnsetChallenge(ch)
success, _ := Execute(
Shell,
AcmeAuthCmd,
"unset",
ch.DNSName,
ch.ResolvedFQDN,
ch.Key,
"-l",
"\"\"", //localip,
//"-v",
)
klog.Infof("Execute unset TXT returned success: %t\n", success)
return err
return nil
}
// Initialize will be called when the webhook first starts.
@ -91,36 +147,32 @@ func (loc *LocalDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, sto
return fmt.Errorf("failed to get kubernetes client: %w", err)
}
loc.client = cl
sh.SetupWorkdir()
klog.Infof("webhook \"%s\" started.", ProviderName)
klog.InfoS("CZ: Successfully initialised kubernetes client!")
return nil
}
// Check verifies that the DNS records for the ACME challenge have propagated.
func (s *LocalDNSProviderSolver) Check(DNSName string, Key string) error {
ttl := 20
klog.Info("waiting DNS record TTL to allow the DNS01 record to propagate for domain")
time.Sleep(time.Second * time.Duration(ttl))
fqdn, err := util.DNS01LookupFQDN(DNSName, false, DNS01Nameservers...)
if err != nil {
return err
}
//klog.Info("checking DNS propagation: ", "dns: ", DNSName, ", fqdn: ", fqdn, ", key: ", Key, ", nameservers: ", DNS01Nameservers)
klog.Infof("checking DNS prop: %s", fqdn)
klog.Info("checking DNS propagation: ", "dns: ", DNSName, ", fqdn: ", fqdn, ", key: ", Key, ", nameservers: ", DNS01Nameservers)
ok, err := util.PreCheckDNS(fqdn, Key, DNS01Nameservers, DNS01CheckAuthoritative)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("DNS record for %q not yet propagated", fqdn)
return fmt.Errorf("DNS record for %q not yet propagated", DNSName)
}
// klog.Info("waiting DNS record TTL to allow the DNS01 record to propagate for domain")
// time.Sleep(time.Second * time.Duration(ttl))
klog.Infof("ACME DNS01 validation record propagated: %s", fqdn)
ttl := 60
klog.Info("waiting DNS record TTL to allow the DNS01 record to propagate for domain", "ttl", ttl, "fqdn", fqdn)
time.Sleep(time.Second * time.Duration(ttl))
klog.Info("ACME DNS01 validation record propagated", "fqdn", fqdn)
return nil
}

View File

@ -1,101 +0,0 @@
package dns
import (
"bytes"
"fmt"
"io"
"os/exec"
"sync"
"github.com/stuurmcp/cert-manager-webhook-sthome/pkg/util"
"k8s.io/klog/v2"
)
const (
nsupdateCmd = "nsupdate"
ttl = "7200"
)
type output struct {
buf *bytes.Buffer
lines []string
*sync.Mutex
}
func AddTxtRecord(nameservers []string, domain string, fulldomain string, recordvalue string) error {
return processTXT("add", nameservers, domain, fulldomain, recordvalue)
}
func DeleteTxtRecord(nameservers []string, domain string, fulldomain string, recordvalue string) error {
return processTXT("delete", nameservers, domain, fulldomain, recordvalue)
}
func processTXT(action string, nameservers []string, domain string, fulldomain string, recordvalue string) error {
for _, ns := range nameservers {
klog.Infof("======= ns: %s =======", ns)
zone := extractDomainName(domain)
nsucmd := " gsstsig\n server " + ns + "\n zone " + zone + "\n class IN\n update " + action + " " + fulldomain + " " + ttl + " TXT " + recordvalue + "\n send\n"
out, err := nsupdate(nsucmd)
if err != nil {
return err
}
for _, line := range out.lines {
klog.Infof(line)
}
}
return nil
}
func newOutput() *output {
return &output{
buf: &bytes.Buffer{},
lines: []string{},
Mutex: &sync.Mutex{},
}
}
func (rw *output) Write(p []byte) (int, error) {
rw.Lock()
defer rw.Unlock()
return rw.buf.Write(p)
}
func nsupdate(nsucmd string) (*output, error) {
klog.Infof("nsupdate\n%s", nsucmd) // for debugging
cmd := exec.Command(nsupdateCmd)
stdout := newOutput()
cmd.Stdout = stdout
stdinR, stdinW := io.Pipe()
stderrR, stderrW := io.Pipe()
cmd.Stdin = stdinR
cmd.Stderr = stderrW
err := cmd.Start()
if err != nil {
return stdout, fmt.Errorf("could not start %s command: %v", nsupdateCmd, err)
}
go func() {
io.WriteString(stdinW, nsucmd)
stdinW.Close()
}()
errBuf := new(bytes.Buffer)
go func() {
io.Copy(errBuf, stderrR)
stderrR.Close()
}()
err = cmd.Wait()
if err != nil {
return stdout, fmt.Errorf("%s did not run successfully: %v stderr: %s", nsupdateCmd, err, errBuf.String())
}
return stdout, nil
}
func extractDomainName(zone string) string {
authZone, err := util.FindZoneByFqdn(zone, util.RecursiveNameservers)
if err != nil {
klog.Errorf("could not get zone by fqdn %v", err)
return zone
}
return util.UnFqdn(authZone)
}

View File

@ -1,227 +0,0 @@
package shell
import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"sync"
"time"
"github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
"k8s.io/klog/v2"
)
const (
bashShell = "/bin/bash"
zshShell = "/bin/zsh"
//Workdir = "/workdir"
//AcmeDir = "/acme"
Shell = bashShell
AuthScript = "acmeauth.sh"
DnsUpdScript = "updatedns.sh"
KrbConf = "krb5.conf"
Keytab = "krb5.keytab"
AuthScriptMode = 0744
DnsUpdScriptMode = 0744
KrbConfMode = 0644
KeytabMode = 0644
)
var (
Workdir = os.Getenv("WORKDIR")
AcmeDir = os.Getenv("ACMEDIR")
AcmeAuthCmd = Workdir + "/" + AuthScript
mutex sync.Mutex // mutex is used to define a critical section of code.
)
// CapturingPassThroughWriter is a writer that remembers
// data written to it and passes it to w
type CapturingPassThroughWriter struct {
buf bytes.Buffer
w io.Writer
}
// NewCapturingPassThroughWriter creates new CapturingPassThroughWriter
func NewCapturingPassThroughWriter(w io.Writer) *CapturingPassThroughWriter {
return &CapturingPassThroughWriter{
w: w,
}
}
func (w *CapturingPassThroughWriter) Write(d []byte) (int, error) {
w.buf.Write(d)
return w.w.Write(d)
}
// Bytes returns bytes written to the writer
func (w *CapturingPassThroughWriter) Bytes() []byte {
return w.buf.Bytes()
}
func SetChallenge(ch *v1alpha1.ChallengeRequest) (bool, error) {
SetupWorkdir() // update workdir if any of the copied files were updated on host
return Execute(
Shell,
AcmeAuthCmd,
"set",
ch.DNSName,
ch.ResolvedFQDN,
ch.Key,
//"-l",
//"\"\"", //localip,
//"-v",
)
}
func UnsetChallenge(ch *v1alpha1.ChallengeRequest) (bool, error) {
SetupWorkdir() // update workdir if any of the copied files were updated on host
return Execute(
Shell,
AcmeAuthCmd,
"unset",
ch.DNSName,
ch.ResolvedFQDN,
ch.Key,
//"-l",
//"\"\"", //localip,
//"-v",
)
}
func Execute(shell string, arg ...string) (bool, error) {
var errStdout, errStderr error
crlf := []byte("\r\n")
lf := []byte("\n")
cmd := exec.Command(shell, arg...)
stdoutIn, _ := cmd.StdoutPipe()
stderrIn, _ := cmd.StderrPipe()
stdout := NewCapturingPassThroughWriter(os.Stdout)
stderr := NewCapturingPassThroughWriter(os.Stderr)
err := cmd.Start()
if err != nil {
klog.Fatalf("cmd.Start() failed with '%s'\n", err)
}
var wg sync.WaitGroup
wg.Add(1)
go func() {
_, errStdout = io.Copy(stdout, stdoutIn)
wg.Done()
}()
_, errStderr = io.Copy(stderr, stderrIn)
wg.Wait()
err = cmd.Wait()
if err != nil {
klog.Fatalf("cmd.Run() failed with %s\n", err)
}
if errStdout != nil || errStderr != nil {
klog.Fatalf("failed to capture stdout or stderr\n")
}
//outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
//fmt.Printf("\nout:\n%s\nerr:\n%s\n", outStr, errStr)
errb := bytes.TrimSuffix(stderr.Bytes(), crlf)
errb = bytes.TrimSuffix(errb, lf)
if errb != nil {
klog.Infof("err:\n%s\n", string(errb))
}
return true, nil
}
func SetupWorkdir() {
mutex.Lock()
defer mutex.Unlock()
err := createWorkdir()
if err != nil {
panic(err)
}
updateWorkdir()
}
func updateWorkdir() {
updateIfStale(AuthScript, AuthScriptMode)
updateIfStale(DnsUpdScript, DnsUpdScriptMode)
updateIfStale(KrbConf, KrbConfMode)
updateIfStale(Keytab, KeytabMode)
}
func createWorkdir() error {
finfo, err := os.Stat(Workdir)
if os.IsNotExist(err) {
klog.Infof("Creating folder \"%s\"", Workdir)
err = os.Mkdir(Workdir, 0755)
if err != nil {
panic(err)
}
} else if !finfo.IsDir() {
return fmt.Errorf("\"%s\" exists, but it's not a folder", Workdir)
}
return nil
}
func updateIfStale(filename string, mode os.FileMode) error {
sourceFile := AcmeDir + "/" + filename
destFile := Workdir + "/" + filename
result, err := cmpModTime(sourceFile, destFile)
if result > 0 {
err = CopyFile(sourceFile, destFile, mode)
if err == nil {
klog.Infof("Updated \"%s\" from \"%s\" folder", destFile, AcmeDir)
}
}
return err
}
func cmpModTime(file1 string, file2 string) (int, error) {
// Get the fileinfo
fileInfo, err := os.Stat(file1)
if err != nil {
klog.Fatal(err)
}
modtime1 := fileInfo.ModTime()
fileInfo, err = os.Stat(file2)
// if file2 does not exist, return as if file1.modtime > file2.modtime
if os.IsNotExist(err) {
return 1, nil
}
// for any other error, return fatal err
if err != nil {
klog.Fatal(err)
}
modtime2 := fileInfo.ModTime()
diff := modtime1.Sub(modtime2)
if diff < (time.Duration(0) * time.Second) {
return -1, nil
}
if diff > (time.Duration(0) * time.Second) {
return 1, nil
}
return 0, nil
}
func CopyFile(sourceFile string, destFile string, mode os.FileMode) error {
source, err := os.Open(sourceFile) //open the source file
if err != nil {
panic(err)
}
defer source.Close()
destination, err := os.Create(destFile) //create the destination file
if err != nil {
panic(err)
}
defer destination.Close()
_, err = io.Copy(destination, source) //copy the contents of source to destination file
if err != nil {
panic(err)
}
err = os.Chmod(destFile, mode)
//klog.Infof("Copied %s to %s.", sourceFile, destFile)
return err
}

View File

@ -110,19 +110,15 @@ func checkDNSPropagation(fqdn, value string, nameservers []string,
var err error
fqdn, err = followCNAMEs(fqdn, nameservers)
//klog.Infof("PreCheckDNS: followCNAMEs returned fqdn: %s, err: %s", fqdn, err)
if err != nil {
return false, err
}
if !useAuthoritative {
var success bool
success, err = checkAuthoritativeNss(fqdn, value, nameservers)
//klog.Infof("PreCheckDNS: returned success: %t, err: %s", success, err)
return success, err
return checkAuthoritativeNss(fqdn, value, nameservers)
}
authoritativeNss, err := lookupNameservers(fqdn, nameservers)
//klog.Infof("PreCheckDNS: lookupNameservers returned authoritativeNss: %s, err: %s", authoritativeNss, err)
if err != nil {
return false, err
}
@ -136,36 +132,20 @@ func checkDNSPropagation(fqdn, value string, nameservers []string,
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
for _, ns := range nameservers {
klog.Infof("================== NS: %s ==================", ns)
r, err := DNSQuery(fqdn, dns.TypeTXT, []string{ns}, true)
//klog.Infof("DNSQuery returned \nr: %s, \nerr: %s", r, err)
rCodeStr := ""
if r == nil {
rCodeStr = "nil"
} else {
rCodeStr = dns.RcodeToString[r.Rcode]
}
if err != nil {
klog.Infof("DNSQuery failed, err: %s, ns: %s, rcode: %s", err, ns, rCodeStr)
return false, err
}
klog.Infof("DNSQuery succeeded with r.Rcode: %s", rCodeStr)
// NXDomain response is not really an error, just waiting for propagation to happen
if !(r.Rcode == dns.RcodeSuccess || r.Rcode == dns.RcodeNameError) {
//klog.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
return false, fmt.Errorf("NS %s returned %s for %s", ns, rCodeStr, fqdn)
return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn)
}
klog.Infof("%q: must be %s", fqdn, value)
//klog.Infof("r.Answer: %s", r.Answer)
klog.Infof("Looking up TXT records for %q", fqdn)
var found bool
var i = 0
for _, rr := range r.Answer {
i++
//txt1, ok1 := rr.(*dns.TXT)
//klog.Infof("Compare[%d]: ok: %t, query: %s, value: %s", i, ok1, strings.Join(txt1.Txt, ""), value)
if txt, ok := rr.(*dns.TXT); ok {
klog.Infof("Compare[%d]: Q: %s, A: %s", i, value, strings.Join(txt.Txt, ""))
if strings.Join(txt.Txt, "") == value {
found = true
break
@ -396,8 +376,7 @@ func matchCAA(caas []*dns.CAA, issuerIDs map[string]bool, iswildcard bool) bool
func lookupNameservers(fqdn string, nameservers []string) ([]string, error) {
var authoritativeNss []string
//klog.Infof("Searching fqdn %q using seed nameservers [%s]", fqdn, strings.Join(nameservers, ", "))
klog.Infof("Searching fqdn %q", fqdn)
klog.Infof("Searching fqdn %q using seed nameservers [%s]", fqdn, strings.Join(nameservers, ", "))
zone, err := FindZoneByFqdn(fqdn, nameservers)
if err != nil {
return nil, fmt.Errorf("could not determine the zone for %q: %v", fqdn, err)
@ -415,7 +394,7 @@ func lookupNameservers(fqdn string, nameservers []string) ([]string, error) {
}
if len(authoritativeNss) > 0 {
//klog.Infof("Returning authoritative nameservers [%s]", strings.Join(authoritativeNss, ", "))
klog.Infof("Returning authoritative nameservers [%s]", strings.Join(authoritativeNss, ", "))
return authoritativeNss, nil
}
return nil, fmt.Errorf("could not determine authoritative nameservers for %q", fqdn)
@ -428,7 +407,7 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
// Do we have it cached?
if zone, ok := fqdnToZone[fqdn]; ok {
fqdnToZoneLock.RUnlock()
klog.Infof("Return zone %q for %q", zone, fqdn)
klog.Infof("Returning cached zone record %q for fqdn %q", zone, fqdn)
return zone, nil
}
fqdnToZoneLock.RUnlock()
@ -482,7 +461,7 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) {
zone := soa.Hdr.Name
fqdnToZone[fqdn] = zone
klog.Infof("Zone record %q for fqdn %q", zone, fqdn)
klog.Infof("Returning discovered zone record %q for fqdn %q", zone, fqdn)
return zone, nil
}
}

View File

@ -1,3 +1,3 @@
0.0.6-alpha.0
20240422-0918
0
0.0.5-alpha.24
20240409-1838
24