226 lines
5.0 KiB
Go
226 lines
5.0 KiB
Go
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 (
|
|
AcmeAuthCmd = AcmeDir + "/" + 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
|
|
}
|