Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HTTP health server (closes #115) #227

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ else
endif
go_path := PATH="$(go_bin_dir):$(PATH)"

golangci_lint_version = v1.60.3
golangci_lint_version = v1.62.2
keeganwitt marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
41 changes: 22 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,28 @@ The flag `-exitWhenReady` is also supported.
## Configuration
The configuration file is an [HCL](https://github.com/hashicorp/hcl) formatted file that defines the following configurations:

| Configuration | Description | Example Value |
|-------------------------------|----------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `agent_address` | Socket address of SPIRE Agent. | `"/tmp/agent.sock"` |
| `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"` |
| `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"` |
| `svid_key_file_name` | File name to be used to store the X.509 SVID private key and public certificate in PEM format. | `"svid_key.pem"` |
| `svid_bundle_file_name` | File name to be used to store the X.509 SVID Bundle in PEM format. | `"svid_bundle.pem"` |
| `jwt_svids` | An array with the audience, optional extra audiences array, and file name to store the JWT SVIDs. File is Base64-encoded string). | `[{jwt_audience="your-audience", jwt_extra_audiences=["your-extra-audience-1", "your-extra-audience-2"], jwt_svid_file_name="jwt_svid.token"}]` |
| `jwt_bundle_file_name` | File name to be used to store JWT Bundle in JSON format. | `"jwt_bundle.json"` |
| `include_federated_domains` | Include trust domains from federated servers in the CA bundle. | `true` |
| `cert_file_mode` | The octal file mode to use when saving the X.509 public certificate file. | `0644` |
| `key_file_mode` | The octal file mode to use when saving the X.509 private key file. | `0600` |
| `jwt_bundle_file_mode` | The octal file mode to use when saving a JWT Bundle file. | `0600` |
| `jwt_svid_file_mode` | The octal file mode to use when saving a JWT SVID file. | `0600` |
| Configuration | Description | Example Value |
|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `agent_address` | Socket address of SPIRE Agent. | `"/tmp/agent.sock"` |
| `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"` |
| `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"` |
| `svid_key_file_name` | File name to be used to store the X.509 SVID private key and public certificate in PEM format. | `"svid_key.pem"` |
| `svid_bundle_file_name` | File name to be used to store the X.509 SVID Bundle in PEM format. | `"svid_bundle.pem"` |
| `jwt_svids` | An array with the audience, optional extra audiences array, and file name to store the JWT SVIDs. File is Base64-encoded string). | `[{jwt_audience="your-audience", jwt_extra_audiences=["your-extra-audience-1", "your-extra-audience-2"], jwt_svid_file_name="jwt_svid.token"}]` |
| `jwt_bundle_file_name` | File name to be used to store JWT Bundle in JSON format. | `"jwt_bundle.json"` |
| `include_federated_domains` | Include trust domains from federated servers in the CA bundle. | `true` |
| `cert_file_mode` | The octal file mode to use when saving the X.509 public certificate file. | `0644` |
| `key_file_mode` | The octal file mode to use when saving the X.509 private key file. | `0600` |
| `jwt_bundle_file_mode` | The octal file mode to use when saving a JWT Bundle file. | `0600` |
| `jwt_svid_file_mode` | The octal file mode to use when saving a JWT SVID file. | `0600` |
| `health_checks.enable_health_check` | Whether to start an HTTP server at the configured endpoint for the daemon health. Doesn't apply for non-daemon mode. | `false` |
| `health_checks.health_check_port` | The port to run the HTTP health server. | `8081` |
| `health_checks.health_check_path` | The URL path for the health check | `/healthz` |
keeganwitt marked this conversation as resolved.
Show resolved Hide resolved

