diff --git a/deploy/sthome-webhook/Chart.yaml b/deploy/sthome-webhook/Chart.yaml index 2cb7f59..4453d26 100644 --- a/deploy/sthome-webhook/Chart.yaml +++ b/deploy/sthome-webhook/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v1 -appVersion: v0.0.5-alpha.109 +appVersion: v0.0.5-alpha.110 description: Cert-Manager webhook for sthome name: sthome-webhook -version: 0.0.5-alpha.109 +version: 0.0.5-alpha.110 diff --git a/deploy/sthome-webhook/values.yaml b/deploy/sthome-webhook/values.yaml index 738307c..b8d88a9 100644 --- a/deploy/sthome-webhook/values.yaml +++ b/deploy/sthome-webhook/values.yaml @@ -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.109 + tag: 0.0.5-alpha.110 #pullPolicy should be IfNotPresent. Set to Always for testing purposes pullPolicy: IfNotPresent diff --git a/pkg/dns/config.go b/pkg/dns/config.go index 6a6f72e..c82a89a 100644 --- a/pkg/dns/config.go +++ b/pkg/dns/config.go @@ -9,35 +9,16 @@ import ( ) const ( + ProviderName = "sthome" SthomeAccessKeyEnv = "STHOME_ACCESS_KEY" SthomeSecretKeyEnv = "STHOME_SECRET_KEY" - ProviderName = "sthome" - 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 - Dnsserver_net = "10.0.0.15" Dnsserver_lan = "192.168.2.1" Hostserver_net = "truenas.sthome.net" Hostserver_lan = "truenas.sthome.lan" ) -var ( - AcmeAuthCmd = AcmeDir + "/" + AuthScript -) - // localDNSProviderConfig is a structure that is used to decode into when // solving a DNS01 challenge. // This information is provided by cert-manager, and may be a reference to diff --git a/pkg/dns/shell.go b/pkg/dns/shell.go deleted file mode 100644 index a3bbc27..0000000 --- a/pkg/dns/shell.go +++ /dev/null @@ -1,77 +0,0 @@ -package dns - -import ( - "bytes" - "io" - "os" - "os/exec" - "sync" - - "k8s.io/klog/v2" -) - -// 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 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 -} diff --git a/pkg/dns/solver_local.go b/pkg/dns/solver_local.go index 80e5ea1..948260a 100644 --- a/pkg/dns/solver_local.go +++ b/pkg/dns/solver_local.go @@ -9,6 +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/stuurmcp/cert-manager-webhook-sthome/pkg/util" ) @@ -49,21 +50,10 @@ func (loc *LocalDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error if err != nil { return err } - updateWorkdir() + sh.SetupWorkdir() // TODO: convert shell script to golang //localip := GetOutboundIP(Dnsserver_net) - success, err := Execute( - Shell, - // "-c", - AcmeAuthCmd, - "set", - ch.DNSName, - ch.ResolvedFQDN, - ch.Key, - "-l", - "\"\"", //localip, - //"-v", - ) + 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 { @@ -81,19 +71,7 @@ func (loc *LocalDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error func (loc *LocalDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { //domainName := extractDomainName(ch.ResolvedZone) //localip := GetOutboundIP(Dnsserver_net) - updateWorkdir() - success, err := Execute( - Shell, - // "-c", - AcmeAuthCmd, - "unset", - ch.DNSName, - ch.ResolvedFQDN, - ch.Key, - "-l", - "\"\"", //localip, - //"-v", - ) + success, err := sh.UnsetChallenge(ch) klog.Infof("Execute unset TXT returned success: %t\n", success) return err } @@ -113,7 +91,7 @@ func (loc *LocalDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, sto return fmt.Errorf("failed to get kubernetes client: %w", err) } loc.client = cl - setupWorkdir() + sh.SetupWorkdir() klog.Infof("webhook \"%s\" started.", ProviderName) return nil } diff --git a/pkg/dns/utils.go b/pkg/dns/utils.go index 187e8d6..6f8ffa8 100644 --- a/pkg/dns/utils.go +++ b/pkg/dns/utils.go @@ -6,18 +6,8 @@ package dns import ( "encoding/json" "fmt" - "io" - "os" - "sync" - "time" extapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/klog/v2" -) - -var ( - wg sync.WaitGroup // wg is used to wait for the program to finish. - mutex sync.Mutex // mutex is used to define a critical section of code. ) // loadConfig is a small helper function that decodes JSON configuration into @@ -35,105 +25,6 @@ func LoadConfig(cfgJSON *extapi.JSON) (LocalDNSProviderConfig, error) { return cfg, nil } -func setupWorkdir() { - err := createWorkdir() - if err != nil { - panic(err) - } - updateWorkdir() -} - -func updateWorkdir() { - wg.Add(4) // Add a count, one for each goroutine. - - go updateIfStale(AuthScript, AuthScriptMode) - go updateIfStale(DnsUpdScript, DnsUpdScriptMode) - go updateIfStale(KrbConf, KrbConfMode) - go updateIfStale(Keytab, KeytabMode) - - wg.Wait() // Wait for the goroutines to finish. -} - -func createWorkdir() error { - defer mutex.Unlock() - mutex.Lock() - 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 { - defer wg.Done() // Schedule the call to Done to tell main we are done. - mutex.Lock() - 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) - } - } - mutex.Unlock() - 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 -} - /* // quote quotes the provide value func quote(value string) string { diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go new file mode 100644 index 0000000..6c69ba6 --- /dev/null +++ b/pkg/shell/shell.go @@ -0,0 +1,228 @@ +package shell + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "sync" + "time" + + "github.com/jetstack/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 ( + AcmeAuthCmd = AcmeDir + "/" + AuthScript + wg sync.WaitGroup // wg is used to wait for the program to finish. + 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, + // "-c", + 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, + // "-c", + 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 +} diff --git a/version.txt b/version.txt index 924b241..5b0977a 100644 --- a/version.txt +++ b/version.txt @@ -1,3 +1,3 @@ -0.0.5-alpha.109 -20240419-1520 -109 \ No newline at end of file +0.0.5-alpha.110 +20240419-2130 +110 \ No newline at end of file