Skip to content

Commit

Permalink
Add daemon mode flag (#161)
Browse files Browse the repository at this point in the history
* Add daemon mode flag

Signed-off-by: Faisal Memon <[email protected]>

* Rename example file

Signed-off-by: Faisal Memon <[email protected]>

* Change flag name, fix bug

Signed-off-by: Faisal Memon <[email protected]>

* Cleanup client setup

Signed-off-by: Faisal Memon <[email protected]>

* Skewer case

Signed-off-by: Faisal Memon <[email protected]>

* Update cmd/spiffe-helper/main.go

Signed-off-by: Faisal Memon <[email protected]>

Co-authored-by: Marcos Yacob <[email protected]>
Signed-off-by: Faisal Memon <[email protected]>

* Update cmd/spiffe-helper/main.go

Co-authored-by: Marcos Yacob <[email protected]>
Signed-off-by: Faisal Memon <[email protected]>

* Update flag description

Signed-off-by: Faisal Memon <[email protected]>

* Address code review comments

Signed-off-by: Faisal Memon <[email protected]>

* add back comment

Signed-off-by: Faisal Memon <[email protected]>

* Update helper.conf

Signed-off-by: Faisal Memon <[email protected]>

* Make some function part of struct

Signed-off-by: Faisal Memon <[email protected]>

---------

Signed-off-by: Faisal Memon <[email protected]>
Co-authored-by: Marcos Yacob <[email protected]>
  • Loading branch information
faisal-memon and MarcosDY authored Aug 27, 2024
1 parent 1c72433 commit 9dcaa32
Show file tree
Hide file tree
Showing 10 changed files with 402 additions and 55 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The configuration file is an [HCL](https://github.com/hashicorp/hcl) formatted f
| `cmd` | The path to the process to launch. | `"ghostunnel"` |
| `cmd_args` | 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"` |
| `cert_dir` | Directory name to store the fetched certificates. This directory must be created previously. | `"certs"` |
| `exit_when_ready` | Fetch x509 certificate and then exit(0) | `true` |
| `daemon_mode` | Toggle running as a daemon, keeping X.509 and JWT up to date; or just fetch X.509 and JWT and exit 0 | `true` |
| `add_intermediates_to_bundle` | Add intermediate certificates into Bundle file instead of SVID file. | `true` |
| `renew_signal` | The signal that the process to be launched expects to reload the certificates. It is not supported on Windows. | `"SIGUSR1"` |
| `svid_file_name` | File name to be used to store the X.509 SVID public certificate in PEM format. | `"svid.pem"` |
Expand Down
69 changes: 54 additions & 15 deletions cmd/spiffe-helper/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package config

import (
"errors"
"flag"
"os"

"github.com/hashicorp/hcl"
Expand All @@ -23,10 +24,10 @@ type Config struct {
CmdArgsDeprecated string `hcl:"cmdArgs"`
CertDir string `hcl:"cert_dir"`
CertDirDeprecated string `hcl:"certDir"`
ExitWhenReady bool `hcl:"exit_when_ready"`
IncludeFederatedDomains bool `hcl:"include_federated_domains"`
RenewSignal string `hcl:"renew_signal"`
RenewSignalDeprecated string `hcl:"renewSignal"`
DaemonMode *bool `hcl:"daemon_mode"`

// x509 configuration
SVIDFileName string `hcl:"svid_file_name"`
Expand All @@ -48,26 +49,38 @@ type JWTConfig struct {

// ParseConfig parses the given HCL file into a Config struct
func ParseConfig(file string) (*Config, error) {
sidecarConfig := new(Config)

// Read HCL file
dat, err := os.ReadFile(file)
if err != nil {
return nil, err
}

// Parse HCL
if err := hcl.Decode(sidecarConfig, string(dat)); err != nil {
config := new(Config)
if err := hcl.Decode(config, string(dat)); err != nil {
return nil, err
}

return sidecarConfig, nil
return config, nil
}

// ParseConfigFlagOverrides handles command line arguments that override config file settings
func (c *Config) ParseConfigFlagOverrides(daemonModeFlag bool, daemonModeFlagName string) {
if isFlagPassed(daemonModeFlagName) {
// If daemon mode is set by CLI this takes precedence
c.DaemonMode = &daemonModeFlag
} else if c.DaemonMode == nil {
// If daemon mode is not set, then default to true
daemonMode := true
c.DaemonMode = &daemonMode
}
}

func ValidateConfig(c *Config, exitWhenReady bool, log logrus.FieldLogger) error {
func (c *Config) ValidateConfig(log logrus.FieldLogger) error {
if err := validateOSConfig(c); err != nil {
return err
}

if c.AgentAddressDeprecated != "" {
if c.AgentAddress != "" {
return errors.New("use of agent_address and agentAddress found, use only agent_address")
Expand Down Expand Up @@ -140,16 +153,15 @@ func ValidateConfig(c *Config, exitWhenReady bool, log logrus.FieldLogger) error
}
}

c.ExitWhenReady = c.ExitWhenReady || exitWhenReady

x509EmptyCount := countEmpty(c.SVIDFileName, c.SVIDBundleFileName, c.SVIDKeyFileName)
jwtBundleEmptyCount := countEmpty(c.SVIDBundleFileName)
if x509EmptyCount == 3 && len(c.JWTSVIDs) == 0 && jwtBundleEmptyCount == 1 {
return errors.New("at least one of the sets ('svid_file_name', 'svid_key_file_name', 'svid_bundle_file_name'), 'jwt_svids', or 'jwt_bundle_file_name' must be fully specified")
x509Enabled, err := validateX509Config(c)
if err != nil {
return err
}

if x509EmptyCount != 0 && x509EmptyCount != 3 {
return errors.New("all or none of 'svid_file_name', 'svid_key_file_name', 'svid_bundle_file_name' must be specified")
jwtBundleEnabled, jwtSVIDsEnabled := validateJWTConfig(c)

if !x509Enabled && !jwtBundleEnabled && !jwtSVIDsEnabled {
return errors.New("at least one of the sets ('svid_file_name', 'svid_key_file_name', 'svid_bundle_file_name'), 'jwt_svids', or 'jwt_bundle_file_name' must be fully specified")
}

return nil
Expand All @@ -162,7 +174,6 @@ func NewSidecarConfig(config *Config, log logrus.FieldLogger) *sidecar.Config {
Cmd: config.Cmd,
CmdArgs: config.CmdArgs,
CertDir: config.CertDir,
ExitWhenReady: config.ExitWhenReady,
IncludeFederatedDomains: config.IncludeFederatedDomains,
JWTBundleFilename: config.JWTBundleFilename,
Log: log,
Expand All @@ -182,6 +193,21 @@ func NewSidecarConfig(config *Config, log logrus.FieldLogger) *sidecar.Config {
return sidecarConfig
}

func validateX509Config(c *Config) (bool, error) {
x509EmptyCount := countEmpty(c.SVIDFileName, c.SVIDBundleFileName, c.SVIDKeyFileName)
if x509EmptyCount != 0 && x509EmptyCount != 3 {
return false, errors.New("all or none of 'svid_file_name', 'svid_key_file_name', 'svid_bundle_file_name' must be specified")
}

return x509EmptyCount == 0, nil
}

func validateJWTConfig(c *Config) (bool, bool) {
jwtBundleEmptyCount := countEmpty(c.SVIDBundleFileName)

return jwtBundleEmptyCount == 0, len(c.JWTSVIDs) > 0
}

func getWarning(s1 string, s2 string) string {
return s1 + " will be deprecated, should be used as " + s2
}
Expand All @@ -193,5 +219,18 @@ func countEmpty(configs ...string) int {
cnt++
}
}

return cnt
}

// isFlagPassed tests to see if a command line argument was set at all or left empty
func isFlagPassed(name string) bool {
var found bool
flag.Visit(func(f *flag.Flag) {
if f.Name == name {
found = true
}
})

return found
}
23 changes: 17 additions & 6 deletions cmd/spiffe-helper/config/config_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"flag"
"os"
"testing"

Expand All @@ -10,6 +11,10 @@ import (
"github.com/stretchr/testify/require"
)

const (
daemonModeFlagName = "daemon-mode"
)

func TestParseConfig(t *testing.T) {
c, err := ParseConfig("testdata/helper.conf")

Expand Down Expand Up @@ -294,7 +299,7 @@ func TestValidateConfig(t *testing.T) {
} {
t.Run(tt.name, func(t *testing.T) {
log, hook := test.NewNullLogger()
err := ValidateConfig(tt.config, false, log)
err := tt.config.ValidateConfig(log)

require.ElementsMatch(t, tt.expectLogs, getShortEntries(hook.AllEntries()))

Expand Down Expand Up @@ -345,7 +350,7 @@ func TestDefaultAgentAddress(t *testing.T) {
SVIDBundleFileName: "bundle.pem",
}
log, _ := test.NewNullLogger()
err := ValidateConfig(config, false, log)
err := config.ValidateConfig(log)
require.NoError(t, err)
assert.Equal(t, config.AgentAddress, tt.expectedAgentAddress)
})
Expand Down Expand Up @@ -388,16 +393,22 @@ func TestNewSidecarConfig(t *testing.T) {
assert.Equal(t, "", sidecarConfig.RenewSignal)
}

func TestExitOnWaitFlag(t *testing.T) {
func TestDaemonModeFlag(t *testing.T) {
config := &Config{
SVIDFileName: "cert.pem",
SVIDKeyFileName: "key.pem",
SVIDBundleFileName: "bundle.pem",
}
log, _ := test.NewNullLogger()
err := ValidateConfig(config, true, log)

daemonModeFlag := flag.Bool(daemonModeFlagName, true, "Toggle running as a daemon to rotate X.509/JWT or just fetch and exit")
flag.Parse()

err := flag.Set(daemonModeFlagName, "false")
require.NoError(t, err)
assert.Equal(t, config.ExitWhenReady, true)

config.ParseConfigFlagOverrides(*daemonModeFlag, daemonModeFlagName)
require.NotNil(t, config.DaemonMode)
assert.Equal(t, false, *config.DaemonMode)
}

type shortEntry struct {
Expand Down
34 changes: 21 additions & 13 deletions cmd/spiffe-helper/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,48 @@ import (
"github.com/spiffe/spiffe-helper/pkg/sidecar"
)

func main() {
// 0. Load configuration
// 1. Create Sidecar
// 2. Run Sidecar's Daemon
const (
daemonModeFlagName = "daemon-mode"
)

func main() {
configFile := flag.String("config", "helper.conf", "<configFile> Configuration file path")
exitWhenReady := flag.Bool("exitWhenReady", false, "Exit once the requested objects are retrieved")
daemonModeFlag := flag.Bool(daemonModeFlagName, true, "Toggle running as a daemon to rotate X.509/JWT or just fetch and exit")
flag.Parse()

log := logrus.WithField("system", "spiffe-helper")
log.Infof("Using configuration file: %q\n", *configFile)

if err := startSidecar(*configFile, *exitWhenReady, log); err != nil {
log.WithError(err).Error("Exiting due this error")
if err := startSidecar(*configFile, *daemonModeFlag, log); err != nil {
log.WithError(err).Errorf("Error starting spiffe-helper")
os.Exit(1)
}

log.Infof("Exiting")
os.Exit(0)
}

func startSidecar(configPath string, exitWhenReady bool, log logrus.FieldLogger) error {
func startSidecar(configFile string, daemonModeFlag bool, log logrus.FieldLogger) error {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()

hclConfig, err := config.ParseConfig(configPath)
log.Infof("Using configuration file: %q", configFile)
hclConfig, err := config.ParseConfig(configFile)
if err != nil {
return fmt.Errorf("failed to parse %q: %w", configPath, err)
return fmt.Errorf("failed to parse %q: %w", configFile, err)
}
if err := config.ValidateConfig(hclConfig, exitWhenReady, log); err != nil {
hclConfig.ParseConfigFlagOverrides(daemonModeFlag, daemonModeFlagName)

if err := hclConfig.ValidateConfig(log); err != nil {
return fmt.Errorf("invalid configuration: %w", err)
}

sidecarConfig := config.NewSidecarConfig(hclConfig, log)
spiffeSidecar := sidecar.New(sidecarConfig)

if !*hclConfig.DaemonMode {
log.Info("Daemon mode disabled")
return spiffeSidecar.Run(ctx)
}

log.Info("Launching daemon")
return spiffeSidecar.RunDaemon(ctx)
}
56 changes: 56 additions & 0 deletions examples/k8s/helper-no-daemon.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: spiffe-helper
---
apiVersion: v1
kind: ConfigMap
metadata:
name: spiffe-helper
data:
helper.conf: |
cmd = ""
cmd_args = ""
cert_dir = ""
renew_signal = ""
svid_file_name = "tls.crt"
svid_key_file_name = "tls.key"
svid_bundle_file_name = "ca.pem"
jwt_bundle_file_name = "cert.jwt"
jwt_svids = [{jwt_audience="test", jwt_svid_file_name="jwt_svid.token"}]
daemon_mode = false
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spiffe-helper
labels:
app: spiffe-helper
spec:
selector:
matchLabels:
app: spiffe-helper
template:
metadata:
labels:
app: spiffe-helper
spec:
serviceAccountName: spiffe-helper
containers:
- name: spiffe-helper
image: ghcr.io/spiffe/spiffe-helper:devel
args: ["-config", "config/helper.conf"]
volumeMounts:
- name: spire-agent-socket
mountPath: /tmp/spire-agent/public/
readOnly: true
- name: helper-config
mountPath: /config
volumes:
- name: spire-agent-socket
hostPath:
path: /run/spire/agent-sockets
type: Directory
- name: helper-config
configMap:
name: spiffe-helper
14 changes: 14 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,25 @@ require (
github.com/stretchr/testify v1.9.0
golang.org/x/sys v0.24.0
google.golang.org/grpc v1.65.0
k8s.io/apimachinery v0.30.1
k8s.io/client-go v0.30.1
)

require (
github.com/go-jose/go-jose/v4 v4.0.2 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
k8s.io/klog/v2 v2.120.1 // indirect
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
)

require (
Expand Down
Loading

0 comments on commit 9dcaa32

Please sign in to comment.