diff --git a/.gitignore b/.gitignore index 294fcefa..1862d629 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ vendor -/spiffe-helper +/spiffe-helper* /artifacts /releases rpm/*.rpm diff --git a/.go-version b/.go-version index 83d5e73f..0044d6cb 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.19.5 +1.20.1 diff --git a/Makefile b/Makefile index e6f21a73..c2863cc9 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,9 @@ os2=osx else ifeq ($(os1),Linux) os1=linux os2=linux +else ifeq (,$(findstring MYSYS_NT-10-0-, $(os1))) +os1=windows +os2=windows else $(error unsupported OS: $(os1)) endif @@ -76,11 +79,18 @@ build_dir := $(DIR)/.build/$(os1)-$(arch1) go_version := $(shell cat .go-version) go_dir := $(build_dir)/go/$(go_version) -go_bin_dir := $(go_dir)/bin -go_url = https://storage.googleapis.com/golang/go$(go_version).$(os1)-$(arch2).tar.gz -go := PATH="$(go_bin_dir):$(PATH)" go +ifeq ($(os1),windows) + go_bin_dir = $(go_dir)/go/bin + go_url = https://storage.googleapis.com/golang/go$(go_version).$(os1)-$(arch2).zip + exe=".exe" +else + go_bin_dir = $(go_dir)/bin + go_url = https://storage.googleapis.com/golang/go$(go_version).$(os1)-$(arch2).tar.gz + exe= +endif +go_path := PATH="$(go_bin_dir):$(PATH)" -golangci_lint_version = v1.50.1 +golangci_lint_version = v1.51.1 golangci_lint_dir = $(build_dir)/golangci_lint/$(golangci_lint_version) golangci_lint_bin = $(golangci_lint_dir)/golangci-lint golangci_lint_cache = $(golangci_lint_dir)/cache @@ -90,7 +100,14 @@ golangci_lint_cache = $(golangci_lint_dir)/cache ############################################################################ go-check: -ifneq (go$(go_version), $(shell $(go) version 2>/dev/null | cut -f3 -d' ')) +ifeq (go$(go_version), $(shell $(go_path) go version 2>/dev/null | cut -f3 -d' ')) +else ifeq ($(os1),windows) + @echo "Installing go$(go_version)..." + $(E)rm -rf $(dir $(go_dir)) + $(E)mkdir -p $(go_dir) + $(E)curl -o $(go_dir)\go.zip -sSfL $(go_url) + $(E)unzip -qq $(go_dir)\go.zip -d $(go_dir) +else @echo "Installing go$(go_version)..." $(E)rm -rf $(dir $(go_dir)) $(E)mkdir -p $(go_dir) @@ -130,7 +147,7 @@ endif .PHONY: tidy tidy-check lint lint-code tidy: | go-check - $(E)$(go) mod tidy + $(E)$(go_path) mod tidy tidy-check: ifneq ($(git_dirty),) @@ -153,7 +170,7 @@ lint-code: $(golangci_lint_bin) | go-check .PHONY: build test clean distclean artifact tarball rpm build: | go-check - go build -o spiffe-helper ./cmd/spiffe-helper + go build -o spiffe-helper${exe} ./cmd/spiffe-helper artifact: tarball rpm diff --git a/README.md b/README.md index 1c14ea07..6c0d5e22 100644 --- a/README.md +++ b/README.md @@ -6,29 +6,29 @@ The SPIFFE Helper is a simple utility for fetching X.509 SVID certificates from the SPIFFE Workload API, launch a process that makes use of the certificates and continuously get new certificates before they expire. The launched process is signaled to reload the certificates when is needed. -### Usage +## Usage `$ spiffe-helper -config ` ``: file path to the configuration file. If `-config` is not specified, the default value `helper.conf` is assumed. -### Configuration +## Configuration The configuration file is an [HCL](https://github.com/hashicorp/hcl) formatted file that defines the following configurations: |Configuration | Description | Example Value | |--------------------------|------------------------------------------------------------------------------------------------| ------------- | - |`agentAddress` | Socket address of SPIRE Agent. | `"/tmp/agent.sock"` | - |`cmd` | The path to the process to launch. | `"ghostunnel"` | - |`cmdArgs` | The arguments of the process to launch. | `"server --listen localhost:8002 --target localhost:8001--keystore certs/svid_key.pem --cacert certs/svid_bundle.pem --allow-uri-san spiffe://example.org/Database"` | - |`certDir` | Directory name to store the fetched certificates. This directory must be created previously. | `"certs"` | - |`addIntermediatesToBundle`| Add intermediate certificates into Bundle file instead of SVID file. | `true` | - |`renewSignal` | The signal that the process to be launched expects to reload the certificates. | `"SIGUSR1"` | - |`svidFileName` | File name to be used to store the X.509 SVID public certificate in PEM format. | `"svid.pem"` | - |`svidKeyFileName` | File name to be used to store the X.509 SVID private key and public certificate in PEM format. | `"svid_key.pem"` | - |`svidBundleFileName` | File name to be used to store the X.509 SVID Bundle in PEM format. | `"svid_bundle.pem"` | - -#### Configuration example + |`agentAddress` | Socket address of SPIRE Agent. | `"/tmp/agent.sock"` | + |`cmd` | The path to the process to launch. | `"ghostunnel"` | + |`cmdArgs` | The arguments of the process to launch. | `"server --listen localhost:8002 --target localhost:8001--keystore certs/svid_key.pem --cacert certs/svid_bundle.pem --allow-uri-san spiffe://example.org/Database"` | + |`certDir` | Directory name to store the fetched certificates. This directory must be created previously. | `"certs"` | + |`addIntermediatesToBundle`| Add intermediate certificates into Bundle file instead of SVID file. | `true` | + |`renewSignal` | The signal that the process to be launched expects to reload the certificates. It is not supported on Windows. | `"SIGUSR1"` | + |`svidFileName` | File name to be used to store the X.509 SVID public certificate in PEM format. | `"svid.pem"` | + |`svidKeyFileName` | File name to be used to store the X.509 SVID private key and public certificate in PEM format. | `"svid_key.pem"` | + |`svidBundleFileName` | File name to be used to store the X.509 SVID Bundle in PEM format. | `"svid_bundle.pem"` | + +### Configuration example ``` agentAddress = "/tmp/agent.sock" cmd = "ghostunnel" @@ -39,3 +39,11 @@ svidFileName = "svid.pem" svidKeyFileName = "svid_key.pem" svidBundleFileName = "svid_bundle.pem" ``` + +### Windows example + +agentAddress = "spire-agent\\public\\api" +certDir = "certs" +svidFileName = "svid.pem" +svidKeyFileName = "svid_key.pem" +svidBundleFileName = "svid_bundle.pem" diff --git a/go.mod b/go.mod index 7a464a0d..27e3df2c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/spiffe/spiffe-helper -go 1.19 +go 1.20 require ( github.com/hashicorp/hcl v1.0.0 diff --git a/helper_windows.conf b/helper_windows.conf new file mode 100644 index 00000000..91463327 --- /dev/null +++ b/helper_windows.conf @@ -0,0 +1,11 @@ +agentAddress = "spire-agent\\public\\api" +cmd = "" +cmdArgs = "" +certDir = "certs" +svidFileName = "svid.pem" +svidKeyFileName = "svid_key.pem" +svidBundleFileName = "svid_bundle.pem" +# Add CA with intermediates into Bundle file instead of SVID file, +# it is the expected behavior in some scenarios like MySQL. +# Default: false +# addIntermediatesToBundle = false diff --git a/pkg/sidecar/config.go b/pkg/sidecar/config.go index 2c967515..5444e48f 100644 --- a/pkg/sidecar/config.go +++ b/pkg/sidecar/config.go @@ -47,6 +47,10 @@ func ParseConfig(file string) (*Config, error) { } func ValidateConfig(c *Config) error { + if err := validateOSConfig(c); err != nil { + return err + } + switch { case c.AgentAddress == "": return errors.New("agentAddress is required") diff --git a/pkg/sidecar/sidecar.go b/pkg/sidecar/sidecar.go index 973a3d63..8e245a2e 100644 --- a/pkg/sidecar/sidecar.go +++ b/pkg/sidecar/sidecar.go @@ -1,11 +1,9 @@ package sidecar import ( - "context" "crypto/x509" "encoding/csv" "encoding/pem" - "errors" "fmt" "os" "os/exec" @@ -15,7 +13,6 @@ import ( "github.com/spiffe/go-spiffe/v2/logger" "github.com/spiffe/go-spiffe/v2/workloadapi" - "golang.org/x/sys/unix" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -62,19 +59,6 @@ func New(configPath string, log logger.Logger) (*Sidecar, error) { }, nil } -// RunDaemon starts the main loop -// Starts the workload API client to listen for new SVID updates -// When a new SVID is received on the updateChan, the SVID certificates -// are stored in disk and a restart signal is sent to the proxy's process -func (s *Sidecar) RunDaemon(ctx context.Context) error { - err := workloadapi.WatchX509Context(ctx, &x509Watcher{sidecar: s}, workloadapi.WithAddr("unix://"+s.config.AgentAddress)) - if err != nil && !errors.Is(err, context.Canceled) { - return err - } - - return nil -} - // CertReadyChan returns a channel to know when the certificates are ready func (s *Sidecar) CertReadyChan() <-chan struct{} { return s.certReadyChan @@ -122,15 +106,8 @@ func (s *Sidecar) signalProcess() (err error) { s.process = cmd.Process go s.checkProcessExit() } else { - // Signal to reload certs - sig := unix.SignalNum(s.config.RenewSignal) - if sig == 0 { - return fmt.Errorf("error getting signal: %v", s.config.RenewSignal) - } - - err = s.process.Signal(sig) - if err != nil { - return fmt.Errorf("error signaling process with signal: %v\n%w", sig, err) + if err := s.SignalProcess(); err != nil { + return err } } diff --git a/pkg/sidecar/util_posix.go b/pkg/sidecar/util_posix.go new file mode 100644 index 00000000..90231b28 --- /dev/null +++ b/pkg/sidecar/util_posix.go @@ -0,0 +1,45 @@ +//go:build !windows +// +build !windows + +package sidecar + +import ( + "context" + "errors" + "fmt" + + "github.com/spiffe/go-spiffe/v2/workloadapi" + "golang.org/x/sys/unix" +) + +// RunDaemon starts the main loop +// Starts the workload API client to listen for new SVID updates +// When a new SVID is received on the updateChan, the SVID certificates +// are stored in disk and a restart signal is sent to the proxy's process +func (s *Sidecar) RunDaemon(ctx context.Context) error { + err := workloadapi.WatchX509Context(ctx, &x509Watcher{sidecar: s}, workloadapi.WithAddr("unix://"+s.config.AgentAddress)) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + + return nil +} + +func (s *Sidecar) SignalProcess() error { + // Signal to reload certs + sig := unix.SignalNum(s.config.RenewSignal) + if sig == 0 { + return fmt.Errorf("error getting signal: %v", s.config.RenewSignal) + } + + err := s.process.Signal(sig) + if err != nil { + return fmt.Errorf("error signaling process with signal: %v\n%w", sig, err) + } + + return nil +} + +func validateOSConfig(c *Config) error { + return nil +} diff --git a/pkg/sidecar/util_windows.go b/pkg/sidecar/util_windows.go new file mode 100644 index 00000000..62cbe65b --- /dev/null +++ b/pkg/sidecar/util_windows.go @@ -0,0 +1,38 @@ +//go:build windows +// +build windows + +package sidecar + +import ( + "context" + "errors" + + "github.com/spiffe/go-spiffe/v2/workloadapi" +) + +// RunDaemon starts the main loop +// Starts the workload API client to listen for new SVID updates +// When a new SVID is received on the updateChan, the SVID certificates +// are stored in disk and a restart signal is sent to the proxy's process +func (s *Sidecar) RunDaemon(ctx context.Context) error { + err := workloadapi.WatchX509Context(ctx, &x509Watcher{sidecar: s}, workloadapi.WithNamedPipeName(s.config.AgentAddress)) + if err != nil && !errors.Is(err, context.Canceled) { + return err + } + + return nil +} + +func (s *Sidecar) SignalProcess() error { + // Signal to reload certs + // TODO: it is not possible to get signal by name on windows, + // we must provide int here + return errors.New("sending signal is not supported on windows") +} + +func validateOSConfig(c *Config) error { + if c.RenewSignal != "" { + return errors.New("sending signals is not supported on windows") + } + return nil +}