From 01cd9d37bb49814a3a8b13e10798526b07c507a7 Mon Sep 17 00:00:00 2001 From: Marc Boudreau Date: Wed, 11 Oct 2023 14:23:21 -0400 Subject: [PATCH] Add Ability to Disable Replication Status Endpoints in Listener Configuration (#23547) * CI: Pre-emptively delete logs dir after cache restore in test-collect-reports (#23600) * Fix OktaNumberChallenge (#23565) * remove arg * changelog * exclude changelog in verifying doc/ui PRs (#23601) * Audit: eventlogger sink node reopen on SIGHUP (#23598) * ensure nodes are asked to reload audit files on SIGHUP * added changelog * Capture errors emitted from all nodes during proccessing of audit pipelines (#23582) * Update security-scan.yml * Listeners: Redaction only for TCP (#23592) * redaction should only work for TCP listeners, also fix bug that allowed custom response headers for unix listeners * fix failing test * updates from PR feedback * fix panic when unlocking unlocked user (#23611) * VAULT-18307: update rotation period for aws static roles on update (#23528) * add disable_replication_status_endpoints tcp listener config parameter * add wrapping handler for disabled replication status endpoints setting * adapt disable_replication_status_endpoints configuration parsing code to refactored parsing code * refactor configuration parsing code to facilitate testing * fix a panic when parsing configuration * update refactored configuration parsing code * fix merge corruption * add changelog file * document new TCP listener configuration parameter * make sure disable_replication_status_endpoints only has effect on TCP listeners * use active voice for explanation of disable_replication_status_endpoints * fix minor merge issue --------- Co-authored-by: Kuba Wieczorek Co-authored-by: Angel Garbarino Co-authored-by: Hamid Ghaf <83242695+hghaf099@users.noreply.github.com> Co-authored-by: Peter Wilson Co-authored-by: Mark Collao <106274486+mcollao-hc@users.noreply.github.com> Co-authored-by: davidadeleon <56207066+davidadeleon@users.noreply.github.com> Co-authored-by: kpcraig <3031348+kpcraig@users.noreply.github.com> --- changelog/23547.txt | 3 + http/handler.go | 27 +- http/util.go | 8 + internalshared/configutil/listener.go | 254 +++++++++-------- internalshared/configutil/listener_test.go | 267 ++++++++++++++++++ .../docs/configuration/listener/tcp.mdx | 3 + 6 files changed, 437 insertions(+), 125 deletions(-) create mode 100644 changelog/23547.txt diff --git a/changelog/23547.txt b/changelog/23547.txt new file mode 100644 index 0000000000..f5ddb19938 --- /dev/null +++ b/changelog/23547.txt @@ -0,0 +1,3 @@ +```release-note:feature +config/listener: allow per-listener configuration setting to disable replication status endpoints. +``` \ No newline at end of file diff --git a/http/handler.go b/http/handler.go index ac8258eca8..8d83c8030f 100644 --- a/http/handler.go +++ b/http/handler.go @@ -237,20 +237,27 @@ func handler(props *vault.HandlerProperties) http.Handler { additionalRoutes(mux, core) } - // Wrap the handler in another handler to trigger all help paths. - helpWrappedHandler := wrapHelpHandler(mux, core) - corsWrappedHandler := wrapCORSHandler(helpWrappedHandler, core) - quotaWrappedHandler := rateLimitQuotaWrapping(corsWrappedHandler, core) - genericWrappedHandler := genericWrapping(core, quotaWrappedHandler, props) + // Build up a chain of wrapping handlers. + wrappedHandler := wrapHelpHandler(mux, core) + wrappedHandler = wrapCORSHandler(wrappedHandler, core) + wrappedHandler = rateLimitQuotaWrapping(wrappedHandler, core) + wrappedHandler = genericWrapping(core, wrappedHandler, props) - // Wrap the handler with PrintablePathCheckHandler to check for non-printable - // characters in the request path. - printablePathCheckHandler := genericWrappedHandler + // Add an extra wrapping handler if the DisablePrintableCheck listener + // setting isn't true that checks for non-printable characters in the + // request path. if !props.DisablePrintableCheck { - printablePathCheckHandler = cleanhttp.PrintablePathCheckHandler(genericWrappedHandler, nil) + wrappedHandler = cleanhttp.PrintablePathCheckHandler(wrappedHandler, nil) } - return printablePathCheckHandler + // Add an extra wrapping handler if the DisableReplicationStatusEndpoints + // setting is true that will create a new request with a context that has + // a value indicating that the replication status endpoints are disabled. + if props.ListenerConfig != nil && props.ListenerConfig.DisableReplicationStatusEndpoints { + wrappedHandler = disableReplicationStatusEndpointWrapping(wrappedHandler) + } + + return wrappedHandler } type copyResponseWriter struct { diff --git a/http/util.go b/http/util.go index 7986a3f37d..bbf49951dc 100644 --- a/http/util.go +++ b/http/util.go @@ -133,6 +133,14 @@ func rateLimitQuotaWrapping(handler http.Handler, core *vault.Core) http.Handler }) } +func disableReplicationStatusEndpointWrapping(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + request := r.WithContext(context.WithValue(r.Context(), "disable_replication_status_endpoints", true)) + + h.ServeHTTP(w, request) + }) +} + func parseRemoteIPAddress(r *http.Request) string { ip, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { diff --git a/internalshared/configutil/listener.go b/internalshared/configutil/listener.go index b040ef9246..de9c6096cd 100644 --- a/internalshared/configutil/listener.go +++ b/internalshared/configutil/listener.go @@ -139,6 +139,10 @@ type Listener struct { RedactClusterName bool `hcl:"-"` RedactVersionRaw any `hcl:"redact_version"` RedactVersion bool `hcl:"-"` + + // DisableReplicationStatusEndpoint disables the unauthenticated replication status endpoints + DisableReplicationStatusEndpointsRaw interface{} `hcl:"disable_replication_status_endpoints"` + DisableReplicationStatusEndpoints bool `hcl:"-"` } // AgentAPI allows users to select which parts of the Agent API they want enabled. @@ -252,6 +256,7 @@ func parseListener(item *ast.ObjectItem) (*Listener, error) { l.parseHTTPHeaderSettings, l.parseChrootNamespaceSettings, l.parseRedactionSettings, + l.parseDisableReplicationStatusEndpointSettings, } { err := parser() if err != nil { @@ -272,22 +277,114 @@ func (t ListenerType) String() string { return string(t.Normalize()) } +// parseAndClearBool parses a raw setting as a bool configuration parameter. If +// the raw value is successfully parsed, the parsedSetting argument is set to it +// and the rawSetting argument is cleared. Otherwise, the rawSetting argument is +// left unchanged and an error is returned. +func parseAndClearBool(rawSetting *interface{}, parsedSetting *bool) error { + var err error + + if *rawSetting != nil { + *parsedSetting, err = parseutil.ParseBool(*rawSetting) + if err != nil { + return err + } + + *rawSetting = nil + } + + return nil +} + +// parseAndClearString parses a raw setting as a string configuration parameter. +// If the raw value is successfully parsed, the parsedSetting argument is set to +// it and the rawSetting argument is cleared. Otherwise, the rawSetting argument +// is left unchanged and an error is returned. +func parseAndClearString(rawSetting *interface{}, parsedSetting *string) error { + var err error + + if *rawSetting != nil { + *parsedSetting, err = parseutil.ParseString(*rawSetting) + if err != nil { + return err + } + + *rawSetting = nil + } + + return nil +} + +// parseAndClearInt parses a raw setting as an integer configuration parameter. +// If the raw value is successfully parsed, the parsedSetting argument is set to +// it and the rawSetting argument is cleared. Otherwise, the rawSetting argument +// is left unchanged and an error is returned. +func parseAndClearInt(rawSetting *interface{}, parsedSetting *int64) error { + var err error + + if *rawSetting != nil { + *parsedSetting, err = parseutil.ParseInt(*rawSetting) + if err != nil { + return err + } + + *rawSetting = nil + } + + return nil +} + +// parseAndClearDurationSecond parses a raw setting as a time duration +// configuration parameter. If the raw value is successfully parsed, the +// parsedSetting argument is set to it and the rawSetting argument is cleared. +// Otherwise, the rawSetting argument is left unchanged and an error is +// returned. +func parseAndClearDurationSecond(rawSetting *interface{}, parsedSetting *time.Duration) error { + var err error + + if *rawSetting != nil { + *parsedSetting, err = parseutil.ParseDurationSecond(*rawSetting) + if err != nil { + return err + } + + *rawSetting = nil + } + + return nil +} + +// parseDisableReplicationStatusEndpointSettings attempts to parse the raw +// disable_replication_status_endpoints setting. The receiving Listener's +// DisableReplicationStatusEndpoints field will be set with the successfully +// parsed value. +func (l *Listener) parseDisableReplicationStatusEndpointSettings() error { + if l.Type != TCP { + return nil + } + + if err := parseAndClearBool(&l.DisableReplicationStatusEndpointsRaw, &l.DisableReplicationStatusEndpoints); err != nil { + return fmt.Errorf("invalid value for disable_replication_status_endpoints: %w", err) + } + + return nil +} + // 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. func (l *Listener) parseChrootNamespaceSettings() error { - var err error + var ( + err error + setting string + ) - // If a valid ChrootNamespace value exists, then canonicalize the namespace value - if l.ChrootNamespaceRaw != nil { - l.ChrootNamespace, err = parseutil.ParseString(l.ChrootNamespaceRaw) - if err != nil { - return fmt.Errorf("invalid value for chroot_namespace: %w", err) - } - l.ChrootNamespace = namespace.Canonicalize(l.ChrootNamespace) + err = parseAndClearString(&l.ChrootNamespaceRaw, &setting) + if err != nil { + return fmt.Errorf("invalid value for chroot_namespace: %w", err) } - l.ChrootNamespaceRaw = nil + l.ChrootNamespace = namespace.Canonicalize(setting) return nil } @@ -327,13 +424,8 @@ func (l *Listener) parseType(fallback string) error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseRequestSettings() error { - if l.MaxRequestSizeRaw != nil { - maxRequestSize, err := parseutil.ParseInt(l.MaxRequestSizeRaw) - if err != nil { - return fmt.Errorf("error parsing max_request_size: %w", err) - } - - l.MaxRequestSize = maxRequestSize + if err := parseAndClearInt(&l.MaxRequestSizeRaw, &l.MaxRequestSize); err != nil { + return fmt.Errorf("error parsing max_request_size: %w", err) } if l.MaxRequestDurationRaw != nil { @@ -347,22 +439,13 @@ func (l *Listener) parseRequestSettings() error { } l.MaxRequestDuration = maxRequestDuration + l.MaxRequestDurationRaw = nil } - if l.RequireRequestHeaderRaw != nil { - requireRequestHeader, err := parseutil.ParseBool(l.RequireRequestHeaderRaw) - if err != nil { - return fmt.Errorf("invalid value for require_request_header: %w", err) - } - - l.RequireRequestHeader = requireRequestHeader + if err := parseAndClearBool(&l.RequireRequestHeaderRaw, &l.RequireRequestHeader); err != nil { + return fmt.Errorf("invalid value for require_request_header: %w", err) } - // Clear raw values after successful parsing. - l.MaxRequestSizeRaw = nil - l.MaxRequestDurationRaw = nil - l.RequireRequestHeaderRaw = nil - return nil } @@ -370,12 +453,8 @@ func (l *Listener) parseRequestSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseTLSSettings() error { - if l.TLSDisableRaw != nil { - tlsDisable, err := parseutil.ParseBool(l.TLSDisableRaw) - if err != nil { - return fmt.Errorf("invalid value for tls_disable: %w", err) - } - l.TLSDisable = tlsDisable + if err := parseAndClearBool(&l.TLSDisableRaw, &l.TLSDisable); err != nil { + return fmt.Errorf("invalid value for tls_disable: %w", err) } if l.TLSCipherSuitesRaw != "" { @@ -386,27 +465,16 @@ func (l *Listener) parseTLSSettings() error { l.TLSCipherSuites = tlsCipherSuites } - if l.TLSRequireAndVerifyClientCertRaw != nil { - tlsRequireAndVerifyClientCert, err := parseutil.ParseBool(l.TLSRequireAndVerifyClientCertRaw) - if err != nil { - return fmt.Errorf("invalid value for tls_require_and_verify_client_cert: %w", err) - } - l.TLSRequireAndVerifyClientCert = tlsRequireAndVerifyClientCert + if err := parseAndClearBool(&l.TLSRequireAndVerifyClientCertRaw, &l.TLSRequireAndVerifyClientCert); err != nil { + return fmt.Errorf("invalid value for tls_require_and_verify_client_cert: %w", err) } - if l.TLSDisableClientCertsRaw != nil { - tlsDisableClientCerts, err := parseutil.ParseBool(l.TLSDisableClientCertsRaw) - if err != nil { - return fmt.Errorf("invalid value for tls_disable_client_certs: %w", err) - } - l.TLSDisableClientCerts = tlsDisableClientCerts + if err := parseAndClearBool(&l.TLSDisableClientCertsRaw, &l.TLSDisableClientCerts); err != nil { + return fmt.Errorf("invalid value for tls_disable_client_certs: %w", err) } // Clear raw values after successful parsing. - l.TLSDisableRaw = nil l.TLSCipherSuitesRaw = "" - l.TLSRequireAndVerifyClientCertRaw = nil - l.TLSDisableClientCertsRaw = nil return nil } @@ -438,38 +506,22 @@ func (l *Listener) parseHTTPHeaderSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseHTTPTimeoutSettings() error { - var err error - - if l.HTTPReadTimeoutRaw != nil { - if l.HTTPReadTimeout, err = parseutil.ParseDurationSecond(l.HTTPReadTimeoutRaw); err != nil { - return fmt.Errorf("error parsing http_read_timeout: %w", err) - } + if err := parseAndClearDurationSecond(&l.HTTPReadTimeoutRaw, &l.HTTPReadTimeout); err != nil { + return fmt.Errorf("error parsing http_read_timeout: %w", err) } - if l.HTTPReadHeaderTimeoutRaw != nil { - if l.HTTPReadHeaderTimeout, err = parseutil.ParseDurationSecond(l.HTTPReadHeaderTimeoutRaw); err != nil { - return fmt.Errorf("error parsing http_read_header_timeout: %w", err) - } + if err := parseAndClearDurationSecond(&l.HTTPReadHeaderTimeoutRaw, &l.HTTPReadHeaderTimeout); err != nil { + return fmt.Errorf("error parsing http_read_header_timeout: %w", err) } - if l.HTTPWriteTimeoutRaw != nil { - if l.HTTPWriteTimeout, err = parseutil.ParseDurationSecond(l.HTTPWriteTimeoutRaw); err != nil { - return fmt.Errorf("error parsing http_write_timeout: %w", err) - } + if err := parseAndClearDurationSecond(&l.HTTPWriteTimeoutRaw, &l.HTTPWriteTimeout); err != nil { + return fmt.Errorf("error parsing http_write_timeout: %w", err) } - if l.HTTPIdleTimeoutRaw != nil { - if l.HTTPIdleTimeout, err = parseutil.ParseDurationSecond(l.HTTPIdleTimeoutRaw); err != nil { - return fmt.Errorf("error parsing http_idle_timeout: %w", err) - } + if err := parseAndClearDurationSecond(&l.HTTPIdleTimeoutRaw, &l.HTTPIdleTimeout); err != nil { + return fmt.Errorf("error parsing http_idle_timeout: %w", err) } - // Clear raw values after successful parsing. - l.HTTPReadTimeoutRaw = nil - l.HTTPReadHeaderTimeoutRaw = nil - l.HTTPWriteTimeoutRaw = nil - l.HTTPIdleTimeoutRaw = nil - return nil } @@ -524,25 +576,20 @@ func (l *Listener) parseForwardedForSettings() error { if l.XForwardedForHopSkips < 0 { return fmt.Errorf("x_forwarded_for_hop_skips cannot be negative but set to %d", l.XForwardedForHopSkips) } + + l.XForwardedForHopSkipsRaw = nil } - if l.XForwardedForRejectNotAuthorizedRaw != nil { - if l.XForwardedForRejectNotAuthorized, err = parseutil.ParseBool(l.XForwardedForRejectNotAuthorizedRaw); err != nil { - return fmt.Errorf("invalid value for x_forwarded_for_reject_not_authorized: %w", err) - } + if err := parseAndClearBool(&l.XForwardedForRejectNotAuthorizedRaw, &l.XForwardedForRejectNotAuthorized); err != nil { + return fmt.Errorf("invalid value for x_forwarded_for_reject_not_authorized: %w", err) } - if l.XForwardedForRejectNotPresentRaw != nil { - if l.XForwardedForRejectNotPresent, err = parseutil.ParseBool(l.XForwardedForRejectNotPresentRaw); err != nil { - return fmt.Errorf("invalid value for x_forwarded_for_reject_not_present: %w", err) - } + if err := parseAndClearBool(&l.XForwardedForRejectNotPresentRaw, &l.XForwardedForRejectNotPresent); err != nil { + return fmt.Errorf("invalid value for x_forwarded_for_reject_not_present: %w", err) } // Clear raw values after successful parsing. l.XForwardedForAuthorizedAddrsRaw = nil - l.XForwardedForHopSkipsRaw = nil - l.XForwardedForRejectNotAuthorizedRaw = nil - l.XForwardedForRejectNotPresentRaw = nil return nil } @@ -551,16 +598,10 @@ func (l *Listener) parseForwardedForSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseTelemetrySettings() error { - var err error - - if l.Telemetry.UnauthenticatedMetricsAccessRaw != nil { - if l.Telemetry.UnauthenticatedMetricsAccess, err = parseutil.ParseBool(l.Telemetry.UnauthenticatedMetricsAccessRaw); err != nil { - return fmt.Errorf("invalid value for telemetry.unauthenticated_metrics_access: %w", err) - } + if err := parseAndClearBool(&l.Telemetry.UnauthenticatedMetricsAccessRaw, &l.Telemetry.UnauthenticatedMetricsAccess); err != nil { + return fmt.Errorf("invalid value for telemetry.unauthenticated_metrics_access: %w", err) } - l.Telemetry.UnauthenticatedMetricsAccessRaw = nil - return nil } @@ -568,16 +609,10 @@ func (l *Listener) parseTelemetrySettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseProfilingSettings() error { - var err error - - if l.Profiling.UnauthenticatedPProfAccessRaw != nil { - if l.Profiling.UnauthenticatedPProfAccess, err = parseutil.ParseBool(l.Profiling.UnauthenticatedPProfAccessRaw); err != nil { - return fmt.Errorf("invalid value for profiling.unauthenticated_pprof_access: %w", err) - } + if err := parseAndClearBool(&l.Profiling.UnauthenticatedPProfAccessRaw, &l.Profiling.UnauthenticatedPProfAccess); err != nil { + return fmt.Errorf("invalid value for profiling.unauthenticated_pprof_access: %w", err) } - l.Profiling.UnauthenticatedPProfAccessRaw = nil - return nil } @@ -585,16 +620,10 @@ func (l *Listener) parseProfilingSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseInFlightRequestSettings() error { - var err error - - if l.InFlightRequestLogging.UnauthenticatedInFlightAccessRaw != nil { - if l.InFlightRequestLogging.UnauthenticatedInFlightAccess, err = parseutil.ParseBool(l.InFlightRequestLogging.UnauthenticatedInFlightAccessRaw); err != nil { - return fmt.Errorf("invalid value for inflight_requests_logging.unauthenticated_in_flight_requests_access: %w", err) - } + if err := parseAndClearBool(&l.InFlightRequestLogging.UnauthenticatedInFlightAccessRaw, &l.InFlightRequestLogging.UnauthenticatedInFlightAccess); err != nil { + return fmt.Errorf("invalid value for inflight_requests_logging.unauthenticated_in_flight_requests_access: %w", err) } - l.InFlightRequestLogging.UnauthenticatedInFlightAccessRaw = nil - return nil } @@ -602,12 +631,8 @@ func (l *Listener) parseInFlightRequestSettings() error { // The state of the listener will be modified, raw data will be cleared upon // successful parsing. func (l *Listener) parseCORSSettings() error { - var err error - - if l.CorsEnabledRaw != nil { - if l.CorsEnabled, err = parseutil.ParseBool(l.CorsEnabledRaw); err != nil { - return fmt.Errorf("invalid value for cors_enabled: %w", err) - } + if err := parseAndClearBool(&l.CorsEnabledRaw, &l.CorsEnabled); err != nil { + return fmt.Errorf("invalid value for cors_enabled: %w", err) } if strutil.StrListContains(l.CorsAllowedOrigins, "*") && len(l.CorsAllowedOrigins) > 1 { @@ -620,7 +645,6 @@ func (l *Listener) parseCORSSettings() error { } } - l.CorsEnabledRaw = nil l.CorsAllowedHeadersRaw = nil return nil diff --git a/internalshared/configutil/listener_test.go b/internalshared/configutil/listener_test.go index 4ea11d57cc..6205ccf773 100644 --- a/internalshared/configutil/listener_test.go +++ b/internalshared/configutil/listener_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1095,3 +1096,269 @@ func TestListener_parseRedactionSettings(t *testing.T) { }) } } + +func TestParseAndClearBool(t *testing.T) { + testcases := []struct { + name string + raw interface{} + rawAssertion func(assert.TestingT, any, ...any) bool + expectedParsed bool + errorAssertion func(assert.TestingT, error, ...any) bool + }{ + { + name: "valid-true-as-string", + raw: "true", + rawAssertion: assert.Nil, + expectedParsed: true, + errorAssertion: assert.NoError, + }, + { + name: "valid-false-as-string", + raw: "false", + rawAssertion: assert.Nil, + expectedParsed: false, + errorAssertion: assert.NoError, + }, + { + name: "valid-true-as-bool", + raw: true, + rawAssertion: assert.Nil, + expectedParsed: true, + errorAssertion: assert.NoError, + }, + { + name: "valid-false-as-bool", + raw: false, + rawAssertion: assert.Nil, + expectedParsed: false, + errorAssertion: assert.NoError, + }, + { + name: "valid-true-as-string-mix-case", + raw: "True", + rawAssertion: assert.Nil, + expectedParsed: true, + errorAssertion: assert.NoError, + }, + { + name: "valid-false-as-integer", + raw: 0, + rawAssertion: assert.Nil, + expectedParsed: false, + errorAssertion: assert.NoError, + }, + { + name: "valid-true-as-integer", + raw: 2, + rawAssertion: assert.Nil, + expectedParsed: true, + errorAssertion: assert.NoError, + }, + { + name: "valid-true-as-float", + raw: 3.14, + rawAssertion: assert.Nil, + expectedParsed: true, + errorAssertion: assert.NoError, + }, + { + name: "valid-false-as-float", + raw: 0.0, + rawAssertion: assert.Nil, + expectedParsed: false, + errorAssertion: assert.NoError, + }, + { + name: "invalid-as-string", + raw: "0.0.0.0:8200", + rawAssertion: assert.NotNil, + errorAssertion: assert.Error, + }, + { + name: "invalid-as-struct", + raw: struct{}{}, + rawAssertion: assert.NotNil, + errorAssertion: assert.Error, + }, + { + name: "not-set", + raw: nil, + rawAssertion: assert.Nil, + errorAssertion: assert.NoError, + }, + } + + for _, testcase := range testcases { + var parsed bool + err := parseAndClearBool(&testcase.raw, &parsed) + + testcase.errorAssertion(t, err, testcase.name) + assert.Equal(t, testcase.expectedParsed, parsed, testcase.name) + testcase.rawAssertion(t, testcase.raw, testcase.name) + } +} + +func TestParseAndClearString(t *testing.T) { + testcases := []struct { + name string + raw any + rawAssertion func(assert.TestingT, any, ...any) bool + expectedParsed string + errorAssertion func(assert.TestingT, error, ...any) bool + }{ + { + name: "valid-empty-string", + raw: "", + rawAssertion: assert.Nil, + expectedParsed: "", + errorAssertion: assert.NoError, + }, + { + name: "valid-some-string", + raw: "blah blah", + rawAssertion: assert.Nil, + expectedParsed: "blah blah", + errorAssertion: assert.NoError, + }, + { + name: "valid-as-integer", + raw: 8, + rawAssertion: assert.Nil, + expectedParsed: "8", + errorAssertion: assert.NoError, + }, + { + name: "valid-as-bool", + raw: true, + rawAssertion: assert.Nil, + expectedParsed: "1", + errorAssertion: assert.NoError, + }, + { + name: "not-set", + raw: nil, + rawAssertion: assert.Nil, + expectedParsed: "", + errorAssertion: assert.NoError, + }, + { + name: "invalid-as-struct", + raw: struct{}{}, + rawAssertion: assert.NotNil, + errorAssertion: assert.Error, + }, + } + for _, testcase := range testcases { + var parsed string + err := parseAndClearString(&testcase.raw, &parsed) + + testcase.errorAssertion(t, err, testcase.name) + assert.Equal(t, testcase.expectedParsed, parsed, testcase.name) + testcase.rawAssertion(t, testcase.raw, testcase.name) + } +} + +func TestParseAndClearInt(t *testing.T) { + testcases := []struct { + name string + raw any + rawAssertion func(assert.TestingT, any, ...any) bool + expectedParsed int64 + errorAssertion func(assert.TestingT, error, ...any) bool + }{ + { + name: "valid-as-int", + raw: 200, + rawAssertion: assert.Nil, + expectedParsed: int64(200), + errorAssertion: assert.NoError, + }, + { + name: "valid-as-string", + raw: "53", + rawAssertion: assert.Nil, + expectedParsed: int64(53), + errorAssertion: assert.NoError, + }, + { + name: "invalid-as-hex-string", + raw: "0xa", + rawAssertion: assert.NotNil, + errorAssertion: assert.Error, + }, + { + name: "not-set", + raw: nil, + rawAssertion: assert.Nil, + errorAssertion: assert.NoError, + }, + } + + for _, testcase := range testcases { + var parsed int64 + err := parseAndClearInt(&testcase.raw, &parsed) + + testcase.errorAssertion(t, err, testcase.name) + assert.Equal(t, testcase.expectedParsed, parsed, testcase.name) + testcase.rawAssertion(t, testcase.raw, testcase.name) + } +} + +func TestParseAndClearDurationSecond(t *testing.T) { + testcases := []struct { + name string + raw any + rawAssertion func(assert.TestingT, any, ...any) bool + expectedParsed time.Duration + errorAssertion func(assert.TestingT, error, ...any) bool + }{ + { + name: "valid-as-string", + raw: "30s", + rawAssertion: assert.Nil, + expectedParsed: time.Duration(30 * time.Second), + errorAssertion: assert.NoError, + }, + { + name: "valid-as-string-more-complex", + raw: "29h24m49s", + rawAssertion: assert.Nil, + expectedParsed: time.Duration((29 * time.Hour) + (24 * time.Minute) + (49 * time.Second)), + errorAssertion: assert.NoError, + }, + { + name: "invalid-as-string-using-days", + raw: "1d3s", + rawAssertion: assert.NotNil, + errorAssertion: assert.Error, + }, + { + name: "valid-as-integer", + raw: 87, + rawAssertion: assert.Nil, + expectedParsed: time.Duration(87 * time.Second), + errorAssertion: assert.NoError, + }, + { + name: "not-set", + raw: nil, + rawAssertion: assert.Nil, + errorAssertion: assert.NoError, + }, + { + name: "invalid-as-struct", + raw: struct{}{}, + rawAssertion: assert.NotNil, + errorAssertion: assert.Error, + }, + } + + for _, testcase := range testcases { + var parsed time.Duration + + err := parseAndClearDurationSecond(&testcase.raw, &parsed) + testcase.errorAssertion(t, err, testcase.name) + assert.Equal(t, testcase.expectedParsed, parsed) + testcase.rawAssertion(t, testcase.raw, testcase.name) + } +} diff --git a/website/content/docs/configuration/listener/tcp.mdx b/website/content/docs/configuration/listener/tcp.mdx index 6c814a5cf5..e9286eb666 100644 --- a/website/content/docs/configuration/listener/tcp.mdx +++ b/website/content/docs/configuration/listener/tcp.mdx @@ -202,6 +202,9 @@ default value in the `"/sys/config/ui"` [API endpoint](/vault/api-docs/system/co there is no X-Forwarded-For header or it is empty, the client address will be used as-is, rather than the client connection rejected. +- `disable_replication_status_endpoints` `(bool: false)` - Disables replication + status endpoints for the configured listener when set to `true`. + ### `telemetry` parameters - `unauthenticated_metrics_access` `(bool: false)` - If set to true, allows