2023-03-28 18:39:22 +00:00
// Copyright (c) HashiCorp, Inc.
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-03-28 18:39:22 +00:00
2021-04-06 18:19:59 +00:00
package structs
import (
2021-04-29 21:44:32 +00:00
"encoding/json"
2021-04-06 18:19:59 +00:00
"fmt"
2024-10-16 16:23:33 +00:00
"strings"
2021-04-06 18:19:59 +00:00
"github.com/hashicorp/consul/acl"
2022-03-30 18:43:59 +00:00
"github.com/hashicorp/consul/types"
2021-04-06 18:19:59 +00:00
)
2021-04-28 22:13:29 +00:00
type MeshConfigEntry struct {
2021-04-06 18:19:59 +00:00
// TransparentProxy contains cluster-wide options pertaining to TPROXY mode
// when enabled.
2021-04-28 22:13:29 +00:00
TransparentProxy TransparentProxyMeshConfig ` alias:"transparent_proxy" `
2021-04-06 18:19:59 +00:00
2023-04-19 19:45:00 +00:00
// AllowEnablingPermissiveMutualTLS must be true in order to allow setting
// MutualTLSMode=permissive in either service-defaults or proxy-defaults.
AllowEnablingPermissiveMutualTLS bool ` json:",omitempty" alias:"allow_enabling_permissive_mutual_tls" `
2024-08-20 05:39:28 +00:00
// ValidateClusters controls whether the clusters the route table refers to are validated. The default value is
// false. When set to false and a route refers to a cluster that does not exist, the route table loads and routing
// to a non-existent cluster results in a 404. When set to true and the route is set to a cluster that do not exist,
// the route table will not load. For more information, refer to
// [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters)
// for more details.
ValidateClusters bool ` json:",omitempty" alias:"validate_clusters" `
2022-03-30 18:43:59 +00:00
TLS * MeshTLSConfig ` json:",omitempty" `
2022-04-26 20:46:29 +00:00
HTTP * MeshHTTPConfig ` json:",omitempty" `
2022-09-02 20:52:11 +00:00
Peering * PeeringMeshConfig ` json:",omitempty" `
2022-03-13 03:55:53 +00:00
Meta map [ string ] string ` json:",omitempty" `
2023-12-12 13:29:13 +00:00
Hash uint64 ` json:",omitempty" hash:"ignore" `
2022-03-13 03:55:53 +00:00
acl . EnterpriseMeta ` hcl:",squash" mapstructure:",squash" `
2023-12-12 13:29:13 +00:00
RaftIndex ` hash:"ignore" `
}
func ( e * MeshConfigEntry ) SetHash ( h uint64 ) {
e . Hash = h
}
func ( e * MeshConfigEntry ) GetHash ( ) uint64 {
return e . Hash
2021-04-06 18:19:59 +00:00
}
2021-04-28 22:13:29 +00:00
// TransparentProxyMeshConfig contains cluster-wide options pertaining to
2021-04-06 18:19:59 +00:00
// TPROXY mode when enabled.
2021-04-28 22:13:29 +00:00
type TransparentProxyMeshConfig struct {
2021-06-14 20:15:09 +00:00
// MeshDestinationsOnly can be used to disable the pass-through that
2021-04-06 18:19:59 +00:00
// allows traffic to destinations outside of the mesh.
2021-06-14 20:15:09 +00:00
MeshDestinationsOnly bool ` alias:"mesh_destinations_only" `
2021-04-06 18:19:59 +00:00
}
2022-03-30 18:43:59 +00:00
type MeshTLSConfig struct {
Incoming * MeshDirectionalTLSConfig ` json:",omitempty" `
Outgoing * MeshDirectionalTLSConfig ` json:",omitempty" `
}
type MeshDirectionalTLSConfig struct {
TLSMinVersion types . TLSVersion ` json:",omitempty" alias:"tls_min_version" `
TLSMaxVersion types . TLSVersion ` json:",omitempty" alias:"tls_max_version" `
// Define a subset of cipher suites to restrict
// Only applicable to connections negotiated via TLS 1.2 or earlier
CipherSuites [ ] types . TLSCipherSuite ` json:",omitempty" alias:"cipher_suites" `
}
2022-05-02 16:35:34 +00:00
type MeshHTTPConfig struct {
SanitizeXForwardedClientCert bool ` alias:"sanitize_x_forwarded_client_cert" `
2024-10-16 16:23:33 +00:00
// Incoming configures settings for incoming HTTP traffic to mesh proxies.
Incoming * MeshDirectionalHTTPConfig ` json:",omitempty" `
// There is not currently an outgoing MeshDirectionalHTTPConfig, as
// the only required config for either direction at present is inbound
// request normalization.
}
// MeshDirectionalHTTPConfig holds mesh configuration specific to HTTP
// requests for a given traffic direction.
type MeshDirectionalHTTPConfig struct {
RequestNormalization * RequestNormalizationMeshConfig ` json:",omitempty" alias:"request_normalization" `
2022-05-02 16:35:34 +00:00
}
2022-09-02 20:52:11 +00:00
// PeeringMeshConfig contains cluster-wide options pertaining to peering.
type PeeringMeshConfig struct {
// PeerThroughMeshGateways determines whether peering traffic between
// control planes should flow through mesh gateways. If enabled,
// Consul servers will advertise mesh gateway addresses as their own.
// Additionally, mesh gateways will configure themselves to expose
// the local servers using a peering-specific SNI.
PeerThroughMeshGateways bool ` alias:"peer_through_mesh_gateways" `
}
2024-10-16 16:23:33 +00:00
// RequestNormalizationMeshConfig contains options pertaining to the
// normalization of HTTP requests processed by mesh proxies.
type RequestNormalizationMeshConfig struct {
// InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's
// `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is
// set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to
// RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions
// with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with
// non-normalized path values.
InsecureDisablePathNormalization bool ` alias:"insecure_disable_path_normalization" `
// MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`.
// The default value is \`false\`. This option controls the normalization of request URL paths by merging
// consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path
// match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path
// values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services
// are configured to differentiate between single and multiple slashes.
MergeSlashes bool ` alias:"merge_slashes" `
// PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy
// listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to
// \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths
// with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this
// setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic
// depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate
// between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available
// options.
PathWithEscapedSlashesAction PathWithEscapedSlashesAction ` alias:"path_with_escaped_slashes_action" `
// HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy
// listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is
// empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available
// options.
HeadersWithUnderscoresAction HeadersWithUnderscoresAction ` alias:"headers_with_underscores_action" `
}
// PathWithEscapedSlashesAction is an enum that defines the action to take when
// a request path contains escaped slashes. It mirrors exactly the set of options
// in Envoy's UriPathNormalizationOptions.PathWithEscapedSlashesAction enum.
type PathWithEscapedSlashesAction string
// See github.com/envoyproxy/go-control-plane envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction.
const (
PathWithEscapedSlashesActionDefault PathWithEscapedSlashesAction = "IMPLEMENTATION_SPECIFIC_DEFAULT"
PathWithEscapedSlashesActionKeep PathWithEscapedSlashesAction = "KEEP_UNCHANGED"
PathWithEscapedSlashesActionReject PathWithEscapedSlashesAction = "REJECT_REQUEST"
PathWithEscapedSlashesActionUnescapeAndRedirect PathWithEscapedSlashesAction = "UNESCAPE_AND_REDIRECT"
PathWithEscapedSlashesActionUnescapeAndForward PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD"
)
// PathWithEscapedSlashesActionStrings returns an ordered slice of all PathWithEscapedSlashesAction values as strings.
func PathWithEscapedSlashesActionStrings ( ) [ ] string {
return [ ] string {
string ( PathWithEscapedSlashesActionDefault ) ,
string ( PathWithEscapedSlashesActionKeep ) ,
string ( PathWithEscapedSlashesActionReject ) ,
string ( PathWithEscapedSlashesActionUnescapeAndRedirect ) ,
string ( PathWithEscapedSlashesActionUnescapeAndForward ) ,
}
}
// pathWithEscapedSlashesActions contains the canonical set of PathWithEscapedSlashesActionValues values.
var pathWithEscapedSlashesActions = ( func ( ) map [ PathWithEscapedSlashesAction ] struct { } {
m := make ( map [ PathWithEscapedSlashesAction ] struct { } )
for _ , v := range PathWithEscapedSlashesActionStrings ( ) {
m [ PathWithEscapedSlashesAction ( v ) ] = struct { } { }
}
return m
} ) ( )
// HeadersWithUnderscoresAction is an enum that defines the action to take when
// a request contains headers with underscores. It mirrors exactly the set of
// options in Envoy's HttpProtocolOptions.HeadersWithUnderscoresAction enum.
type HeadersWithUnderscoresAction string
// See github.com/envoyproxy/go-control-plane envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction.
const (
HeadersWithUnderscoresActionAllow HeadersWithUnderscoresAction = "ALLOW"
HeadersWithUnderscoresActionRejectRequest HeadersWithUnderscoresAction = "REJECT_REQUEST"
HeadersWithUnderscoresActionDropHeader HeadersWithUnderscoresAction = "DROP_HEADER"
)
// HeadersWithUnderscoresActionStrings returns an ordered slice of all HeadersWithUnderscoresAction values as strings
// for use in returning validation errors.
func HeadersWithUnderscoresActionStrings ( ) [ ] string {
return [ ] string {
string ( HeadersWithUnderscoresActionAllow ) ,
string ( HeadersWithUnderscoresActionRejectRequest ) ,
string ( HeadersWithUnderscoresActionDropHeader ) ,
}
}
// headersWithUnderscoresActions contains the canonical set of HeadersWithUnderscoresAction values.
var headersWithUnderscoresActions = ( func ( ) map [ HeadersWithUnderscoresAction ] struct { } {
m := make ( map [ HeadersWithUnderscoresAction ] struct { } )
for _ , v := range HeadersWithUnderscoresActionStrings ( ) {
m [ HeadersWithUnderscoresAction ( v ) ] = struct { } { }
}
return m
} ) ( )
2021-04-28 22:13:29 +00:00
func ( e * MeshConfigEntry ) GetKind ( ) string {
return MeshConfig
2021-04-06 18:19:59 +00:00
}
2021-04-28 22:13:29 +00:00
func ( e * MeshConfigEntry ) GetName ( ) string {
2021-04-06 18:19:59 +00:00
if e == nil {
return ""
}
2021-04-29 19:54:27 +00:00
return MeshConfigMesh
2021-04-06 18:19:59 +00:00
}
2021-04-28 22:13:29 +00:00
func ( e * MeshConfigEntry ) GetMeta ( ) map [ string ] string {
2021-04-06 18:19:59 +00:00
if e == nil {
return nil
}
return e . Meta
}
2021-04-28 22:13:29 +00:00
func ( e * MeshConfigEntry ) Normalize ( ) error {
2021-04-06 18:19:59 +00:00
if e == nil {
return fmt . Errorf ( "config entry is nil" )
}
e . EnterpriseMeta . Normalize ( )
2023-12-12 13:29:13 +00:00
h , err := HashConfigEntry ( e )
if err != nil {
return err
}
e . Hash = h
2021-04-06 18:19:59 +00:00
return nil
}
2021-04-28 22:13:29 +00:00
func ( e * MeshConfigEntry ) Validate ( ) error {
2021-04-06 18:19:59 +00:00
if e == nil {
return fmt . Errorf ( "config entry is nil" )
}
2022-03-30 18:43:59 +00:00
2021-04-06 18:19:59 +00:00
if err := validateConfigEntryMeta ( e . Meta ) ; err != nil {
return err
}
2022-03-30 18:43:59 +00:00
if e . TLS != nil {
if e . TLS . Incoming != nil {
if err := validateMeshDirectionalTLSConfig ( e . TLS . Incoming ) ; err != nil {
return fmt . Errorf ( "error in incoming TLS configuration: %v" , err )
}
}
if e . TLS . Outgoing != nil {
if err := validateMeshDirectionalTLSConfig ( e . TLS . Outgoing ) ; err != nil {
return fmt . Errorf ( "error in outgoing TLS configuration: %v" , err )
}
}
}
2024-10-16 16:23:33 +00:00
if err := validateRequestNormalizationMeshConfig ( e . GetHTTPIncomingRequestNormalization ( ) ) ; err != nil {
return fmt . Errorf ( "error in HTTP incoming request normalization configuration: %v" , err )
}
2021-04-06 18:19:59 +00:00
return e . validateEnterpriseMeta ( )
}
2022-03-11 21:45:51 +00:00
func ( e * MeshConfigEntry ) CanRead ( authz acl . Authorizer ) error {
return nil
2021-04-06 18:19:59 +00:00
}
2022-03-11 21:45:51 +00:00
func ( e * MeshConfigEntry ) CanWrite ( authz acl . Authorizer ) error {
2021-04-06 18:19:59 +00:00
var authzContext acl . AuthorizerContext
e . FillAuthzContext ( & authzContext )
2022-03-11 21:45:51 +00:00
return authz . ToAllowAuthorizer ( ) . MeshWriteAllowed ( & authzContext )
2021-04-06 18:19:59 +00:00
}
2021-04-28 22:13:29 +00:00
func ( e * MeshConfigEntry ) GetRaftIndex ( ) * RaftIndex {
2021-04-06 18:19:59 +00:00
if e == nil {
return & RaftIndex { }
}
return & e . RaftIndex
}
2022-03-13 03:55:53 +00:00
func ( e * MeshConfigEntry ) GetEnterpriseMeta ( ) * acl . EnterpriseMeta {
2021-04-06 18:19:59 +00:00
if e == nil {
return nil
}
return & e . EnterpriseMeta
}
2021-04-29 21:44:32 +00:00
// MarshalJSON adds the Kind field so that the JSON can be decoded back into the
// correct type.
// This method is implemented on the structs type (as apposed to the api type)
// because that is what the API currently uses to return a response.
func ( e * MeshConfigEntry ) MarshalJSON ( ) ( [ ] byte , error ) {
type Alias MeshConfigEntry
source := & struct {
Kind string
* Alias
} {
Kind : MeshConfig ,
Alias : ( * Alias ) ( e ) ,
}
return json . Marshal ( source )
}
2022-03-30 18:43:59 +00:00
2022-09-23 03:14:25 +00:00
func ( e * MeshConfigEntry ) PeerThroughMeshGateways ( ) bool {
if e == nil || e . Peering == nil {
return false
}
return e . Peering . PeerThroughMeshGateways
}
2024-10-16 16:23:33 +00:00
func ( e * MeshConfigEntry ) GetHTTP ( ) * MeshHTTPConfig {
if e == nil {
return nil
}
return e . HTTP
}
func ( e * MeshHTTPConfig ) GetIncoming ( ) * MeshDirectionalHTTPConfig {
if e == nil {
return nil
}
return e . Incoming
}
func ( e * MeshDirectionalHTTPConfig ) GetRequestNormalization ( ) * RequestNormalizationMeshConfig {
if e == nil {
return nil
}
return e . RequestNormalization
}
// GetHTTPIncomingRequestNormalization is a convenience accessor for mesh.http.incoming.request_normalization
// since no other fields currently exist under mesh.http.incoming.
func ( e * MeshConfigEntry ) GetHTTPIncomingRequestNormalization ( ) * RequestNormalizationMeshConfig {
return e . GetHTTP ( ) . GetIncoming ( ) . GetRequestNormalization ( )
}
func ( r * RequestNormalizationMeshConfig ) GetInsecureDisablePathNormalization ( ) bool {
if r == nil {
return false
}
return r . InsecureDisablePathNormalization
}
func ( r * RequestNormalizationMeshConfig ) GetMergeSlashes ( ) bool {
if r == nil {
return false
}
return r . MergeSlashes
}
func ( r * RequestNormalizationMeshConfig ) GetPathWithEscapedSlashesAction ( ) PathWithEscapedSlashesAction {
if r == nil || r . PathWithEscapedSlashesAction == "" {
return PathWithEscapedSlashesActionDefault
}
return r . PathWithEscapedSlashesAction
}
func ( r * RequestNormalizationMeshConfig ) GetHeadersWithUnderscoresAction ( ) HeadersWithUnderscoresAction {
if r == nil || r . HeadersWithUnderscoresAction == "" {
return HeadersWithUnderscoresActionAllow
}
return r . HeadersWithUnderscoresAction
}
2022-03-30 18:43:59 +00:00
func validateMeshDirectionalTLSConfig ( cfg * MeshDirectionalTLSConfig ) error {
if cfg == nil {
return nil
}
return validateTLSConfig ( cfg . TLSMinVersion , cfg . TLSMaxVersion , cfg . CipherSuites )
}
func validateTLSConfig (
tlsMinVersion types . TLSVersion ,
tlsMaxVersion types . TLSVersion ,
cipherSuites [ ] types . TLSCipherSuite ,
) error {
if tlsMinVersion != types . TLSVersionUnspecified {
if err := types . ValidateTLSVersion ( tlsMinVersion ) ; err != nil {
return err
}
}
if tlsMaxVersion != types . TLSVersionUnspecified {
if err := types . ValidateTLSVersion ( tlsMaxVersion ) ; err != nil {
return err
}
if tlsMinVersion != types . TLSVersionUnspecified {
if err , maxLessThanMin := tlsMaxVersion . LessThan ( tlsMinVersion ) ; err == nil && maxLessThanMin {
return fmt . Errorf ( "configuring max version %s less than the configured min version %s is invalid" , tlsMaxVersion , tlsMinVersion )
}
}
}
if len ( cipherSuites ) != 0 {
if _ , ok := types . TLSVersionsWithConfigurableCipherSuites [ tlsMinVersion ] ; ! ok {
return fmt . Errorf ( "configuring CipherSuites is only applicable to connections negotiated with TLS 1.2 or earlier, TLSMinVersion is set to %s" , tlsMinVersion )
}
// NOTE: it would be nice to emit a warning but not return an error from
// here if TLSMaxVersion is unspecified, TLS_AUTO or TLSv1_3
if err := types . ValidateEnvoyCipherSuites ( cipherSuites ) ; err != nil {
return err
}
}
return nil
}
2024-10-16 16:23:33 +00:00
func validateRequestNormalizationMeshConfig ( cfg * RequestNormalizationMeshConfig ) error {
if cfg == nil {
return nil
}
if err := validatePathWithEscapedSlashesAction ( cfg . PathWithEscapedSlashesAction ) ; err != nil {
return err
}
if err := validateHeadersWithUnderscoresAction ( cfg . HeadersWithUnderscoresAction ) ; err != nil {
return err
}
return nil
}
func validatePathWithEscapedSlashesAction ( v PathWithEscapedSlashesAction ) error {
if v == "" {
return nil
}
if _ , ok := pathWithEscapedSlashesActions [ v ] ; ! ok {
return fmt . Errorf ( "no matching PathWithEscapedSlashesAction value found for %s, please specify one of [%s]" , string ( v ) , strings . Join ( PathWithEscapedSlashesActionStrings ( ) , ", " ) )
}
return nil
}
func validateHeadersWithUnderscoresAction ( v HeadersWithUnderscoresAction ) error {
if v == "" {
return nil
}
if _ , ok := headersWithUnderscoresActions [ v ] ; ! ok {
return fmt . Errorf ( "no matching HeadersWithUnderscoresAction value found for %s, please specify one of [%s]" , string ( v ) , strings . Join ( HeadersWithUnderscoresActionStrings ( ) , ", " ) )
}
return nil
}