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.Errorf("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.Errorf("cmd.Run() failed with %s\n", err) } if errStdout != nil || errStderr != nil { klog.Errorf("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 }