consul/agent/structs/config_entry_mesh.go

442 lines
16 KiB
Go
Raw Normal View History

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
2023-08-11 13:12:13 +00:00
// SPDX-License-Identifier: BUSL-1.1
package structs
import (
"encoding/json"
"fmt"
"strings"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/types"
)
type MeshConfigEntry struct {
// TransparentProxy contains cluster-wide options pertaining to TPROXY mode
// when enabled.
TransparentProxy TransparentProxyMeshConfig `alias:"transparent_proxy"`
// 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"`
// 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"`
TLS *MeshTLSConfig `json:",omitempty"`
Add x-forwarded-client-cert headers Description Add x-fowarded-client-cert information on trusted incoming connections. Envoy provides support forwarding and annotating the x-forwarded-client-cert header via the forward_client_cert_details set_current_client_cert_details filter fields. It would be helpful for consul to support this directly in its config. The escape hatches are a bit cumbersome for this purpose. This has been implemented on incoming connections to envoy. Outgoing (from the local service through the sidecar) will not have a certificate, and so are left alone. A service on an incoming connection will now get headers something like this: ``` X-Forwarded-Client-Cert:[By=spiffe://efad7282-d9b2-3298-f6d8-38b37fb58df3.consul/ns/default/dc/dc1/svc/counting;Hash=61ad5cbdfcb50f5a3ec0ca60923d61613c149a9d4495010a64175c05a0268ab2;Cert="-----BEGIN%20CERTIFICATE-----%0AMIICHDCCAcOgAwIBAgIBCDAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktMTli%0AYXdyb2YuY29uc3VsLmNhLmVmYWQ3MjgyLmNvbnN1bDAeFw0yMjA0MjkwMzE0NTBa%0AFw0yMjA1MDIwMzE0NTBaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARVIZ7Y%0AZEXfbOGBfxGa7Vuok1MIng%2FuzLQK2xLVlSTIPDbO5hstTGP%2B%2FGx182PYFP3jYqk5%0Aq6rYWe1wiPNMA30Io4H8MIH5MA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQgrp4q50oX%0AHHghMbxz5Bk8OJFWMdfgH0Upr350WlhyxvkwKwYDVR0jBCQwIoAgUe6uERAIj%2FLM%0AyuFzDc3Wbp9TGAKBJYAwyhF14ToOQCMwYgYDVR0RAQH%2FBFgwVoZUc3BpZmZlOi8v%0AZWZhZDcyODItZDliMi0zMjk4LWY2ZDgtMzhiMzdmYjU4ZGYzLmNvbnN1bC9ucy9k%0AZWZhdWx0L2RjL2RjMS9zdmMvZGFzaGJvYXJkMAoGCCqGSM49BAMCA0cAMEQCIDwb%0AFlchufggNTijnQ5SUcvTZrWlZyq%2FrdVC20nbbmWLAiAVshNNv1xBqJI1NmY2HI9n%0AgRMfb8aEPVSuxEHhqy57eQ%3D%3D%0A-----END%20CERTIFICATE-----%0A";Chain="-----BEGIN%20CERTIFICATE-----%0AMIICHDCCAcOgAwIBAgIBCDAKBggqhkjOPQQDAjAxMS8wLQYDVQQDEyZwcmktMTli%0AYXdyb2YuY29uc3VsLmNhLmVmYWQ3MjgyLmNvbnN1bDAeFw0yMjA0MjkwMzE0NTBa%0AFw0yMjA1MDIwMzE0NTBaMAAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARVIZ7Y%0AZEXfbOGBfxGa7Vuok1MIng%2FuzLQK2xLVlSTIPDbO5hstTGP%2B%2FGx182PYFP3jYqk5%0Aq6rYWe1wiPNMA30Io4H8MIH5MA4GA1UdDwEB%2FwQEAwIDuDAdBgNVHSUEFjAUBggr%0ABgEFBQcDAgYIKwYBBQUHAwEwDAYDVR0TAQH%2FBAIwADApBgNVHQ4EIgQgrp4q50oX%0AHHghMbxz5Bk8OJFWMdfgH0Upr350WlhyxvkwKwYDVR0jBCQwIoAgUe6uERAIj%2FLM%0AyuFzDc3Wbp9TGAKBJYAwyhF14ToOQCMwYgYDVR0RAQH%2FBFgwVoZUc3BpZmZlOi8v%0AZWZhZDcyODItZDliMi0zMjk4LWY2ZDgtMzhiMzdmYjU4ZGYzLmNvbnN1bC9ucy9k%0AZWZhdWx0L2RjL2RjMS9zdmMvZGFzaGJvYXJkMAoGCCqGSM49BAMCA0cAMEQCIDwb%0AFlchufggNTijnQ5SUcvTZrWlZyq%2FrdVC20nbbmWLAiAVshNNv1xBqJI1NmY2HI9n%0AgRMfb8aEPVSuxEHhqy57eQ%3D%3D%0A-----END%20CERTIFICATE-----%0A";Subject="";URI=spiffe://efad7282-d9b2-3298-f6d8-38b37fb58df3.consul/ns/default/dc/dc1/svc/dashboard] ``` Closes #12852
2022-04-26 20:46:29 +00:00
HTTP *MeshHTTPConfig `json:",omitempty"`
Peering *PeeringMeshConfig `json:",omitempty"`
Meta map[string]string `json:",omitempty"`
Hash uint64 `json:",omitempty" hash:"ignore"`
acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex `hash:"ignore"`
}
func (e *MeshConfigEntry) SetHash(h uint64) {
e.Hash = h
}
func (e *MeshConfigEntry) GetHash() uint64 {
return e.Hash
}
// TransparentProxyMeshConfig contains cluster-wide options pertaining to
// TPROXY mode when enabled.
type TransparentProxyMeshConfig struct {
// MeshDestinationsOnly can be used to disable the pass-through that
// allows traffic to destinations outside of the mesh.
MeshDestinationsOnly bool `alias:"mesh_destinations_only"`
}
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"`
}
type MeshHTTPConfig struct {
SanitizeXForwardedClientCert bool `alias:"sanitize_x_forwarded_client_cert"`
// 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"`
}
// 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"`
}
// 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
})()
func (e *MeshConfigEntry) GetKind() string {
return MeshConfig
}
func (e *MeshConfigEntry) GetName() string {
if e == nil {
return ""
}
return MeshConfigMesh
}
func (e *MeshConfigEntry) GetMeta() map[string]string {
if e == nil {
return nil
}
return e.Meta
}
func (e *MeshConfigEntry) Normalize() error {
if e == nil {
return fmt.Errorf("config entry is nil")
}
e.EnterpriseMeta.Normalize()
h, err := HashConfigEntry(e)
if err != nil {
return err
}
e.Hash = h
return nil
}
func (e *MeshConfigEntry) Validate() error {
if e == nil {
return fmt.Errorf("config entry is nil")
}
if err := validateConfigEntryMeta(e.Meta); err != nil {
return err
}
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)
}
}
}
if err := validateRequestNormalizationMeshConfig(e.GetHTTPIncomingRequestNormalization()); err != nil {
return fmt.Errorf("error in HTTP incoming request normalization configuration: %v", err)
}
return e.validateEnterpriseMeta()
}
func (e *MeshConfigEntry) CanRead(authz acl.Authorizer) error {
return nil
}
func (e *MeshConfigEntry) CanWrite(authz acl.Authorizer) error {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.ToAllowAuthorizer().MeshWriteAllowed(&authzContext)
}
func (e *MeshConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
func (e *MeshConfigEntry) GetEnterpriseMeta() *acl.EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}
// 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)
}
func (e *MeshConfigEntry) PeerThroughMeshGateways() bool {
if e == nil || e.Peering == nil {
return false
}
return e.Peering.PeerThroughMeshGateways
}
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
}
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
}
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
}