diff --git a/cmd/spiffe-helper/config/config.go b/cmd/spiffe-helper/config/config.go index f3f1b5c..2c190b4 100644 --- a/cmd/spiffe-helper/config/config.go +++ b/cmd/spiffe-helper/config/config.go @@ -26,6 +26,7 @@ type Config struct { AgentAddressDeprecated string `hcl:"agentAddress"` Cmd string `hcl:"cmd"` CmdArgs string `hcl:"cmd_args"` + PIDFileName string `hcl:"pid_file_name"` CmdArgsDeprecated string `hcl:"cmdArgs"` CertDir string `hcl:"cert_dir"` CertDirDeprecated string `hcl:"certDir"` @@ -162,6 +163,10 @@ func (c *Config) ValidateConfig(log logrus.FieldLogger) error { } } + if (c.Cmd != "" || c.PIDFileName != "") && c.RenewSignal == "" { + return errors.New("Must specify renew_signal when using cmd or pid_file_name") + } + x509Enabled, err := validateX509Config(c) if err != nil { return err @@ -203,11 +208,12 @@ func NewSidecarConfig(config *Config, log logrus.FieldLogger) *sidecar.Config { AgentAddress: config.AgentAddress, Cmd: config.Cmd, CmdArgs: config.CmdArgs, + PIDFileName: config.PIDFileName, CertDir: config.CertDir, - CertFileMode: fs.FileMode(config.CertFileMode), //nolint:gosec,G115 - KeyFileMode: fs.FileMode(config.KeyFileMode), //nolint:gosec,G115 - JWTBundleFileMode: fs.FileMode(config.JWTBundleFileMode), //nolint:gosec,G115 - JWTSVIDFileMode: fs.FileMode(config.JWTSVIDFileMode), //nolint:gosec,G115 + CertFileMode: fs.FileMode(config.CertFileMode), //nolint:gosec + KeyFileMode: fs.FileMode(config.KeyFileMode), //nolint:gosec + JWTBundleFileMode: fs.FileMode(config.JWTBundleFileMode), //nolint:gosec + JWTSVIDFileMode: fs.FileMode(config.JWTSVIDFileMode), //nolint:gosec IncludeFederatedDomains: config.IncludeFederatedDomains, JWTBundleFilename: config.JWTBundleFilename, Log: log, diff --git a/pkg/sidecar/config.go b/pkg/sidecar/config.go index 6de560c..9b95e32 100644 --- a/pkg/sidecar/config.go +++ b/pkg/sidecar/config.go @@ -20,6 +20,9 @@ type Config struct { // The arguments of the process to launch. CmdArgs string + // Signal external process via PID file + PIDFileName string + // The directory name to store the x509s and/or JWTs. CertDir string diff --git a/pkg/sidecar/sidecar.go b/pkg/sidecar/sidecar.go index b56ef57..47995c2 100644 --- a/pkg/sidecar/sidecar.go +++ b/pkg/sidecar/sidecar.go @@ -1,11 +1,13 @@ package sidecar import ( + "bytes" "context" "encoding/csv" "fmt" "os" "os/exec" + "strconv" "strings" "sync" "sync/atomic" @@ -141,6 +143,7 @@ func (s *Sidecar) CertReadyChan() <-chan struct{} { return s.certReadyChan } +// setupClients create the necessary workloadapi clients func (s *Sidecar) setupClients(ctx context.Context) error { if s.x509Enabled() || s.jwtBundleEnabled() { client, err := workloadapi.New(ctx, s.getWorkloadAPIAddress()) @@ -176,48 +179,70 @@ func (s *Sidecar) updateCertificates(svidResponse *workloadapi.X509Context) { } } + if s.config.PIDFileName != "" { + if err := s.signalPID(); err != nil { + s.config.Log.WithError(err).Error("Unable to signal PID file") + } + } + + // TODO: is ReloadExternalProcess still used? + if s.config.ReloadExternalProcess != nil { + if err := s.config.ReloadExternalProcess(); err != nil { + s.config.Log.WithError(err).Error("Unable to reload external process") + } + } + select { case s.certReadyChan <- struct{}{}: default: } } -// signalProcess sends the configured Renew signal to the process running the proxy -// to reload itself so that the proxy uses the new SVID -func (s *Sidecar) signalProcess() (err error) { - // TODO: is ReloadExternalProcess still used? - switch s.config.ReloadExternalProcess { - case nil: - if atomic.LoadInt32(&s.processRunning) == 0 { - cmdArgs, err := getCmdArgs(s.config.CmdArgs) - if err != nil { - return fmt.Errorf("error parsing cmd arguments: %w", err) - } - - cmd := exec.Command(s.config.Cmd, cmdArgs...) // #nosec - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Start() - if err != nil { - return fmt.Errorf("error executing process: %v\n%w", s.config.Cmd, err) - } - s.process = cmd.Process - go s.checkProcessExit() - } else { - if err := s.SignalProcess(); err != nil { - return err - } +// signalProcessCMD sends the renew signal to the process or starts it if its first time +func (s *Sidecar) signalProcess() error { + if atomic.LoadInt32(&s.processRunning) == 0 { + cmdArgs, err := getCmdArgs(s.config.CmdArgs) + if err != nil { + return fmt.Errorf("error parsing cmd arguments: %w", err) } - default: - if err = s.config.ReloadExternalProcess(); err != nil { - return fmt.Errorf("error reloading external process: %w", err) + cmd := exec.Command(s.config.Cmd, cmdArgs...) // #nosec + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Start(); err != nil { + return fmt.Errorf("error executing process \"%v\": %w", s.config.Cmd, err) + } + s.process = cmd.Process + go s.checkProcessExit() + } else { + if err := SignalProcess(s.process, s.config.RenewSignal); err != nil { + return err } } return nil } +// signalPID sends the renew signal to the PID file +func (s *Sidecar) signalPID() error { + fileBytes, err := os.ReadFile(s.config.PIDFileName) + if err != nil { + return fmt.Errorf("failed to read pid file \"%s\": %w", s.config.PIDFileName, err) + } + + pid, err := strconv.Atoi(string(bytes.TrimSpace(fileBytes))) + if err != nil { + return fmt.Errorf("failed to parse pid file \"%s\": %w", s.config.PIDFileName, err) + } + + pidProcess, err := os.FindProcess(pid) + if err != nil { + return fmt.Errorf("failed to find process id %d: %w", pid, err) + } + + return SignalProcess(pidProcess, s.config.RenewSignal) +} + func (s *Sidecar) checkProcessExit() { atomic.StoreInt32(&s.processRunning, 1) _, err := s.process.Wait() diff --git a/pkg/sidecar/util_posix.go b/pkg/sidecar/util_posix.go index 5e754dc..6d9e44d 100644 --- a/pkg/sidecar/util_posix.go +++ b/pkg/sidecar/util_posix.go @@ -5,6 +5,7 @@ package sidecar import ( "fmt" + "os" "github.com/spiffe/go-spiffe/v2/workloadapi" "golang.org/x/sys/unix" @@ -14,20 +15,14 @@ func (s *Sidecar) getWorkloadAPIAddress() workloadapi.ClientOption { return workloadapi.WithAddr("unix://" + s.config.AgentAddress) } -func (s *Sidecar) SignalProcess() error { - // Signal to reload certs - if s.config.RenewSignal == "" { - // no signal provided - return nil - } - sig := unix.SignalNum(s.config.RenewSignal) +func SignalProcess(process *os.Process, renewSignal string) error { + sig := unix.SignalNum(renewSignal) if sig == 0 { - return fmt.Errorf("error getting signal: %v", s.config.RenewSignal) + return fmt.Errorf("error getting signal: %v", renewSignal) } - err := s.process.Signal(sig) - if err != nil { - return fmt.Errorf("error signaling process with signal: %v\n%w", sig, err) + if err := process.Signal(sig); err != nil { + return fmt.Errorf("error signaling process with signal num %v: %w", sig, err) } return nil diff --git a/pkg/sidecar/util_windows.go b/pkg/sidecar/util_windows.go index 15bf6ba..878db7f 100644 --- a/pkg/sidecar/util_windows.go +++ b/pkg/sidecar/util_windows.go @@ -13,7 +13,7 @@ func (s *Sidecar) getWorkloadAPIAddress() workloadapi.ClientOption { return workloadapi.WithNamedPipeName(s.config.AgentAddress) } -func (s *Sidecar) SignalProcess() error { +func SignalProcess(_ *os.Process, _ string) error { // Signal to reload certs // TODO: it is not possible to get signal by name on windows, // we must provide int here