diff --git a/command/server.go b/command/server.go index 6b91557ac2b1..870871e11bfb 100644 --- a/command/server.go +++ b/command/server.go @@ -886,9 +886,9 @@ func (c *ServerCommand) InitListeners(config *server.Config, disableClustering b } if reloadFunc != nil { - relSlice := (*c.reloadFuncs)["listener|"+lnConfig.Type] + relSlice := (*c.reloadFuncs)[fmt.Sprintf("listener|%s", lnConfig.Type)] relSlice = append(relSlice, reloadFunc) - (*c.reloadFuncs)["listener|"+lnConfig.Type] = relSlice + (*c.reloadFuncs)[fmt.Sprintf("listener|%s", lnConfig.Type)] = relSlice } if !disableClustering && lnConfig.Type == "tcp" { diff --git a/command/server/config_test_helpers.go b/command/server/config_test_helpers.go index cb10655a7e0c..bfeec3ca0de7 100644 --- a/command/server/config_test_helpers.go +++ b/command/server/config_test_helpers.go @@ -793,7 +793,7 @@ func testConfig_Sanitized(t *testing.T) { "address": "127.0.0.1:443", "chroot_namespace": "admin/", }, - "type": "tcp", + "type": configutil.TCP, }, }, "log_format": "", @@ -890,6 +890,15 @@ listener "tcp" { redact_addresses = true redact_cluster_name = true redact_version = true +} +listener "unix" { + address = "/var/run/vault.sock" + socket_mode = "644" + socket_user = "1000" + socket_group = "1000" + redact_addresses = true + redact_cluster_name = true + redact_version = true }`)) config := Config{ @@ -903,16 +912,14 @@ listener "tcp" { config.Listeners = listeners // Track which types of listener were found. for _, l := range config.Listeners { - config.found(l.Type, l.Type) + config.found(l.Type.String(), l.Type.String()) } - if len(config.Listeners) == 0 { - t.Fatalf("expected at least one listener in the config") - } - listener := config.Listeners[0] - if listener.Type != "tcp" { - t.Fatalf("expected tcp listener in the config") - } + require.Len(t, config.Listeners, 2) + tcpListener := config.Listeners[0] + require.Equal(t, configutil.TCP, tcpListener.Type) + unixListner := config.Listeners[1] + require.Equal(t, configutil.Unix, unixListner.Type) expected := &Config{ SharedConfig: &configutil.SharedConfig{ @@ -946,6 +953,16 @@ listener "tcp" { RedactClusterName: true, RedactVersion: true, }, + { + Type: "unix", + Address: "/var/run/vault.sock", + SocketMode: "644", + SocketUser: "1000", + SocketGroup: "1000", + RedactAddresses: false, + RedactClusterName: false, + RedactVersion: false, + }, }, }, } diff --git a/command/server/listener.go b/command/server/listener.go index 1e2133dc395b..7e308a0ccb79 100644 --- a/command/server/listener.go +++ b/command/server/listener.go @@ -22,7 +22,7 @@ import ( type ListenerFactory func(*configutil.Listener, io.Writer, cli.Ui) (net.Listener, map[string]string, reloadutil.ReloadFunc, error) // BuiltinListeners is the list of built-in listener types. -var BuiltinListeners = map[string]ListenerFactory{ +var BuiltinListeners = map[configutil.ListenerType]ListenerFactory{ "tcp": tcpListenerFactory, "unix": unixListenerFactory, } diff --git a/internalshared/configutil/config.go b/internalshared/configutil/config.go index f5e8d6fa0122..2cd7058d01d8 100644 --- a/internalshared/configutil/config.go +++ b/internalshared/configutil/config.go @@ -141,7 +141,7 @@ func ParseConfig(d string) (*SharedConfig, error) { // Track which types of listener were found. for _, l := range result.Listeners { - result.found(l.Type, l.Type) + result.found(l.Type.String(), l.Type.String()) } } diff --git a/internalshared/configutil/listener.go b/internalshared/configutil/listener.go index 20cc7bdfc52b..b040ef9246bb 100644 --- a/internalshared/configutil/listener.go +++ b/internalshared/configutil/listener.go @@ -22,6 +22,14 @@ import ( "github.com/hashicorp/vault/helper/namespace" ) +const ( + TCP ListenerType = "tcp" + Unix ListenerType = "unix" +) + +// ListenerType represents the supported types of listener. +type ListenerType string + type ListenerTelemetry struct { UnusedKeys UnusedKeyMap `hcl:",unusedKeyPositions"` UnauthenticatedMetricsAccess bool `hcl:"-"` @@ -45,7 +53,7 @@ type Listener struct { UnusedKeys UnusedKeyMap `hcl:",unusedKeyPositions"` RawConfig map[string]interface{} - Type string + Type ListenerType Purpose []string `hcl:"-"` PurposeRaw interface{} `hcl:"purpose"` Role string `hcl:"role"` @@ -254,6 +262,16 @@ func parseListener(item *ast.ObjectItem) (*Listener, error) { return l, nil } +// Normalize returns the lower case string version of a listener type. +func (t ListenerType) Normalize() ListenerType { + return ListenerType(strings.ToLower(string(t))) +} + +// String returns the string version of a listener type. +func (t ListenerType) String() string { + return string(t.Normalize()) +} + // parseChrootNamespace attempts to parse the raw listener chroot namespace settings. // The state of the listener will be modified, raw data will be cleared upon // successful parsing. @@ -286,20 +304,21 @@ func (l *Listener) parseType(fallback string) error { } // Use type if available, otherwise fall back. - result := l.Type - if result == "" { - result = fallback + rawType := l.Type + if rawType == "" { + rawType = ListenerType(fallback) } - result = strings.ToLower(result) + + parsedType := rawType.Normalize() // Sanity check the values - switch result { - case "tcp", "unix": + switch parsedType { + case TCP, Unix: default: - return fmt.Errorf("unsupported listener type %q", result) + return fmt.Errorf("unsupported listener type %q", parsedType) } - l.Type = result + l.Type = parsedType return nil } @@ -396,6 +415,13 @@ func (l *Listener) parseTLSSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseHTTPHeaderSettings() error { + // Custom response headers are only supported by TCP listeners. + // Clear raw data and return early if it was something else. + if l.Type != TCP { + l.CustomResponseHeadersRaw = nil + return nil + } + // if CustomResponseHeadersRaw is nil, we still need to set the default headers customHeadersMap, err := ParseCustomResponseHeaders(l.CustomResponseHeadersRaw) if err != nil { @@ -604,6 +630,16 @@ func (l *Listener) parseCORSSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseRedactionSettings() error { + // Redaction is only supported on TCP listeners. + // Clear raw data and return early if it was something else. + if l.Type != TCP { + l.RedactAddressesRaw = nil + l.RedactClusterNameRaw = nil + l.RedactVersionRaw = nil + + return nil + } + var err error if l.RedactAddressesRaw != nil { diff --git a/internalshared/configutil/listener_test.go b/internalshared/configutil/listener_test.go index ac4239efc1ff..4ea11d57cc60 100644 --- a/internalshared/configutil/listener_test.go +++ b/internalshared/configutil/listener_test.go @@ -159,7 +159,7 @@ func TestListener_parseType(t *testing.T) { tc := tc t.Run(name, func(t *testing.T) { t.Parallel() - l := &Listener{Type: tc.inputType} + l := &Listener{Type: ListenerType(tc.inputType)} err := l.parseType(tc.inputFallback) switch { case tc.isErrorExpected: @@ -167,7 +167,7 @@ func TestListener_parseType(t *testing.T) { require.ErrorContains(t, err, tc.errorMessage) default: require.NoError(t, err) - require.Equal(t, tc.expectedValue, l.Type) + require.Equal(t, tc.expectedValue, l.Type.String()) } }) } @@ -861,16 +861,19 @@ func TestListener_parseCORSSettings(t *testing.T) { // assign the relevant value on the SharedConfig struct. func TestListener_parseHTTPHeaderSettings(t *testing.T) { tests := map[string]struct { + listenerType ListenerType rawCustomResponseHeaders []map[string]any expectedNumCustomResponseHeaders int isErrorExpected bool errorMessage string }{ "nil": { + listenerType: TCP, isErrorExpected: false, expectedNumCustomResponseHeaders: 1, // default: Strict-Transport-Security }, "custom-headers-bad": { + listenerType: TCP, rawCustomResponseHeaders: []map[string]any{ {"juan": false}, }, @@ -878,6 +881,7 @@ func TestListener_parseHTTPHeaderSettings(t *testing.T) { errorMessage: "failed to parse custom_response_headers", }, "custom-headers-good": { + listenerType: TCP, rawCustomResponseHeaders: []map[string]any{ { "2xx": []map[string]any{ @@ -888,6 +892,18 @@ func TestListener_parseHTTPHeaderSettings(t *testing.T) { expectedNumCustomResponseHeaders: 2, isErrorExpected: false, }, + "unix-no-headers": { + listenerType: Unix, + rawCustomResponseHeaders: []map[string]any{ + { + "2xx": []map[string]any{ + {"X-Custom-Header": []any{"Custom Header Value 1", "Custom Header Value 2"}}, + }, + }, + }, + expectedNumCustomResponseHeaders: 0, + isErrorExpected: false, + }, } for name, tc := range tests { @@ -898,6 +914,7 @@ func TestListener_parseHTTPHeaderSettings(t *testing.T) { // Configure listener with raw values l := &Listener{ + Type: tc.listenerType, CustomResponseHeadersRaw: tc.rawCustomResponseHeaders, } @@ -978,6 +995,7 @@ func TestListener_parseChrootNamespaceSettings(t *testing.T) { // assign the relevant value on the SharedConfig struct. func TestListener_parseRedactionSettings(t *testing.T) { tests := map[string]struct { + listenerType ListenerType rawRedactAddresses any expectedRedactAddresses bool rawRedactClusterName any @@ -988,41 +1006,58 @@ func TestListener_parseRedactionSettings(t *testing.T) { errorMessage string }{ "missing": { + listenerType: TCP, isErrorExpected: false, expectedRedactAddresses: false, expectedRedactClusterName: false, expectedRedactVersion: false, }, "redact-addresses-bad": { + listenerType: TCP, rawRedactAddresses: "juan", isErrorExpected: true, errorMessage: "invalid value for redact_addresses", }, "redact-addresses-good": { + listenerType: TCP, rawRedactAddresses: "true", expectedRedactAddresses: true, isErrorExpected: false, }, "redact-cluster-name-bad": { + listenerType: TCP, rawRedactClusterName: "juan", isErrorExpected: true, errorMessage: "invalid value for redact_cluster_name", }, "redact-cluster-name-good": { + listenerType: TCP, rawRedactClusterName: "true", expectedRedactClusterName: true, isErrorExpected: false, }, "redact-version-bad": { + listenerType: TCP, rawRedactVersion: "juan", isErrorExpected: true, errorMessage: "invalid value for redact_version", }, "redact-version-good": { + listenerType: TCP, rawRedactVersion: "true", expectedRedactVersion: true, isErrorExpected: false, }, + "redact-unix-na": { + listenerType: Unix, + rawRedactAddresses: "true", + expectedRedactAddresses: false, + rawRedactClusterName: "true", + expectedRedactClusterName: false, + rawRedactVersion: "true", + expectedRedactVersion: false, + isErrorExpected: false, + }, } for name, tc := range tests { @@ -1033,6 +1068,7 @@ func TestListener_parseRedactionSettings(t *testing.T) { // Configure listener with raw values l := &Listener{ + Type: tc.listenerType, RedactAddressesRaw: tc.rawRedactAddresses, RedactClusterNameRaw: tc.rawRedactClusterName, RedactVersionRaw: tc.rawRedactVersion,