istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/mesh/mesh.go (about)

     1  // Copyright Istio Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package mesh
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"time"
    21  
    22  	"github.com/hashicorp/go-multierror"
    23  	"google.golang.org/protobuf/proto"
    24  	"google.golang.org/protobuf/types/known/durationpb"
    25  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    26  	"sigs.k8s.io/yaml"
    27  
    28  	meshconfig "istio.io/api/mesh/v1alpha1"
    29  	"istio.io/api/networking/v1alpha3"
    30  	"istio.io/istio/pkg/config/constants"
    31  	"istio.io/istio/pkg/config/validation/agent"
    32  	"istio.io/istio/pkg/log"
    33  	"istio.io/istio/pkg/ptr"
    34  	"istio.io/istio/pkg/util/protomarshal"
    35  	"istio.io/istio/pkg/util/sets"
    36  )
    37  
    38  // DefaultProxyConfig for individual proxies
    39  func DefaultProxyConfig() *meshconfig.ProxyConfig {
    40  	// TODO: include revision based on REVISION env
    41  	// TODO: set default namespace based on POD_NAMESPACE env
    42  	return &meshconfig.ProxyConfig{
    43  		ConfigPath:               constants.ConfigPathDir,
    44  		ClusterName:              &meshconfig.ProxyConfig_ServiceCluster{ServiceCluster: constants.ServiceClusterName},
    45  		DrainDuration:            durationpb.New(45 * time.Second),
    46  		TerminationDrainDuration: durationpb.New(5 * time.Second),
    47  		ProxyAdminPort:           15000,
    48  		ControlPlaneAuthPolicy:   meshconfig.AuthenticationPolicy_MUTUAL_TLS,
    49  		DiscoveryAddress:         "istiod.istio-system.svc:15012",
    50  
    51  		// Code defaults
    52  		BinaryPath:     constants.BinaryPathFilename,
    53  		StatNameLength: 189,
    54  		StatusPort:     15020,
    55  	}
    56  }
    57  
    58  // DefaultMeshNetworks returns a default meshnetworks configuration.
    59  // By default, it is empty.
    60  func DefaultMeshNetworks() *meshconfig.MeshNetworks {
    61  	return ptr.Of(EmptyMeshNetworks())
    62  }
    63  
    64  // DefaultMeshConfig returns the default mesh config.
    65  // This is merged with values from the mesh config map.
    66  func DefaultMeshConfig() *meshconfig.MeshConfig {
    67  	proxyConfig := DefaultProxyConfig()
    68  
    69  	// Defaults matching the standard install
    70  	// order matches the generated mesh config.
    71  	return &meshconfig.MeshConfig{
    72  		EnableTracing:               true,
    73  		AccessLogFile:               "",
    74  		AccessLogEncoding:           meshconfig.MeshConfig_TEXT,
    75  		AccessLogFormat:             "",
    76  		EnableEnvoyAccessLogService: false,
    77  		ProtocolDetectionTimeout:    durationpb.New(0),
    78  		IngressService:              "istio-ingressgateway",
    79  		IngressControllerMode:       meshconfig.MeshConfig_STRICT,
    80  		IngressClass:                "istio",
    81  		TrustDomain:                 constants.DefaultClusterLocalDomain,
    82  		TrustDomainAliases:          []string{},
    83  		EnableAutoMtls:              wrappers.Bool(true),
    84  		OutboundTrafficPolicy:       &meshconfig.MeshConfig_OutboundTrafficPolicy{Mode: meshconfig.MeshConfig_OutboundTrafficPolicy_ALLOW_ANY},
    85  		InboundTrafficPolicy:        &meshconfig.MeshConfig_InboundTrafficPolicy{Mode: meshconfig.MeshConfig_InboundTrafficPolicy_PASSTHROUGH},
    86  		LocalityLbSetting: &v1alpha3.LocalityLoadBalancerSetting{
    87  			Enabled: wrappers.Bool(true),
    88  		},
    89  		Certificates:  []*meshconfig.Certificate{},
    90  		DefaultConfig: proxyConfig,
    91  
    92  		RootNamespace:                  constants.IstioSystemNamespace,
    93  		ProxyListenPort:                15001,
    94  		ProxyInboundListenPort:         15006,
    95  		ConnectTimeout:                 durationpb.New(10 * time.Second),
    96  		DefaultServiceExportTo:         []string{"*"},
    97  		DefaultVirtualServiceExportTo:  []string{"*"},
    98  		DefaultDestinationRuleExportTo: []string{"*"},
    99  		// DnsRefreshRate is only used when DNS requests fail (NXDOMAIN or SERVFAIL). For success, the TTL
   100  		// will be used.
   101  		// https://datatracker.ietf.org/doc/html/rfc2308#section-3 defines how negative DNS results should handle TTLs,
   102  		// but Envoy does not respect this (https://github.com/envoyproxy/envoy/issues/20885).
   103  		// To counter this, we bump up the default to 60s to avoid overloading DNS servers.
   104  		DnsRefreshRate:  durationpb.New(60 * time.Second),
   105  		ServiceSettings: make([]*meshconfig.MeshConfig_ServiceSettings, 0),
   106  
   107  		EnablePrometheusMerge: wrappers.Bool(true),
   108  		DefaultProviders:      &meshconfig.MeshConfig_DefaultProviders{},
   109  		ExtensionProviders: []*meshconfig.MeshConfig_ExtensionProvider{
   110  			{
   111  				Name: "prometheus",
   112  				Provider: &meshconfig.MeshConfig_ExtensionProvider_Prometheus{
   113  					Prometheus: &meshconfig.MeshConfig_ExtensionProvider_PrometheusMetricsProvider{},
   114  				},
   115  			},
   116  			{
   117  				Name: "stackdriver",
   118  				Provider: &meshconfig.MeshConfig_ExtensionProvider_Stackdriver{
   119  					Stackdriver: &meshconfig.MeshConfig_ExtensionProvider_StackdriverProvider{},
   120  				},
   121  			},
   122  			{
   123  				Name: "envoy",
   124  				Provider: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLog{
   125  					EnvoyFileAccessLog: &meshconfig.MeshConfig_ExtensionProvider_EnvoyFileAccessLogProvider{
   126  						Path: "/dev/stdout",
   127  					},
   128  				},
   129  			},
   130  		},
   131  	}
   132  }
   133  
   134  // ApplyProxyConfig applies the give proxy config yaml to a mesh config object. The passed in mesh config
   135  // will not be modified.
   136  func ApplyProxyConfig(yaml string, meshConfig *meshconfig.MeshConfig) (*meshconfig.MeshConfig, error) {
   137  	mc := proto.Clone(meshConfig).(*meshconfig.MeshConfig)
   138  	pc, err := MergeProxyConfig(yaml, mc.DefaultConfig)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	mc.DefaultConfig = pc
   143  	return mc, nil
   144  }
   145  
   146  // MergeProxyConfig merges the given proxy config yaml with the given proxy config object.
   147  func MergeProxyConfig(yaml string, proxyConfig *meshconfig.ProxyConfig) (*meshconfig.ProxyConfig, error) {
   148  	origMetadata := proxyConfig.ProxyMetadata
   149  	origProxyHeaders := proxyConfig.ProxyHeaders
   150  	if err := protomarshal.ApplyYAML(yaml, proxyConfig); err != nil {
   151  		return nil, fmt.Errorf("could not parse proxy config: %v", err)
   152  	}
   153  	newMetadata := proxyConfig.ProxyMetadata
   154  	proxyConfig.ProxyMetadata = mergeMap(origMetadata, newMetadata)
   155  	correctProxyHeaders(proxyConfig, origProxyHeaders)
   156  	return proxyConfig, nil
   157  }
   158  
   159  func correctProxyHeaders(proxyConfig *meshconfig.ProxyConfig, orig *meshconfig.ProxyConfig_ProxyHeaders) {
   160  	ph := proxyConfig.ProxyHeaders
   161  	if ph != nil && orig != nil {
   162  		ph.ForwardedClientCert = ptr.NonEmptyOrDefault(ph.ForwardedClientCert, orig.ForwardedClientCert)
   163  		ph.RequestId = ptr.NonEmptyOrDefault(ph.RequestId, orig.RequestId)
   164  		ph.AttemptCount = ptr.NonEmptyOrDefault(ph.AttemptCount, orig.AttemptCount)
   165  		ph.Server = ptr.NonEmptyOrDefault(ph.Server, orig.Server)
   166  		ph.EnvoyDebugHeaders = ptr.NonEmptyOrDefault(ph.EnvoyDebugHeaders, orig.EnvoyDebugHeaders)
   167  	}
   168  }
   169  
   170  func extractYamlField(key string, mp map[string]any) (string, error) {
   171  	proxyConfig := mp[key]
   172  	if proxyConfig == nil {
   173  		return "", nil
   174  	}
   175  	bytes, err := yaml.Marshal(proxyConfig)
   176  	if err != nil {
   177  		return "", err
   178  	}
   179  	return string(bytes), nil
   180  }
   181  
   182  func toMap(yamlText string) (map[string]any, error) {
   183  	mp := map[string]any{}
   184  	if err := yaml.Unmarshal([]byte(yamlText), &mp); err != nil {
   185  		return nil, err
   186  	}
   187  	return mp, nil
   188  }
   189  
   190  // ApplyMeshConfig returns a new MeshConfig decoded from the
   191  // input YAML with the provided defaults applied to omitted configuration values.
   192  func ApplyMeshConfig(yaml string, defaultConfig *meshconfig.MeshConfig) (*meshconfig.MeshConfig, error) {
   193  	// We want to keep semantics that all fields are overrides, except proxy config is a merge. This allows
   194  	// decent customization while also not requiring users to redefine the entire proxy config if they want to override
   195  	// Note: if we want to add more structure in the future, we will likely need to revisit this idea.
   196  
   197  	// Store the current set proxy config so we don't wipe it out, we will configure this later
   198  	prevProxyConfig := defaultConfig.DefaultConfig
   199  	prevDefaultProvider := defaultConfig.DefaultProviders
   200  	prevExtensionProviders := defaultConfig.ExtensionProviders
   201  	prevTrustDomainAliases := defaultConfig.TrustDomainAliases
   202  
   203  	defaultConfig.DefaultConfig = DefaultProxyConfig()
   204  	if err := protomarshal.ApplyYAML(yaml, defaultConfig); err != nil {
   205  		return nil, multierror.Prefix(err, "failed to convert to proto.")
   206  	}
   207  	defaultConfig.DefaultConfig = prevProxyConfig
   208  
   209  	raw, err := toMap(yaml)
   210  	if err != nil {
   211  		return nil, err
   212  	}
   213  	// Get just the proxy config yaml
   214  	pc, err := extractYamlField("defaultConfig", raw)
   215  	if err != nil {
   216  		return nil, multierror.Prefix(err, "failed to extract proxy config")
   217  	}
   218  	if pc != "" {
   219  		pc, err := MergeProxyConfig(pc, defaultConfig.DefaultConfig)
   220  		if err != nil {
   221  			return nil, err
   222  		}
   223  		defaultConfig.DefaultConfig = pc
   224  	}
   225  
   226  	defaultConfig.DefaultProviders = prevDefaultProvider
   227  	dp, err := extractYamlField("defaultProviders", raw)
   228  	if err != nil {
   229  		return nil, multierror.Prefix(err, "failed to extract default providers")
   230  	}
   231  	if dp != "" {
   232  		if err := protomarshal.ApplyYAML(dp, defaultConfig.DefaultProviders); err != nil {
   233  			return nil, fmt.Errorf("could not parse default providers: %v", err)
   234  		}
   235  	}
   236  
   237  	newExtensionProviders := defaultConfig.ExtensionProviders
   238  	defaultConfig.ExtensionProviders = prevExtensionProviders
   239  	for _, p := range newExtensionProviders {
   240  		found := false
   241  		for _, e := range defaultConfig.ExtensionProviders {
   242  			if p.Name == e.Name {
   243  				e.Provider = p.Provider
   244  				found = true
   245  				break
   246  			}
   247  		}
   248  		if !found {
   249  			defaultConfig.ExtensionProviders = append(defaultConfig.ExtensionProviders, p)
   250  		}
   251  	}
   252  
   253  	defaultConfig.TrustDomainAliases = sets.SortedList(sets.New(append(defaultConfig.TrustDomainAliases, prevTrustDomainAliases...)...))
   254  
   255  	warn, err := agent.ValidateMeshConfig(defaultConfig)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  	if warn != nil {
   260  		log.Warnf("warnings occurred during mesh validation: %v", warn)
   261  	}
   262  
   263  	return defaultConfig, nil
   264  }
   265  
   266  func mergeMap(original map[string]string, merger map[string]string) map[string]string {
   267  	if original == nil && merger == nil {
   268  		return nil
   269  	}
   270  	if original == nil {
   271  		original = map[string]string{}
   272  	}
   273  	for k, v := range merger {
   274  		original[k] = v
   275  	}
   276  	return original
   277  }
   278  
   279  // ApplyMeshConfigDefaults returns a new MeshConfig decoded from the
   280  // input YAML with defaults applied to omitted configuration values.
   281  func ApplyMeshConfigDefaults(yaml string) (*meshconfig.MeshConfig, error) {
   282  	return ApplyMeshConfig(yaml, DefaultMeshConfig())
   283  }
   284  
   285  func DeepCopyMeshConfig(mc *meshconfig.MeshConfig) (*meshconfig.MeshConfig, error) {
   286  	j, err := protomarshal.ToJSON(mc)
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  	nmc := &meshconfig.MeshConfig{}
   291  	if err := protomarshal.ApplyJSON(j, nmc); err != nil {
   292  		return nil, err
   293  	}
   294  	return nmc, nil
   295  }
   296  
   297  // EmptyMeshNetworks configuration with no networks
   298  func EmptyMeshNetworks() meshconfig.MeshNetworks {
   299  	return meshconfig.MeshNetworks{
   300  		Networks: map[string]*meshconfig.Network{},
   301  	}
   302  }
   303  
   304  // ParseMeshNetworks returns a new MeshNetworks decoded from the
   305  // input YAML.
   306  func ParseMeshNetworks(yaml string) (*meshconfig.MeshNetworks, error) {
   307  	out := EmptyMeshNetworks()
   308  	if err := protomarshal.ApplyYAML(yaml, &out); err != nil {
   309  		return nil, multierror.Prefix(err, "failed to convert to proto.")
   310  	}
   311  
   312  	if err := agent.ValidateMeshNetworks(&out); err != nil {
   313  		return nil, err
   314  	}
   315  	return &out, nil
   316  }
   317  
   318  // ReadMeshNetworks gets mesh networks configuration from a config file
   319  func ReadMeshNetworks(filename string) (*meshconfig.MeshNetworks, error) {
   320  	yaml, err := os.ReadFile(filename)
   321  	if err != nil {
   322  		return nil, multierror.Prefix(err, "cannot read networks config file")
   323  	}
   324  	return ParseMeshNetworks(string(yaml))
   325  }
   326  
   327  // ReadMeshConfig gets mesh configuration from a config file
   328  func ReadMeshConfig(filename string) (*meshconfig.MeshConfig, error) {
   329  	yaml, err := os.ReadFile(filename)
   330  	if err != nil {
   331  		return nil, multierror.Prefix(err, "cannot read mesh config file")
   332  	}
   333  	return ApplyMeshConfigDefaults(string(yaml))
   334  }
   335  
   336  // ReadMeshConfigData gets mesh configuration yaml from a config file
   337  func ReadMeshConfigData(filename string) (string, error) {
   338  	yaml, err := os.ReadFile(filename)
   339  	if err != nil {
   340  		return "", multierror.Prefix(err, "cannot read mesh config file")
   341  	}
   342  	return string(yaml), nil
   343  }