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 all 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
47 changes: 28 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,34 @@ 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 Check Configuration
SPIFFE Helper can expose and endpoint that can be used for health checking

| Configuration | Description | Example Value |
|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `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
57 changes: 43 additions & 14 deletions cmd/spiffe-helper/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,31 @@ import (
)

const (
daemonModeFlagName = "daemon-mode"
defaultAgentAddress = "/tmp/spire-agent/public/api.sock"
defaultCertFileMode = 0644
defaultKeyFileMode = 0600
defaultJWTBundleFileMode = 0600
defaultJWTSVIDFileMode = 0600
defaultBindPort = 8081
defaultHealthPath = "/healthz"
)

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"`

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

type HealthCheckConfig struct {
ListenerEnabled bool `hcl:"listener_enabled"`
BindPort int `hcl:"bind_port"`
HealthPath string `hcl:"health_path"`
}

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

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

if c.HealthCheck.ListenerEnabled && c.HealthCheck.BindPort < 0 {
return errors.New("bind port must be positive")
} else if c.HealthCheck.ListenerEnabled && c.HealthCheck.BindPort == 0 {
c.HealthCheck.BindPort = defaultBindPort
}
if c.HealthCheck.ListenerEnabled && c.HealthCheck.HealthPath == "" {
c.HealthCheck.HealthPath = defaultHealthPath
}

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
6 changes: 1 addition & 5 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 @@ -314,7 +310,7 @@ func TestDaemonModeFlag(t *testing.T) {
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