istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/security/security.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 security 16 17 import ( 18 "context" 19 "fmt" 20 "net/http" 21 "os" 22 "strings" 23 "time" 24 25 "google.golang.org/grpc/metadata" 26 "google.golang.org/grpc/peer" 27 28 "istio.io/istio/pkg/env" 29 istiolog "istio.io/istio/pkg/log" 30 ) 31 32 var securityLog = istiolog.RegisterScope("security", "security debugging") 33 34 const ( 35 // etc/certs files are used with external CA managing the certs, 36 // i.e. mounted Secret or external plugin. 37 // If present, FileMountedCerts should be true. 38 39 // DefaultCertChainFilePath is the well-known path for an existing certificate chain file 40 DefaultCertChainFilePath = "./etc/certs/cert-chain.pem" 41 42 // DefaultKeyFilePath is the well-known path for an existing key file 43 DefaultKeyFilePath = "./etc/certs/key.pem" 44 45 // DefaultRootCertFilePath is the well-known path for an existing root certificate file 46 DefaultRootCertFilePath = "./etc/certs/root-cert.pem" 47 48 // WorkloadIdentitySocketPath is the well-known path to the Unix Domain Socket for SDS. 49 WorkloadIdentitySocketPath = "./var/run/secrets/workload-spiffe-uds/socket" 50 51 // CredentialNameSocketPath is the well-known path to the Unix Domain Socket for Credential Name. 52 CredentialNameSocketPath = "./var/run/secrets/credential-uds/socket" 53 54 // CredentialMetaDataName is the name in node meta data. 55 CredentialMetaDataName = "credential" 56 57 // SDSExternalClusterName is the name of the cluster for external SDS connections which is defined via CredentialNameSocketPath 58 SDSExternalClusterName = "sds-external" 59 60 // SDSExternalCredentialPrefix is the prefix for the credentialName which will utilize external SDS connections defined via CredentialNameSocketPath 61 SDSExternalCredentialPrefix = "sds://" 62 63 // WorkloadIdentityCredentialsPath is the well-known path to a folder with workload certificate files. 64 WorkloadIdentityCredentialsPath = "./var/run/secrets/workload-spiffe-credentials" 65 66 // WorkloadIdentityCertChainPath is the well-known path to a workload certificate chain file. 67 WorkloadIdentityCertChainPath = WorkloadIdentityCredentialsPath + "/cert-chain.pem" 68 69 // WorkloadIdentityKeyPath is the well-known path to a workload key file. 70 WorkloadIdentityKeyPath = WorkloadIdentityCredentialsPath + "/key.pem" 71 72 // WorkloadIdentityRootCertPath is the well-known path to a workload root certificate file. 73 WorkloadIdentityRootCertPath = WorkloadIdentityCredentialsPath + "/root-cert.pem" 74 75 // GkeWorkloadCertChainFilePath is the well-known path for the GKE workload certificate chain file. 76 // Quoted from https://cloud.google.com/traffic-director/docs/security-proxyless-setup#create-service: 77 // "On creation, each Pod gets a volume at /var/run/secrets/workload-spiffe-credentials." 78 GkeWorkloadCertChainFilePath = WorkloadIdentityCredentialsPath + "/certificates.pem" 79 80 // GkeWorkloadKeyFilePath is the well-known path for the GKE workload certificate key file 81 GkeWorkloadKeyFilePath = WorkloadIdentityCredentialsPath + "/private_key.pem" 82 83 // GkeWorkloadRootCertFilePath is the well-known path for the GKE workload root certificate file 84 GkeWorkloadRootCertFilePath = WorkloadIdentityCredentialsPath + "/ca_certificates.pem" 85 86 // SystemRootCerts is special case input for root cert configuration to use system root certificates. 87 SystemRootCerts = "SYSTEM" 88 89 // RootCertReqResourceName is resource name of discovery request for root certificate. 90 RootCertReqResourceName = "ROOTCA" 91 92 // WorkloadKeyCertResourceName is the resource name of the discovery request for workload 93 // identity. 94 WorkloadKeyCertResourceName = "default" 95 96 // GCE is Credential fetcher type of Google plugin 97 GCE = "GoogleComputeEngine" 98 99 // JWT is a Credential fetcher type that reads from a JWT token file 100 JWT = "JWT" 101 102 // Mock is Credential fetcher type of mock plugin 103 Mock = "Mock" // testing only 104 105 // GoogleCAProvider uses the Google CA for workload certificate signing 106 GoogleCAProvider = "GoogleCA" 107 108 // GoogleCASProvider uses the Google certificate Authority Service to sign workload certificates 109 GoogleCASProvider = "GoogleCAS" 110 111 // GkeWorkloadCertificateProvider uses the GKE workload certificates 112 GkeWorkloadCertificateProvider = "GkeWorkloadCertificate" 113 114 // FileRootSystemCACert is a unique resource name signaling that the system CA certificate should be used 115 FileRootSystemCACert = "file-root:system" 116 ) 117 118 // TODO: For 1.8, make sure MeshConfig is updated with those settings, 119 // they should be dynamic to allow migrations without restart. 120 // Both are critical. 121 var ( 122 // Require3PToken disables the use of K8S 1P tokens. Note that 1P tokens can be used to request 123 // 3P TOKENS. A 1P token is the token automatically mounted by Kubelet and used for authentication with 124 // the Apiserver. 125 Require3PToken = env.Register("REQUIRE_3P_TOKEN", false, 126 "Reject k8s default tokens, without audience. If false, default K8S token will be accepted") 127 128 // TokenAudiences specifies a list of audiences for SDS trustworthy JWT. This is to make sure that the CSR requests 129 // contain the JWTs intended for Citadel. 130 TokenAudiences = strings.Split(env.Register("TOKEN_AUDIENCES", "istio-ca", 131 "A list of comma separated audiences to check in the JWT token before issuing a certificate. "+ 132 "The token is accepted if it matches with one of the audiences").Get(), ",") 133 ) 134 135 const ( 136 BearerTokenPrefix = "Bearer " 137 138 K8sTokenPrefix = "Istio " 139 140 // CertSigner info 141 CertSigner = "CertSigner" 142 143 // ImpersonatedIdentity declares the identity we are requesting a certificate on behalf of. 144 // This is constrained to only allow identities in CATrustedNodeAccounts, and only to impersonate identities 145 // on their node. 146 ImpersonatedIdentity = "ImpersonatedIdentity" 147 ) 148 149 type ImpersonatedIdentityContextKey struct{} 150 151 // Options provides all of the configuration parameters for secret discovery service 152 // and CA configuration. Used in both Istiod and Agent. 153 // TODO: ProxyConfig should have most of those, and be passed to all components 154 // (as source of truth) 155 type Options struct { 156 // CAEndpoint is the CA endpoint to which node agent sends CSR request. 157 CAEndpoint string 158 159 // CAEndpointSAN overrides the ServerName extracted from CAEndpoint. 160 CAEndpointSAN string 161 162 // The CA provider name. 163 CAProviderName string 164 165 // TrustDomain corresponds to the trust root of a system. 166 // https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain 167 TrustDomain string 168 169 // WorkloadRSAKeySize is the size of a private key for a workload certificate. 170 WorkloadRSAKeySize int 171 172 // Whether to generate PKCS#8 private keys. 173 Pkcs8Keys bool 174 175 // OutputKeyCertToDir is the directory for output the key and certificate 176 OutputKeyCertToDir string 177 178 // ProvCert is the directory for client to provide the key and certificate to CA server when authenticating 179 // with mTLS. This is not used for workload mTLS communication, and is 180 ProvCert string 181 182 // ClusterID is the cluster where the agent resides. 183 // Normally initialized from ISTIO_META_CLUSTER_ID - after a tortuous journey it 184 // makes its way into the ClusterID metadata of Citadel gRPC request to create the cert. 185 // Didn't find much doc - but I suspect used for 'central cluster' use cases - so should 186 // match the cluster name set in the MC setup. 187 ClusterID string 188 189 // The type of Elliptical Signature algorithm to use 190 // when generating private keys. Currently only ECDSA is supported. 191 ECCSigAlg string 192 193 // The type of curve to use when generating private keys with ECC. Currently only ECDSA is supported. 194 ECCCurve string 195 196 // FileMountedCerts indicates whether the proxy is using file 197 // mounted certs created by a foreign CA. Refresh is managed by the external 198 // CA, by updating the Secret or VM file. We will watch the file for changes 199 // or check before the cert expires. This assumes the certs are in the 200 // well-known ./etc/certs location. 201 FileMountedCerts bool 202 203 // PilotCertProvider is the provider of the Pilot certificate (PILOT_CERT_PROVIDER env) 204 // Determines the root CA file to use for connecting to CA gRPC: 205 // - istiod 206 // - kubernetes 207 // - custom 208 // - none 209 PilotCertProvider string 210 211 // secret TTL. 212 SecretTTL time.Duration 213 214 // The ratio of cert lifetime to refresh a cert. For example, at 0.10 and 1 hour TTL, 215 // we would refresh 6 minutes before expiration. 216 SecretRotationGracePeriodRatio float64 217 218 // STS port 219 STSPort int 220 221 // credential fetcher. 222 CredFetcher CredFetcher 223 224 // credential identity provider 225 CredIdentityProvider string 226 227 // Namespace corresponding to workload 228 WorkloadNamespace string 229 230 // Name of the Service Account 231 ServiceAccount string 232 233 // XDS auth provider 234 XdsAuthProvider string 235 236 // Cert signer info 237 CertSigner string 238 239 // Delay in reading certificates from file after the change is detected. This is useful in cases 240 // where the write operation of key and cert take longer. 241 FileDebounceDuration time.Duration 242 243 // Root Cert read from the OS 244 CARootPath string 245 246 // The path for an existing certificate chain file 247 CertChainFilePath string 248 // The path for an existing key file 249 KeyFilePath string 250 // The path for an existing root certificate bundle 251 RootCertFilePath string 252 } 253 254 // Client interface defines the clients need to implement to talk to CA for CSR. 255 // The Agent will create a key pair and a CSR, and use an implementation of this 256 // interface to get back a signed certificate. There is no guarantee that the SAN 257 // in the request will be returned - server may replace it. 258 type Client interface { 259 CSRSign(csrPEM []byte, certValidTTLInSec int64) ([]string, error) 260 Close() 261 // Retrieve CA root certs If CA publishes API endpoint for this 262 GetRootCertBundle() ([]string, error) 263 } 264 265 // SecretManager defines secrets management interface which is used by SDS. 266 type SecretManager interface { 267 // GenerateSecret generates new secret for the given resource. 268 // 269 // The current implementation also watched the generated secret and trigger a callback when it is 270 // near expiry. It will constructs the SAN based on the token's 'sub' claim, expected to be in 271 // the K8S format. No other JWTs are currently supported due to client logic. If JWT is 272 // missing/invalid, the resourceName is used. 273 GenerateSecret(resourceName string) (*SecretItem, error) 274 } 275 276 // SecretItem is the cached item in in-memory secret store. 277 type SecretItem struct { 278 CertificateChain []byte 279 PrivateKey []byte 280 281 RootCert []byte 282 283 // ResourceName passed from envoy SDS discovery request. 284 // "ROOTCA" for root cert request, "default" for key/cert request. 285 ResourceName string 286 287 CreatedTime time.Time 288 289 ExpireTime time.Time 290 } 291 292 type CredFetcher interface { 293 // GetPlatformCredential fetches workload credential provided by the platform. 294 GetPlatformCredential() (string, error) 295 296 // GetIdentityProvider returns the name of the IdentityProvider that can authenticate the workload credential. 297 GetIdentityProvider() string 298 299 // Stop releases resources and cleans up. 300 Stop() 301 } 302 303 // AuthSource represents where authentication result is derived from. 304 type AuthSource int 305 306 const ( 307 AuthSourceClientCertificate AuthSource = iota 308 AuthSourceIDToken 309 ) 310 311 const ( 312 authorizationMeta = "authorization" 313 ) 314 315 type AuthContext struct { 316 // grpc context 317 GrpcContext context.Context 318 // http request 319 Request *http.Request 320 } 321 322 // RemoteAddress returns the authenticated remote address from AuthContext. 323 func (ac *AuthContext) RemoteAddress() string { 324 if ac.GrpcContext != nil { 325 return GetConnectionAddress(ac.GrpcContext) 326 } else if ac.Request != nil { 327 return ac.Request.RemoteAddr 328 } 329 return "" 330 } 331 332 // Header returns the authenticated remote address from AuthContext. 333 func (ac *AuthContext) Header(header string) []string { 334 if ac.GrpcContext != nil { 335 if meta, ok := metadata.FromIncomingContext(ac.GrpcContext); ok { 336 return meta.Get(header) 337 } 338 } else if ac.Request != nil { 339 return ac.Request.Header.Values(header) 340 } 341 return nil 342 } 343 344 // Caller carries the identity and authentication source of a caller. 345 type Caller struct { 346 AuthSource AuthSource 347 Identities []string 348 349 KubernetesInfo KubernetesInfo 350 } 351 352 // KubernetesInfo defines Kubernetes specific information extracted from the caller. 353 // This involves additional metadata about the caller beyond just its SPIFFE identity. 354 type KubernetesInfo struct { 355 PodName string 356 PodNamespace string 357 PodUID string 358 PodServiceAccount string 359 } 360 361 func (k KubernetesInfo) String() string { 362 return fmt.Sprintf("Pod{Name: %s, Namespace: %s, UID: %s, ServiceAccount: %s}", k.PodName, k.PodNamespace, k.PodUID, k.PodServiceAccount) 363 } 364 365 // Authenticator determines the caller identity based on request context. 366 type Authenticator interface { 367 Authenticate(ctx AuthContext) (*Caller, error) 368 AuthenticatorType() string 369 } 370 371 // authenticationManager orchestrates all authenticators to perform authentication. 372 type authenticationManager struct { 373 Authenticators []Authenticator 374 // authFailMsgs contains list of messages that authenticator wants to record - mainly used for logging. 375 authFailMsgs []string 376 } 377 378 // Authenticate loops through all the configured Authenticators and returns if one of the authenticator succeeds. 379 func (am *authenticationManager) authenticate(ctx context.Context) *Caller { 380 req := AuthContext{GrpcContext: ctx} 381 for _, authn := range am.Authenticators { 382 u, err := authn.Authenticate(req) 383 if u != nil && len(u.Identities) > 0 && err == nil { 384 securityLog.Debugf("Authentication successful through auth source %v", u.AuthSource) 385 return u 386 } 387 am.authFailMsgs = append(am.authFailMsgs, fmt.Sprintf("Authenticator %s: %v", authn.AuthenticatorType(), err)) 388 } 389 return nil 390 } 391 392 func GetConnectionAddress(ctx context.Context) string { 393 peerInfo, ok := peer.FromContext(ctx) 394 peerAddr := "unknown" 395 if ok { 396 peerAddr = peerInfo.Addr.String() 397 } 398 return peerAddr 399 } 400 401 func (am *authenticationManager) FailedMessages() string { 402 return strings.Join(am.authFailMsgs, "; ") 403 } 404 405 func ExtractBearerToken(ctx context.Context) (string, error) { 406 md, ok := metadata.FromIncomingContext(ctx) 407 if !ok { 408 return "", fmt.Errorf("no metadata is attached") 409 } 410 411 authHeader, exists := md[authorizationMeta] 412 if !exists { 413 return "", fmt.Errorf("no HTTP authorization header exists") 414 } 415 416 for _, value := range authHeader { 417 if strings.HasPrefix(value, BearerTokenPrefix) { 418 return strings.TrimPrefix(value, BearerTokenPrefix), nil 419 } 420 } 421 422 return "", fmt.Errorf("no bearer token exists in HTTP authorization header") 423 } 424 425 func ExtractRequestToken(req *http.Request) (string, error) { 426 value := req.Header.Get(authorizationMeta) 427 if value == "" { 428 return "", fmt.Errorf("no HTTP authorization header exists") 429 } 430 431 if strings.HasPrefix(value, BearerTokenPrefix) { 432 return strings.TrimPrefix(value, BearerTokenPrefix), nil 433 } 434 if strings.HasPrefix(value, K8sTokenPrefix) { 435 return strings.TrimPrefix(value, K8sTokenPrefix), nil 436 } 437 438 return "", fmt.Errorf("no bearer token exists in HTTP authorization header") 439 } 440 441 // GetOSRootFilePath returns the first file path detected from a list of known CA certificate file paths. 442 // If none of the known CA certificate files are found, a warning in printed and an empty string is returned. 443 func GetOSRootFilePath() string { 444 // Get and store the OS CA certificate path for Linux systems 445 // Source of CA File Paths: https://golang.org/src/crypto/x509/root_linux.go 446 certFiles := []string{ 447 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. 448 "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 449 "/etc/ssl/ca-bundle.pem", // OpenSUSE 450 "/etc/pki/tls/cacert.pem", // OpenELEC 451 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 452 "/etc/ssl/cert.pem", // Alpine Linux 453 "/usr/local/etc/ssl/cert.pem", // FreeBSD 454 "/etc/ssl/certs/ca-certificates", // Talos Linux 455 } 456 457 for _, cert := range certFiles { 458 if _, err := os.Stat(cert); err == nil { 459 istiolog.Debugf("Using OS CA certificate for proxy: %s", cert) 460 return cert 461 } 462 } 463 istiolog.Warn("OS CA Cert could not be found for agent") 464 return "" 465 } 466 467 // CheckWorkloadCertificate returns true when the workload certificate 468 // files are present under the provided paths. Otherwise, return false. 469 func CheckWorkloadCertificate(certChainFilePath, keyFilePath, rootCertFilePath string) bool { 470 if _, err := os.Stat(certChainFilePath); err != nil { 471 return false 472 } 473 if _, err := os.Stat(keyFilePath); err != nil { 474 return false 475 } 476 if _, err := os.Stat(rootCertFilePath); err != nil { 477 return false 478 } 479 return true 480 } 481 482 type SdsCertificateConfig struct { 483 CertificatePath string 484 PrivateKeyPath string 485 CaCertificatePath string 486 } 487 488 const ( 489 ResourceSeparator = "~" 490 ) 491 492 // GetResourceName converts a SdsCertificateConfig to a string to be used as an SDS resource name 493 func (s SdsCertificateConfig) GetResourceName() string { 494 if s.IsKeyCertificate() { 495 return "file-cert:" + s.CertificatePath + ResourceSeparator + s.PrivateKeyPath // Format: file-cert:%s~%s 496 } 497 return "" 498 } 499 500 // GetRootResourceName converts a SdsCertificateConfig to a string to be used as an SDS resource name for the root 501 func (s SdsCertificateConfig) GetRootResourceName() string { 502 if s.IsRootCertificate() { 503 return "file-root:" + s.CaCertificatePath // Format: file-root:%s 504 } 505 return "" 506 } 507 508 // IsRootCertificate returns true if this config represents a root certificate config. 509 func (s SdsCertificateConfig) IsRootCertificate() bool { 510 return s.CaCertificatePath != "" 511 } 512 513 // IsKeyCertificate returns true if this config represents key certificate config. 514 func (s SdsCertificateConfig) IsKeyCertificate() bool { 515 return s.CertificatePath != "" && s.PrivateKeyPath != "" 516 } 517 518 // SdsCertificateConfigFromResourceName converts the provided resource name into a SdsCertificateConfig 519 // If the resource name is not valid, false is returned. 520 func SdsCertificateConfigFromResourceName(resource string) (SdsCertificateConfig, bool) { 521 if strings.HasPrefix(resource, "file-cert:") { 522 filesString := strings.TrimPrefix(resource, "file-cert:") 523 split := strings.Split(filesString, ResourceSeparator) 524 if len(split) != 2 { 525 return SdsCertificateConfig{}, false 526 } 527 return SdsCertificateConfig{split[0], split[1], ""}, true 528 } else if strings.HasPrefix(resource, "file-root:") { 529 filesString := strings.TrimPrefix(resource, "file-root:") 530 split := strings.Split(filesString, ResourceSeparator) 531 532 if len(split) != 1 { 533 return SdsCertificateConfig{}, false 534 } 535 return SdsCertificateConfig{"", "", split[0]}, true 536 } 537 return SdsCertificateConfig{}, false 538 } 539 540 // SdsCertificateConfigFromResourceNameForOSCACert converts the OS resource name into a SdsCertificateConfig 541 func SdsCertificateConfigFromResourceNameForOSCACert(resource string) (SdsCertificateConfig, bool) { 542 if resource == "" { 543 return SdsCertificateConfig{}, false 544 } 545 return SdsCertificateConfig{"", "", resource}, true 546 }