### Configuration example
```
Expand Down
62 changes: 47 additions & 15 deletions cmd/spiffe-helper/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,35 @@ import (
"github.com/spiffe/spiffe-helper/pkg/sidecar"
)

const (
DaemonModeFlagName = "daemon-mode"
)

keeganwitt marked this conversation as resolved.
Show resolved Hide resolved
const (
defaultAgentAddress = "/tmp/spire-agent/public/api.sock"
defaultCertFileMode = 0644
defaultKeyFileMode = 0600
defaultJWTBundleFileMode = 0600
defaultJWTSVIDFileMode = 0600
defaultHealthCheckPort = 8081
defaultHealthCheckPath = "/healthz"
keeganwitt marked this conversation as resolved.
Show resolved Hide resolved
)

type Config struct {
AddIntermediatesToBundle bool `hcl:"add_intermediates_to_bundle"`
AgentAddress string `hcl:"agent_address"`
Cmd string `hcl:"cmd"`
CmdArgs string `hcl:"cmd_args"`
PIDFileName string `hcl:"pid_file_name"`
CertDir string `hcl:"cert_dir"`
CertFileMode int `hcl:"cert_file_mode"`
KeyFileMode int `hcl:"key_file_mode"`
JWTBundleFileMode int `hcl:"jwt_bundle_file_mode"`
JWTSVIDFileMode int `hcl:"jwt_svid_file_mode"`
IncludeFederatedDomains bool `hcl:"include_federated_domains"`
RenewSignal string `hcl:"renew_signal"`
DaemonMode *bool `hcl:"daemon_mode"`
AddIntermediatesToBundle bool `hcl:"add_intermediates_to_bundle"`
AgentAddress string `hcl:"agent_address"`
Cmd string `hcl:"cmd"`
CmdArgs string `hcl:"cmd_args"`
PIDFileName string `hcl:"pid_file_name"`
CertDir string `hcl:"cert_dir"`
CertFileMode int `hcl:"cert_file_mode"`
KeyFileMode int `hcl:"key_file_mode"`
JWTBundleFileMode int `hcl:"jwt_bundle_file_mode"`
JWTSVIDFileMode int `hcl:"jwt_svid_file_mode"`
IncludeFederatedDomains bool `hcl:"include_federated_domains"`
RenewSignal string `hcl:"renew_signal"`
DaemonMode *bool `hcl:"daemon_mode"`
HealthCheck *HealthCheckConfig `hcl:"health_checks"`
keeganwitt marked this conversation as resolved.
Show resolved Hide resolved

// x509 configuration
SVIDFileName string `hcl:"svid_file_name"`
Expand All @@ -50,6 +57,12 @@ type Config struct {
UnusedKeyPositions map[string][]token.Pos `hcl:",unusedKeyPositions"`
}

type HealthCheckConfig struct {
EnableHealthCheck bool `hcl:"enable_health_check"`
HealthCheckPort int `hcl:"health_check_port"`
HealthCheckPath string `hcl:"health_check_path"`
keeganwitt marked this conversation as resolved.
Show resolved Hide resolved
}

type JWTConfig struct {
JWTAudience string `hcl:"jwt_audience"`
JWTExtraAudiences []string `hcl:"jwt_extra_audiences"`
Expand All @@ -76,8 +89,8 @@ func ParseConfig(file string) (*Config, error) {
}

// ParseConfigFlagOverrides handles command line arguments that override config file settings
func (c *Config) ParseConfigFlagOverrides(daemonModeFlag bool, daemonModeFlagName string) {
if isFlagPassed(daemonModeFlagName) {
func (c *Config) ParseConfigFlagOverrides(daemonModeFlag bool) {
if isFlagPassed(DaemonModeFlagName) {
// If daemon mode is set by CLI this takes precedence
c.DaemonMode = &daemonModeFlag
} else if c.DaemonMode == nil {
Expand Down Expand Up @@ -158,9 +171,28 @@ func (c *Config) ValidateConfig(log logrus.FieldLogger) error {
c.JWTSVIDFileMode = defaultJWTSVIDFileMode
}

if c.HealthCheck != nil && c.HealthCheck.HealthCheckPort < 0 {
return errors.New("health check port must be positive")
} else if c.HealthCheck != nil && c.HealthCheck.HealthCheckPort == 0 {
c.HealthCheck.HealthCheckPort = defaultHealthCheckPort
}
if c.HealthCheck != nil && c.HealthCheck.HealthCheckPath == "" {
c.HealthCheck.HealthCheckPath = defaultHealthCheckPath
}

return nil
}

func ParseConfigFile(log logrus.FieldLogger, configFile string, daemonModeFlag bool) (*Config, error) {
log.Infof("Using configuration file: %q", configFile)
hclConfig, err := ParseConfig(configFile)
if err != nil {
return nil, fmt.Errorf("failed to parse %q: %w", configFile, err)
}
hclConfig.ParseConfigFlagOverrides(daemonModeFlag)
return hclConfig, nil
}

// checkForUnknownConfig looks for any unknown configuration keys and returns an error if one is found
func (c *Config) checkForUnknownConfig() error {
if len(c.UnusedKeyPositions) != 0 {
Expand Down
10 changes: 3 additions & 7 deletions cmd/spiffe-helper/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ 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 @@ -308,13 +304,13 @@ func TestDaemonModeFlag(t *testing.T) {
SVIDBundleFileName: "bundle.pem",
}

daemonModeFlag := flag.Bool(daemonModeFlagName, true, "Toggle running as a daemon to rotate X.509/JWT or just fetch and exit")
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")
err := flag.Set(DaemonModeFlagName, "false")
require.NoError(t, err)

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