From 2614cd2e900a23946a93d530a53dbc0243379443 Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 11 May 2024 21:47:02 +0200 Subject: [PATCH] Starting trying out nsupdate calls from Go --- go.mod | 5 ++ go.sum | 10 ++++ pkg/auth/kerberos.go | 126 +++++++++++++++++++++++++++++++++++++++++++ pkg/auth/krbconf.go | 43 +++++++++++++++ pkg/dns/update.go | 101 ++++++++++++++++++++++++++++++++++ version.txt | 4 +- 6 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 pkg/auth/kerberos.go create mode 100644 pkg/auth/krbconf.go create mode 100644 pkg/dns/update.go diff --git a/go.mod b/go.mod index 39302df..b553728 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,8 @@ 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 @@ -47,6 +49,8 @@ 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 ( @@ -82,6 +86,7 @@ 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 diff --git a/go.sum b/go.sum index 8f8032b..9bd2d0e 100644 --- a/go.sum +++ b/go.sum @@ -171,10 +171,16 @@ 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= @@ -442,6 +448,10 @@ 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= diff --git a/pkg/auth/kerberos.go b/pkg/auth/kerberos.go new file mode 100644 index 0000000..74458c8 --- /dev/null +++ b/pkg/auth/kerberos.go @@ -0,0 +1,126 @@ +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) +} diff --git a/pkg/auth/krbconf.go b/pkg/auth/krbconf.go new file mode 100644 index 0000000..303e5e7 --- /dev/null +++ b/pkg/auth/krbconf.go @@ -0,0 +1,43 @@ +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 + ` +) diff --git a/pkg/dns/update.go b/pkg/dns/update.go new file mode 100644 index 0000000..57fb12c --- /dev/null +++ b/pkg/dns/update.go @@ -0,0 +1,101 @@ +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) +} diff --git a/version.txt b/version.txt index 2efd6fe..2c80c7c 100644 --- a/version.txt +++ b/version.txt @@ -1,3 +1,3 @@ -0.0.5-alpha.113 +0.0.6-alpha.0 20240422-0918 -113 \ No newline at end of file +0 \ No newline at end of file