1// Copyright 2018 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 15package model 16 17import ( 18 "sync" 19 20 auth "github.com/envoyproxy/go-control-plane/envoy/api/v2/auth" 21 core "github.com/envoyproxy/go-control-plane/envoy/api/v2/core" 22 envoy_config_grpc_credential_v2alpha "github.com/envoyproxy/go-control-plane/envoy/config/grpc_credential/v2alpha" 23 "github.com/golang/protobuf/ptypes" 24 "github.com/golang/protobuf/ptypes/any" 25 26 networking "istio.io/api/networking/v1alpha3" 27 28 "istio.io/istio/pilot/pkg/features" 29 "istio.io/istio/pilot/pkg/model" 30 "istio.io/istio/pilot/pkg/networking/util" 31 "istio.io/istio/pkg/config/constants" 32) 33 34const ( 35 // SDSStatPrefix is the human readable prefix to use when emitting statistics for the SDS service. 36 SDSStatPrefix = "sdsstat" 37 38 // SDSClusterName is the name of the cluster for SDS connections 39 SDSClusterName = "sds-grpc" 40 41 // SDSDefaultResourceName is the default name in sdsconfig, used for fetching normal key/cert. 42 SDSDefaultResourceName = "default" 43 44 // SDSRootResourceName is the sdsconfig name for root CA, used for fetching root cert. 45 SDSRootResourceName = "ROOTCA" 46 47 // K8sSAJwtFileName is the token volume mount file name for k8s jwt token. 48 K8sSAJwtFileName = "/var/run/secrets/kubernetes.io/serviceaccount/token" 49 50 // K8sSATrustworthyJwtFileName is the token volume mount file name for k8s trustworthy jwt token. 51 K8sSATrustworthyJwtFileName = "/var/run/secrets/tokens/istio-token" 52 53 // FileBasedMetadataPlugName is File Based Metadata credentials plugin name. 54 FileBasedMetadataPlugName = "envoy.grpc_credentials.file_based_metadata" 55 56 // K8sSAJwtTokenHeaderKey is the request header key for k8s jwt token. 57 // Binary header name must has suffix "-bin", according to https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md. 58 K8sSAJwtTokenHeaderKey = "istio_sds_credentials_header-bin" 59 60 // IngressGatewaySdsUdsPath is the UDS path for ingress gateway to get credentials via SDS. 61 IngressGatewaySdsUdsPath = "unix:/var/run/ingress_gateway/sds" 62 63 // SdsCaSuffix is the suffix of the sds resource name for root CA. 64 SdsCaSuffix = "-cacert" 65 66 // IstioJwtFilterName is the name for the Istio Jwt filter. This should be the same 67 // as the name defined in 68 // https://github.com/istio/proxy/blob/master/src/envoy/http/jwt_auth/http_filter_factory.cc#L50 69 IstioJwtFilterName = "jwt-auth" 70 71 // EnvoyJwtFilterName is the name of the Envoy JWT filter. This should be the same as the name defined 72 // in https://github.com/envoyproxy/envoy/blob/v1.9.1/source/extensions/filters/http/well_known_names.h#L48 73 EnvoyJwtFilterName = "envoy.filters.http.jwt_authn" 74 75 // AuthnFilterName is the name for the Istio AuthN filter. This should be the same 76 // as the name defined in 77 // https://github.com/istio/proxy/blob/master/src/envoy/http/authn/http_filter_factory.cc#L30 78 AuthnFilterName = "istio_authn" 79) 80 81// ConstructSdsSecretConfigWithCustomUds constructs SDS secret configuration for ingress gateway. 82func ConstructSdsSecretConfigWithCustomUds(name, sdsUdsPath string) *auth.SdsSecretConfig { 83 if name == "" || sdsUdsPath == "" { 84 return nil 85 } 86 87 gRPCConfig := &core.GrpcService_GoogleGrpc{ 88 TargetUri: sdsUdsPath, 89 StatPrefix: SDSStatPrefix, 90 } 91 92 return &auth.SdsSecretConfig{ 93 Name: name, 94 SdsConfig: &core.ConfigSource{ 95 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 96 ApiConfigSource: &core.ApiConfigSource{ 97 ApiType: core.ApiConfigSource_GRPC, 98 GrpcServices: []*core.GrpcService{ 99 { 100 TargetSpecifier: &core.GrpcService_GoogleGrpc_{ 101 GoogleGrpc: gRPCConfig, 102 }, 103 }, 104 }, 105 }, 106 }, 107 InitialFetchTimeout: features.InitialFetchTimeout, 108 }, 109 } 110} 111 112// ConstructSdsSecretConfig constructs SDS Secret Configuration for workload proxy. 113func ConstructSdsSecretConfig(name, sdsUdsPath string) *auth.SdsSecretConfig { 114 if name == "" || sdsUdsPath == "" { 115 return nil 116 } 117 118 return &auth.SdsSecretConfig{ 119 Name: name, 120 SdsConfig: &core.ConfigSource{ 121 ConfigSourceSpecifier: &core.ConfigSource_ApiConfigSource{ 122 ApiConfigSource: &core.ApiConfigSource{ 123 ApiType: core.ApiConfigSource_GRPC, 124 GrpcServices: []*core.GrpcService{ 125 { 126 TargetSpecifier: &core.GrpcService_EnvoyGrpc_{ 127 EnvoyGrpc: &core.GrpcService_EnvoyGrpc{ClusterName: SDSClusterName}, 128 }, 129 }, 130 }, 131 }, 132 }, 133 InitialFetchTimeout: features.InitialFetchTimeout, 134 }, 135 } 136} 137 138// ConstructValidationContext constructs ValidationContext in CommonTLSContext. 139func ConstructValidationContext(rootCAFilePath string, subjectAltNames []string) *auth.CommonTlsContext_ValidationContext { 140 ret := &auth.CommonTlsContext_ValidationContext{ 141 ValidationContext: &auth.CertificateValidationContext{ 142 TrustedCa: &core.DataSource{ 143 Specifier: &core.DataSource_Filename{ 144 Filename: rootCAFilePath, 145 }, 146 }, 147 }, 148 } 149 150 if len(subjectAltNames) > 0 { 151 ret.ValidationContext.MatchSubjectAltNames = util.StringToExactMatch(subjectAltNames) 152 } 153 154 return ret 155} 156 157// ApplyToCommonTLSContext completes the commonTlsContext for `ISTIO_MUTUAL` TLS mode 158func ApplyToCommonTLSContext(tlsContext *auth.CommonTlsContext, metadata *model.NodeMetadata, sdsPath string, subjectAltNames []string) { 159 // configure TLS with SDS 160 if metadata.SdsEnabled && sdsPath != "" { 161 // These are certs being mounted from within the pod. Rather than reading directly in Envoy, 162 // which does not support rotation, we will serve them over SDS by reading the files. 163 // We should check if these certs have values, if yes we should use them or otherwise fall back to defaults. 164 res := model.SdsCertificateConfig{ 165 CertificatePath: metadata.TLSServerCertChain, 166 PrivateKeyPath: metadata.TLSServerKey, 167 CaCertificatePath: metadata.TLSServerRootCert, 168 } 169 170 // configure server listeners with SDS. 171 tlsContext.ValidationContextType = &auth.CommonTlsContext_CombinedValidationContext{ 172 CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{ 173 DefaultValidationContext: &auth.CertificateValidationContext{MatchSubjectAltNames: util.StringToExactMatch(subjectAltNames)}, 174 ValidationContextSdsSecretConfig: ConstructSdsSecretConfig( 175 model.GetOrDefault(res.GetRootResourceName(), SDSRootResourceName), sdsPath), 176 }, 177 } 178 tlsContext.TlsCertificateSdsSecretConfigs = []*auth.SdsSecretConfig{ 179 ConstructSdsSecretConfig(model.GetOrDefault(res.GetResourceName(), SDSDefaultResourceName), sdsPath), 180 } 181 } else { 182 // TODO(ramaraochavali): Clean this codepath later as we default to SDS. 183 // SDS disabled, fall back on using mounted certificates 184 base := metadata.SdsBase + constants.AuthCertsPath 185 tlsServerRootCert := model.GetOrDefault(metadata.TLSServerRootCert, base+constants.RootCertFilename) 186 187 tlsContext.ValidationContextType = ConstructValidationContext(tlsServerRootCert, subjectAltNames) 188 189 tlsServerCertChain := model.GetOrDefault(metadata.TLSServerCertChain, base+constants.CertChainFilename) 190 tlsServerKey := model.GetOrDefault(metadata.TLSServerKey, base+constants.KeyFilename) 191 192 tlsContext.TlsCertificates = []*auth.TlsCertificate{ 193 { 194 CertificateChain: &core.DataSource{ 195 Specifier: &core.DataSource_Filename{ 196 Filename: tlsServerCertChain, 197 }, 198 }, 199 PrivateKey: &core.DataSource{ 200 Specifier: &core.DataSource_Filename{ 201 Filename: tlsServerKey, 202 }, 203 }, 204 }, 205 } 206 } 207} 208 209// ApplyCustomSDSToCommonTLSContext applies the customized sds to CommonTlsContext 210// Used for building both gateway/sidecar TLS context 211func ApplyCustomSDSToCommonTLSContext(tlsContext *auth.CommonTlsContext, tlsOpts *networking.ServerTLSSettings, sdsUdsPath string) { 212 // create SDS config for gateway/sidecar to fetch key/cert from agent. 213 tlsContext.TlsCertificateSdsSecretConfigs = []*auth.SdsSecretConfig{ 214 ConstructSdsSecretConfigWithCustomUds(tlsOpts.CredentialName, sdsUdsPath), 215 } 216 // If tls mode is MUTUAL, create SDS config for gateway/sidecar to fetch certificate validation context 217 // at gateway agent. Otherwise, use the static certificate validation context config. 218 if tlsOpts.Mode == networking.ServerTLSSettings_MUTUAL { 219 defaultValidationContext := &auth.CertificateValidationContext{ 220 MatchSubjectAltNames: util.StringToExactMatch(tlsOpts.SubjectAltNames), 221 VerifyCertificateSpki: tlsOpts.VerifyCertificateSpki, 222 VerifyCertificateHash: tlsOpts.VerifyCertificateHash, 223 } 224 tlsContext.ValidationContextType = &auth.CommonTlsContext_CombinedValidationContext{ 225 CombinedValidationContext: &auth.CommonTlsContext_CombinedCertificateValidationContext{ 226 DefaultValidationContext: defaultValidationContext, 227 ValidationContextSdsSecretConfig: ConstructSdsSecretConfigWithCustomUds( 228 tlsOpts.CredentialName+SdsCaSuffix, sdsUdsPath), 229 }, 230 } 231 } else if len(tlsOpts.SubjectAltNames) > 0 { 232 tlsContext.ValidationContextType = &auth.CommonTlsContext_ValidationContext{ 233 ValidationContext: &auth.CertificateValidationContext{ 234 MatchSubjectAltNames: util.StringToExactMatch(tlsOpts.SubjectAltNames), 235 }, 236 } 237 } 238} 239 240// ConstructgRPCCallCredentials is used to construct SDS config which is only available from 1.1 241func ConstructgRPCCallCredentials(tokenFileName, headerKey string) []*core.GrpcService_GoogleGrpc_CallCredentials { 242 // If k8s sa jwt token file exists, envoy only handles plugin credentials. 243 config := &envoy_config_grpc_credential_v2alpha.FileBasedMetadataConfig{ 244 SecretData: &core.DataSource{ 245 Specifier: &core.DataSource_Filename{ 246 Filename: tokenFileName, 247 }, 248 }, 249 HeaderKey: headerKey, 250 } 251 252 any := findOrMarshalFileBasedMetadataConfig(tokenFileName, headerKey, config) 253 254 return []*core.GrpcService_GoogleGrpc_CallCredentials{ 255 { 256 CredentialSpecifier: &core.GrpcService_GoogleGrpc_CallCredentials_FromPlugin{ 257 FromPlugin: &core.GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin{ 258 Name: FileBasedMetadataPlugName, 259 ConfigType: &core.GrpcService_GoogleGrpc_CallCredentials_MetadataCredentialsFromPlugin_TypedConfig{ 260 TypedConfig: any}, 261 }, 262 }, 263 }, 264 } 265} 266 267type fbMetadataAnyKey struct { 268 tokenFileName string 269 headerKey string 270} 271 272var fileBasedMetadataConfigAnyMap sync.Map 273 274// findOrMarshalFileBasedMetadataConfig searches google.protobuf.Any in fileBasedMetadataConfigAnyMap 275// by tokenFileName and headerKey, and returns google.protobuf.Any proto if found. If not found, 276// it takes the fbMetadata and marshals it into google.protobuf.Any, and stores this new 277// google.protobuf.Any into fileBasedMetadataConfigAnyMap. 278// FileBasedMetadataConfig only supports non-deterministic marshaling. As each SDS config contains 279// marshaled FileBasedMetadataConfig, the SDS config would differ if marshaling FileBasedMetadataConfig 280// returns different result. Once SDS config differs, Envoy will create multiple SDS clients to fetch 281// same SDS resource. To solve this problem, we use findOrMarshalFileBasedMetadataConfig so that 282// FileBasedMetadataConfig is marshaled once, and is reused in all SDS configs. 283func findOrMarshalFileBasedMetadataConfig(tokenFileName, headerKey string, fbMetadata *envoy_config_grpc_credential_v2alpha.FileBasedMetadataConfig) *any.Any { 284 key := fbMetadataAnyKey{ 285 tokenFileName: tokenFileName, 286 headerKey: headerKey, 287 } 288 if v, found := fileBasedMetadataConfigAnyMap.Load(key); found { 289 marshalAny := v.(any.Any) 290 return &marshalAny 291 } 292 any, _ := ptypes.MarshalAny(fbMetadata) 293 fileBasedMetadataConfigAnyMap.Store(key, *any) 294 return any 295} 296