2023-03-15 16:00:52 +00:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2020-05-14 13:19:27 +00:00
package configutil
import (
"errors"
"fmt"
2020-05-21 01:56:12 +00:00
"net/textproto"
2022-04-29 13:57:17 +00:00
"regexp"
2020-05-14 13:19:27 +00:00
"strings"
"time"
"github.com/hashicorp/go-multierror"
2021-07-16 00:17:31 +00:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/hashicorp/go-secure-stdlib/tlsutil"
2020-05-14 13:19:27 +00:00
"github.com/hashicorp/go-sockaddr"
2021-10-21 14:10:48 +00:00
"github.com/hashicorp/go-sockaddr/template"
2020-05-14 13:19:27 +00:00
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
)
type ListenerTelemetry struct {
2021-06-17 18:09:37 +00:00
UnusedKeys UnusedKeyMap ` hcl:",unusedKeyPositions" `
UnauthenticatedMetricsAccess bool ` hcl:"-" `
UnauthenticatedMetricsAccessRaw interface { } ` hcl:"unauthenticated_metrics_access,alias:UnauthenticatedMetricsAccess" `
2020-05-14 13:19:27 +00:00
}
2021-04-19 18:30:59 +00:00
type ListenerProfiling struct {
2022-01-27 18:06:34 +00:00
UnusedKeys UnusedKeyMap ` hcl:",unusedKeyPositions" `
UnauthenticatedPProfAccess bool ` hcl:"-" `
UnauthenticatedPProfAccessRaw interface { } ` hcl:"unauthenticated_pprof_access,alias:UnauthenticatedPProfAccessRaw" `
2021-12-08 22:34:42 +00:00
}
type ListenerInFlightRequestLogging struct {
UnusedKeys UnusedKeyMap ` hcl:",unusedKeyPositions" `
UnauthenticatedInFlightAccess bool ` hcl:"-" `
UnauthenticatedInFlightAccessRaw interface { } ` hcl:"unauthenticated_in_flight_requests_access,alias:unauthenticatedInFlightAccessRaw" `
2021-04-19 18:30:59 +00:00
}
2020-05-14 13:19:27 +00:00
// Listener is the listener configuration for the server.
type Listener struct {
2021-06-17 18:09:37 +00:00
UnusedKeys UnusedKeyMap ` hcl:",unusedKeyPositions" `
2021-05-04 19:47:56 +00:00
RawConfig map [ string ] interface { }
2020-05-14 13:19:27 +00:00
Type string
Purpose [ ] string ` hcl:"-" `
PurposeRaw interface { } ` hcl:"purpose" `
2022-11-25 21:00:56 +00:00
Role string ` hcl:"role" `
2020-05-14 13:19:27 +00:00
Address string ` hcl:"address" `
ClusterAddress string ` hcl:"cluster_address" `
MaxRequestSize int64 ` hcl:"-" `
MaxRequestSizeRaw interface { } ` hcl:"max_request_size" `
MaxRequestDuration time . Duration ` hcl:"-" `
MaxRequestDurationRaw interface { } ` hcl:"max_request_duration" `
RequireRequestHeader bool ` hcl:"-" `
RequireRequestHeaderRaw interface { } ` hcl:"require_request_header" `
TLSDisable bool ` hcl:"-" `
TLSDisableRaw interface { } ` hcl:"tls_disable" `
TLSCertFile string ` hcl:"tls_cert_file" `
TLSKeyFile string ` hcl:"tls_key_file" `
TLSMinVersion string ` hcl:"tls_min_version" `
2021-03-29 18:39:14 +00:00
TLSMaxVersion string ` hcl:"tls_max_version" `
2020-05-14 13:19:27 +00:00
TLSCipherSuites [ ] uint16 ` hcl:"-" `
TLSCipherSuitesRaw string ` hcl:"tls_cipher_suites" `
TLSRequireAndVerifyClientCert bool ` hcl:"-" `
TLSRequireAndVerifyClientCertRaw interface { } ` hcl:"tls_require_and_verify_client_cert" `
TLSClientCAFile string ` hcl:"tls_client_ca_file" `
TLSDisableClientCerts bool ` hcl:"-" `
TLSDisableClientCertsRaw interface { } ` hcl:"tls_disable_client_certs" `
HTTPReadTimeout time . Duration ` hcl:"-" `
HTTPReadTimeoutRaw interface { } ` hcl:"http_read_timeout" `
HTTPReadHeaderTimeout time . Duration ` hcl:"-" `
HTTPReadHeaderTimeoutRaw interface { } ` hcl:"http_read_header_timeout" `
HTTPWriteTimeout time . Duration ` hcl:"-" `
HTTPWriteTimeoutRaw interface { } ` hcl:"http_write_timeout" `
HTTPIdleTimeout time . Duration ` hcl:"-" `
HTTPIdleTimeoutRaw interface { } ` hcl:"http_idle_timeout" `
ProxyProtocolBehavior string ` hcl:"proxy_protocol_behavior" `
ProxyProtocolAuthorizedAddrs [ ] * sockaddr . SockAddrMarshaler ` hcl:"-" `
2021-06-17 18:09:37 +00:00
ProxyProtocolAuthorizedAddrsRaw interface { } ` hcl:"proxy_protocol_authorized_addrs,alias:ProxyProtocolAuthorizedAddrs" `
2020-05-14 13:19:27 +00:00
XForwardedForAuthorizedAddrs [ ] * sockaddr . SockAddrMarshaler ` hcl:"-" `
2021-06-17 18:09:37 +00:00
XForwardedForAuthorizedAddrsRaw interface { } ` hcl:"x_forwarded_for_authorized_addrs,alias:XForwardedForAuthorizedAddrs" `
2020-05-14 13:19:27 +00:00
XForwardedForHopSkips int64 ` hcl:"-" `
2021-06-17 18:09:37 +00:00
XForwardedForHopSkipsRaw interface { } ` hcl:"x_forwarded_for_hop_skips,alias:XForwardedForHopSkips" `
2020-05-14 13:19:27 +00:00
XForwardedForRejectNotPresent bool ` hcl:"-" `
2021-06-17 18:09:37 +00:00
XForwardedForRejectNotPresentRaw interface { } ` hcl:"x_forwarded_for_reject_not_present,alias:XForwardedForRejectNotPresent" `
2020-05-14 13:19:27 +00:00
XForwardedForRejectNotAuthorized bool ` hcl:"-" `
2021-06-17 18:09:37 +00:00
XForwardedForRejectNotAuthorizedRaw interface { } ` hcl:"x_forwarded_for_reject_not_authorized,alias:XForwardedForRejectNotAuthorized" `
2020-05-14 13:19:27 +00:00
SocketMode string ` hcl:"socket_mode" `
SocketUser string ` hcl:"socket_user" `
SocketGroup string ` hcl:"socket_group" `
2022-02-25 10:29:05 +00:00
AgentAPI * AgentAPI ` hcl:"agent_api" `
2023-05-17 13:38:34 +00:00
ProxyAPI * ProxyAPI ` hcl:"proxy_api" `
2021-12-08 22:34:42 +00:00
Telemetry ListenerTelemetry ` hcl:"telemetry" `
Profiling ListenerProfiling ` hcl:"profiling" `
InFlightRequestLogging ListenerInFlightRequestLogging ` hcl:"inflight_requests_logging" `
2020-05-14 13:19:27 +00:00
// RandomPort is used only for some testing purposes
RandomPort bool ` hcl:"-" `
2020-05-21 01:56:12 +00:00
CorsEnabledRaw interface { } ` hcl:"cors_enabled" `
CorsEnabled bool ` hcl:"-" `
CorsAllowedOrigins [ ] string ` hcl:"cors_allowed_origins" `
CorsAllowedHeaders [ ] string ` hcl:"-" `
2021-06-17 18:09:37 +00:00
CorsAllowedHeadersRaw [ ] string ` hcl:"cors_allowed_headers,alias:cors_allowed_headers" `
2021-10-13 15:06:33 +00:00
// Custom Http response headers
CustomResponseHeaders map [ string ] map [ string ] string ` hcl:"-" `
CustomResponseHeadersRaw interface { } ` hcl:"custom_response_headers" `
2020-05-14 13:19:27 +00:00
}
2022-02-25 10:29:05 +00:00
// AgentAPI allows users to select which parts of the Agent API they want enabled.
type AgentAPI struct {
EnableQuit bool ` hcl:"enable_quit" `
}
2023-05-17 13:38:34 +00:00
// ProxyAPI allows users to select which parts of the Vault Proxy API they want enabled.
type ProxyAPI struct {
EnableQuit bool ` hcl:"enable_quit" `
}
2020-05-14 13:19:27 +00:00
func ( l * Listener ) GoString ( ) string {
return fmt . Sprintf ( "*%#v" , * l )
}
2021-06-17 18:09:37 +00:00
func ( l * Listener ) Validate ( path string ) [ ] ConfigError {
results := append ( ValidateUnusedFields ( l . UnusedKeys , path ) , ValidateUnusedFields ( l . Telemetry . UnusedKeys , path ) ... )
return append ( results , ValidateUnusedFields ( l . Profiling . UnusedKeys , path ) ... )
}
2020-05-14 13:19:27 +00:00
func ParseListeners ( result * SharedConfig , list * ast . ObjectList ) error {
var err error
result . Listeners = make ( [ ] * Listener , 0 , len ( list . Items ) )
for i , item := range list . Items {
var l Listener
if err := hcl . DecodeObject ( & l , item . Val ) ; err != nil {
return multierror . Prefix ( err , fmt . Sprintf ( "listeners.%d:" , i ) )
}
2021-10-21 14:10:48 +00:00
if rendered , err := ParseSingleIPTemplate ( l . Address ) ; err != nil {
return multierror . Prefix ( err , fmt . Sprintf ( "listeners.%d:" , i ) )
} else {
l . Address = rendered
}
if rendered , err := ParseSingleIPTemplate ( l . ClusterAddress ) ; err != nil {
return multierror . Prefix ( err , fmt . Sprintf ( "listeners.%d:" , i ) )
} else {
l . ClusterAddress = rendered
}
2020-05-14 13:19:27 +00:00
// Hacky way, for now, to get the values we want for sanitizing
var m map [ string ] interface { }
if err := hcl . DecodeObject ( & m , item . Val ) ; err != nil {
return multierror . Prefix ( err , fmt . Sprintf ( "listeners.%d:" , i ) )
}
l . RawConfig = m
// Base values
{
switch {
case l . Type != "" :
case len ( item . Keys ) == 1 :
l . Type = strings . ToLower ( item . Keys [ 0 ] . Token . Value ( ) . ( string ) )
default :
return multierror . Prefix ( errors . New ( "listener type must be specified" ) , fmt . Sprintf ( "listeners.%d:" , i ) )
}
l . Type = strings . ToLower ( l . Type )
switch l . Type {
case "tcp" , "unix" :
2022-05-12 16:04:56 +00:00
result . found ( l . Type , l . Type )
2020-05-14 13:19:27 +00:00
default :
return multierror . Prefix ( fmt . Errorf ( "unsupported listener type %q" , l . Type ) , fmt . Sprintf ( "listeners.%d:" , i ) )
}
if l . PurposeRaw != nil {
if l . Purpose , err = parseutil . ParseCommaStringSlice ( l . PurposeRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "unable to parse 'purpose' in listener type %q: %w" , l . Type , err ) , fmt . Sprintf ( "listeners.%d:" , i ) )
}
for i , v := range l . Purpose {
l . Purpose [ i ] = strings . ToLower ( v )
}
2020-05-21 01:56:12 +00:00
l . PurposeRaw = nil
2020-05-14 13:19:27 +00:00
}
2022-11-25 21:00:56 +00:00
switch l . Role {
case "default" , "metrics_only" , "" :
result . found ( l . Type , l . Type )
default :
return multierror . Prefix ( fmt . Errorf ( "unsupported listener role %q" , l . Role ) , fmt . Sprintf ( "listeners.%d:" , i ) )
}
2020-05-14 13:19:27 +00:00
}
// Request Parameters
{
if l . MaxRequestSizeRaw != nil {
if l . MaxRequestSize , err = parseutil . ParseInt ( l . MaxRequestSizeRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing max_request_size: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . MaxRequestSizeRaw = nil
}
if l . MaxRequestDurationRaw != nil {
if l . MaxRequestDuration , err = parseutil . ParseDurationSecond ( l . MaxRequestDurationRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing max_request_duration: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
if l . MaxRequestDuration < 0 {
return multierror . Prefix ( errors . New ( "max_request_duration cannot be negative" ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . MaxRequestDurationRaw = nil
}
if l . RequireRequestHeaderRaw != nil {
if l . RequireRequestHeader , err = parseutil . ParseBool ( l . RequireRequestHeaderRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for require_request_header: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . RequireRequestHeaderRaw = nil
}
}
// TLS Parameters
{
if l . TLSDisableRaw != nil {
if l . TLSDisable , err = parseutil . ParseBool ( l . TLSDisableRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for tls_disable: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . TLSDisableRaw = nil
}
if l . TLSCipherSuitesRaw != "" {
if l . TLSCipherSuites , err = tlsutil . ParseCiphers ( l . TLSCipherSuitesRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for tls_cipher_suites: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
}
if l . TLSRequireAndVerifyClientCertRaw != nil {
if l . TLSRequireAndVerifyClientCert , err = parseutil . ParseBool ( l . TLSRequireAndVerifyClientCertRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for tls_require_and_verify_client_cert: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . TLSRequireAndVerifyClientCertRaw = nil
}
if l . TLSDisableClientCertsRaw != nil {
if l . TLSDisableClientCerts , err = parseutil . ParseBool ( l . TLSDisableClientCertsRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for tls_disable_client_certs: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . TLSDisableClientCertsRaw = nil
}
}
// HTTP timeouts
{
if l . HTTPReadTimeoutRaw != nil {
if l . HTTPReadTimeout , err = parseutil . ParseDurationSecond ( l . HTTPReadTimeoutRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing http_read_timeout: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . HTTPReadTimeoutRaw = nil
}
if l . HTTPReadHeaderTimeoutRaw != nil {
if l . HTTPReadHeaderTimeout , err = parseutil . ParseDurationSecond ( l . HTTPReadHeaderTimeoutRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing http_read_header_timeout: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . HTTPReadHeaderTimeoutRaw = nil
}
if l . HTTPWriteTimeoutRaw != nil {
if l . HTTPWriteTimeout , err = parseutil . ParseDurationSecond ( l . HTTPWriteTimeoutRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing http_write_timeout: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . HTTPWriteTimeoutRaw = nil
}
if l . HTTPIdleTimeoutRaw != nil {
if l . HTTPIdleTimeout , err = parseutil . ParseDurationSecond ( l . HTTPIdleTimeoutRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing http_idle_timeout: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . HTTPIdleTimeoutRaw = nil
}
}
// Proxy Protocol config
{
if l . ProxyProtocolAuthorizedAddrsRaw != nil {
if l . ProxyProtocolAuthorizedAddrs , err = parseutil . ParseAddrs ( l . ProxyProtocolAuthorizedAddrsRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing proxy_protocol_authorized_addrs: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
switch l . ProxyProtocolBehavior {
case "allow_authorized" , "deny_authorized" :
if len ( l . ProxyProtocolAuthorizedAddrs ) == 0 {
return multierror . Prefix ( errors . New ( "proxy_protocol_behavior set to allow or deny only authorized addresses but no proxy_protocol_authorized_addrs value" ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
}
l . ProxyProtocolAuthorizedAddrsRaw = nil
}
}
// X-Forwarded-For config
{
if l . XForwardedForAuthorizedAddrsRaw != nil {
if l . XForwardedForAuthorizedAddrs , err = parseutil . ParseAddrs ( l . XForwardedForAuthorizedAddrsRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing x_forwarded_for_authorized_addrs: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . XForwardedForAuthorizedAddrsRaw = nil
}
if l . XForwardedForHopSkipsRaw != nil {
if l . XForwardedForHopSkips , err = parseutil . ParseInt ( l . XForwardedForHopSkipsRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "error parsing x_forwarded_for_hop_skips: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
if l . XForwardedForHopSkips < 0 {
return multierror . Prefix ( fmt . Errorf ( "x_forwarded_for_hop_skips cannot be negative but set to %d" , l . XForwardedForHopSkips ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . XForwardedForHopSkipsRaw = nil
}
if l . XForwardedForRejectNotAuthorizedRaw != nil {
if l . XForwardedForRejectNotAuthorized , err = parseutil . ParseBool ( l . XForwardedForRejectNotAuthorizedRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for x_forwarded_for_reject_not_authorized: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . XForwardedForRejectNotAuthorizedRaw = nil
}
if l . XForwardedForRejectNotPresentRaw != nil {
if l . XForwardedForRejectNotPresent , err = parseutil . ParseBool ( l . XForwardedForRejectNotPresentRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for x_forwarded_for_reject_not_present: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . XForwardedForRejectNotPresentRaw = nil
}
}
// Telemetry
{
if l . Telemetry . UnauthenticatedMetricsAccessRaw != nil {
if l . Telemetry . UnauthenticatedMetricsAccess , err = parseutil . ParseBool ( l . Telemetry . UnauthenticatedMetricsAccessRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for telemetry.unauthenticated_metrics_access: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . Telemetry . UnauthenticatedMetricsAccessRaw = nil
}
}
2021-04-19 18:30:59 +00:00
// Profiling
{
if l . Profiling . UnauthenticatedPProfAccessRaw != nil {
if l . Profiling . UnauthenticatedPProfAccess , err = parseutil . ParseBool ( l . Profiling . UnauthenticatedPProfAccessRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for profiling.unauthenticated_pprof_access: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . Profiling . UnauthenticatedPProfAccessRaw = nil
}
}
2021-12-08 22:34:42 +00:00
// InFlight Request logging
{
if l . InFlightRequestLogging . UnauthenticatedInFlightAccessRaw != nil {
if l . InFlightRequestLogging . UnauthenticatedInFlightAccess , err = parseutil . ParseBool ( l . InFlightRequestLogging . UnauthenticatedInFlightAccessRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for inflight_requests_logging.unauthenticated_in_flight_requests_access: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . InFlightRequestLogging . UnauthenticatedInFlightAccessRaw = ""
}
}
2020-05-21 01:56:12 +00:00
// CORS
{
if l . CorsEnabledRaw != nil {
if l . CorsEnabled , err = parseutil . ParseBool ( l . CorsEnabledRaw ) ; err != nil {
return multierror . Prefix ( fmt . Errorf ( "invalid value for cors_enabled: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . CorsEnabledRaw = nil
}
if strutil . StrListContains ( l . CorsAllowedOrigins , "*" ) && len ( l . CorsAllowedOrigins ) > 1 {
return multierror . Prefix ( errors . New ( "cors_allowed_origins must only contain a wildcard or only non-wildcard values" ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
if len ( l . CorsAllowedHeadersRaw ) > 0 {
for _ , header := range l . CorsAllowedHeadersRaw {
l . CorsAllowedHeaders = append ( l . CorsAllowedHeaders , textproto . CanonicalMIMEHeaderKey ( header ) )
}
}
}
2021-10-13 15:06:33 +00:00
// HTTP Headers
{
// if CustomResponseHeadersRaw is nil, we still need to set the default headers
customHeadersMap , err := ParseCustomResponseHeaders ( l . CustomResponseHeadersRaw )
if err != nil {
return multierror . Prefix ( fmt . Errorf ( "failed to parse custom_response_headers: %w" , err ) , fmt . Sprintf ( "listeners.%d" , i ) )
}
l . CustomResponseHeaders = customHeadersMap
l . CustomResponseHeadersRaw = nil
}
2020-05-14 13:19:27 +00:00
result . Listeners = append ( result . Listeners , & l )
}
return nil
}
2021-10-21 14:10:48 +00:00
// ParseSingleIPTemplate is used as a helper function to parse out a single IP
// address from a config parameter.
2022-04-29 13:57:17 +00:00
// If the input doesn't appear to contain the 'template' format,
// it will return the specified input unchanged.
2021-10-21 14:10:48 +00:00
func ParseSingleIPTemplate ( ipTmpl string ) ( string , error ) {
2022-04-29 13:57:17 +00:00
r := regexp . MustCompile ( "{{.*?}}" )
if ! r . MatchString ( ipTmpl ) {
return ipTmpl , nil
}
2021-10-21 14:10:48 +00:00
out , err := template . Parse ( ipTmpl )
if err != nil {
return "" , fmt . Errorf ( "unable to parse address template %q: %v" , ipTmpl , err )
}
ips := strings . Split ( out , " " )
switch len ( ips ) {
case 0 :
return "" , errors . New ( "no addresses found, please configure one" )
case 1 :
return strings . TrimSpace ( ips [ 0 ] ) , nil
default :
return "" , fmt . Errorf ( "multiple addresses found (%q), please configure one" , out )
}
}