blob: ae26024072f335f657dfa39a59554da6cbfa72dc [file] [log] [blame]
// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package security
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"time"
)
import (
"google.golang.org/grpc/metadata"
"istio.io/pkg/env"
istiolog "istio.io/pkg/log"
)
const (
// etc/certs files are used with external CA managing the certs,
// i.e. mounted Secret or external plugin.
// If present, FileMountedCerts should be true.
// DefaultCertChainFilePath is the well-known path for an existing certificate chain file
DefaultCertChainFilePath = "./etc/certs/cert-chain.pem"
// DefaultKeyFilePath is the well-known path for an existing key file
DefaultKeyFilePath = "./etc/certs/key.pem"
// DefaultRootCertFilePath is the well-known path for an existing root certificate file
DefaultRootCertFilePath = "./etc/certs/root-cert.pem"
// WorkloadIdentitySocketPath is the well-known path to the Unix Domain Socket for SDS.
WorkloadIdentitySocketPath = "./var/run/secrets/workload-spiffe-uds/socket"
// WorkloadIdentityCredentialsPath is the well-known path to a folder with workload certificate files.
WorkloadIdentityCredentialsPath = "./var/run/secrets/workload-spiffe-credentials"
// WorkloadIdentityCertChainPath is the well-known path to a workload certificate chain file.
WorkloadIdentityCertChainPath = WorkloadIdentityCredentialsPath + "/cert-chain.pem"
// WorkloadIdentityKeyPath is the well-known path to a workload key file.
WorkloadIdentityKeyPath = WorkloadIdentityCredentialsPath + "/key.pem"
// WorkloadIdentityRootCertPath is the well-known path to a workload root certificate file.
WorkloadIdentityRootCertPath = WorkloadIdentityCredentialsPath + "/root-cert.pem"
// GkeWorkloadCertChainFilePath is the well-known path for the GKE workload certificate chain file.
// Quoted from https://cloud.google.com/traffic-director/docs/security-proxyless-setup#create-service:
// "On creation, each Pod gets a volume at /var/run/secrets/workload-spiffe-credentials."
GkeWorkloadCertChainFilePath = WorkloadIdentityCredentialsPath + "/certificates.pem"
// GkeWorkloadKeyFilePath is the well-known path for the GKE workload certificate key file
GkeWorkloadKeyFilePath = WorkloadIdentityCredentialsPath + "/private_key.pem"
// GkeWorkloadRootCertFilePath is the well-known path for the GKE workload root certificate file
GkeWorkloadRootCertFilePath = WorkloadIdentityCredentialsPath + "/ca_certificates.pem"
// SystemRootCerts is special case input for root cert configuration to use system root certificates.
SystemRootCerts = "SYSTEM"
// RootCertReqResourceName is resource name of discovery request for root certificate.
RootCertReqResourceName = "ROOTCA"
// WorkloadKeyCertResourceName is the resource name of the discovery request for workload
// identity.
// TODO: change all the pilot one reference definition here instead.
WorkloadKeyCertResourceName = "default"
// GCE is Credential fetcher type of Google plugin
GCE = "GoogleComputeEngine"
// JWT is a Credential fetcher type that reads from a JWT token file
JWT = "JWT"
// Mock is Credential fetcher type of mock plugin
Mock = "Mock" // testing only
// GoogleCAProvider uses the Google CA for workload certificate signing
GoogleCAProvider = "GoogleCA"
// GoogleCASProvider uses the Google certificate Authority Service to sign workload certificates
GoogleCASProvider = "GoogleCAS"
// GkeWorkloadCertificateProvider uses the GKE workload certificates
GkeWorkloadCertificateProvider = "GkeWorkloadCertificate"
// FileRootSystemCACert is a unique resource name signaling that the system CA certificate should be used
FileRootSystemCACert = "file-root:system"
)
// TODO: For 1.8, make sure MeshConfig is updated with those settings,
// they should be dynamic to allow migrations without restart.
// Both are critical.
var (
// Require3PToken disables the use of K8S 1P tokens. Note that 1P tokens can be used to request
// 3P TOKENS. A 1P token is the token automatically mounted by Kubelet and used for authentication with
// the Apiserver.
Require3PToken = env.RegisterBoolVar("REQUIRE_3P_TOKEN", false,
"Reject k8s default tokens, without audience. If false, default K8S token will be accepted")
// TokenAudiences specifies a list of audiences for SDS trustworthy JWT. This is to make sure that the CSR requests
// contain the JWTs intended for Citadel.
TokenAudiences = strings.Split(env.RegisterStringVar("TOKEN_AUDIENCES", "istio-ca",
"A list of comma separated audiences to check in the JWT token before issuing a certificate. "+
"The token is accepted if it matches with one of the audiences").Get(), ",")
)
const (
BearerTokenPrefix = "Bearer "
K8sTokenPrefix = "Istio "
// CertSigner info
CertSigner = "CertSigner"
)
// Options provides all of the configuration parameters for secret discovery service
// and CA configuration. Used in both Istiod and Agent.
// TODO: ProxyConfig should have most of those, and be passed to all components
// (as source of truth)
type Options struct {
// CAEndpoint is the CA endpoint to which node agent sends CSR request.
CAEndpoint string
// CAEndpointSAN overrides the ServerName extracted from CAEndpoint.
CAEndpointSAN string
// The CA provider name.
CAProviderName string
// TrustDomain corresponds to the trust root of a system.
// https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain
TrustDomain string
// Whether to generate PKCS#8 private keys.
Pkcs8Keys bool
// OutputKeyCertToDir is the directory for output the key and certificate
OutputKeyCertToDir string
// ProvCert is the directory for client to provide the key and certificate to CA server when authenticating
// with mTLS. This is not used for workload mTLS communication, and is
ProvCert string
// ClusterID is the cluster where the agent resides.
// Normally initialized from ISTIO_META_CLUSTER_ID - after a tortuous journey it
// makes its way into the ClusterID metadata of Citadel gRPC request to create the cert.
// Didn't find much doc - but I suspect used for 'central cluster' use cases - so should
// match the cluster name set in the MC setup.
ClusterID string
// The type of Elliptical Signature algorithm to use
// when generating private keys. Currently only ECDSA is supported.
ECCSigAlg string
// FileMountedCerts indicates whether the proxy is using file
// mounted certs created by a foreign CA. Refresh is managed by the external
// CA, by updating the Secret or VM file. We will watch the file for changes
// or check before the cert expires. This assumes the certs are in the
// well-known ./etc/certs location.
FileMountedCerts bool
// PilotCertProvider is the provider of the Pilot certificate (PILOT_CERT_PROVIDER env)
// Determines the root CA file to use for connecting to CA gRPC:
// - istiod
// - kubernetes
// - custom
// - none
PilotCertProvider string
// secret TTL.
SecretTTL time.Duration
// The ratio of cert lifetime to refresh a cert. For example, at 0.10 and 1 hour TTL,
// we would refresh 6 minutes before expiration.
SecretRotationGracePeriodRatio float64
// STS port
STSPort int
// authentication provider specific plugins, will exchange the token
// For example exchange long lived refresh with access tokens.
// Used by the secret fetcher when signing CSRs.
// Optional; if not present the token will be used directly
TokenExchanger TokenExchanger
// credential fetcher.
CredFetcher CredFetcher
// credential identity provider
CredIdentityProvider string
// Namespace corresponding to workload
WorkloadNamespace string
// Name of the Service Account
ServiceAccount string
// XDS auth provider
XdsAuthProvider string
// Token manager for the token exchange of XDS
TokenManager TokenManager
// Cert signer info
CertSigner string
// Delay in reading certificates from file after the change is detected. This is useful in cases
// where the write operation of key and cert take longer.
FileDebounceDuration time.Duration
// Root Cert read from the OS
CARootPath string
// The path for an existing certificate chain file
CertChainFilePath string
// The path for an existing key file
KeyFilePath string
// The path for an existing root certificate bundle
RootCertFilePath string
}
// TokenManager contains methods for generating token.
type TokenManager interface {
// GenerateToken takes STS request parameters and generates token. Returns
// StsResponseParameters in JSON.
GenerateToken(parameters StsRequestParameters) ([]byte, error)
// DumpTokenStatus dumps status of all generated tokens and returns status in JSON.
DumpTokenStatus() ([]byte, error)
// GetMetadata returns the metadata headers related to the token
GetMetadata(forCA bool, xdsAuthProvider, token string) (map[string]string, error)
}
// StsRequestParameters stores all STS request attributes defined in
// https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16#section-2.1
type StsRequestParameters struct {
// REQUIRED. The value "urn:ietf:params:oauth:grant-type:token- exchange"
// indicates that a token exchange is being performed.
GrantType string
// OPTIONAL. Indicates the location of the target service or resource where
// the client intends to use the requested security token.
Resource string
// OPTIONAL. The logical name of the target service where the client intends
// to use the requested security token.
Audience string
// OPTIONAL. A list of space-delimited, case-sensitive strings, that allow
// the client to specify the desired Scope of the requested security token in the
// context of the service or Resource where the token will be used.
Scope string
// OPTIONAL. An identifier, for the type of the requested security token.
RequestedTokenType string
// REQUIRED. A security token that represents the identity of the party on
// behalf of whom the request is being made.
SubjectToken string
// REQUIRED. An identifier, that indicates the type of the security token in
// the "subject_token" parameter.
SubjectTokenType string
// OPTIONAL. A security token that represents the identity of the acting party.
ActorToken string
// An identifier, that indicates the type of the security token in the
// "actor_token" parameter.
ActorTokenType string
}
// Client interface defines the clients need to implement to talk to CA for CSR.
// The Agent will create a key pair and a CSR, and use an implementation of this
// interface to get back a signed certificate. There is no guarantee that the SAN
// in the request will be returned - server may replace it.
type Client interface {
CSRSign(csrPEM []byte, certValidTTLInSec int64) ([]string, error)
Close()
// Retrieve CA root certs If CA publishes API endpoint for this
GetRootCertBundle() ([]string, error)
}
// SecretManager defines secrets management interface which is used by SDS.
type SecretManager interface {
// GenerateSecret generates new secret for the given resource.
//
// The current implementation also watched the generated secret and trigger a callback when it is
// near expiry. It will constructs the SAN based on the token's 'sub' claim, expected to be in
// the K8S format. No other JWTs are currently supported due to client logic. If JWT is
// missing/invalid, the resourceName is used.
GenerateSecret(resourceName string) (*SecretItem, error)
}
// TokenExchanger provides common interfaces so that authentication providers could choose to implement their specific logic.
type TokenExchanger interface {
// ExchangeToken provides a common interface to exchange an existing token for a new one.
ExchangeToken(serviceAccountToken string) (string, error)
}
// SecretItem is the cached item in in-memory secret store.
type SecretItem struct {
CertificateChain []byte
PrivateKey []byte
RootCert []byte
// ResourceName passed from envoy SDS discovery request.
// "ROOTCA" for root cert request, "default" for key/cert request.
ResourceName string
CreatedTime time.Time
ExpireTime time.Time
}
type CredFetcher interface {
// GetPlatformCredential fetches workload credential provided by the platform.
GetPlatformCredential() (string, error)
// GetIdentityProvider returns the name of the IdentityProvider that can authenticate the workload credential.
GetIdentityProvider() string
// Stop releases resources and cleans up.
Stop()
}
// AuthSource represents where authentication result is derived from.
type AuthSource int
const (
AuthSourceClientCertificate AuthSource = iota
AuthSourceIDToken
)
const (
authorizationMeta = "authorization"
)
// Caller carries the identity and authentication source of a caller.
type Caller struct {
AuthSource AuthSource
Identities []string
}
type Authenticator interface {
Authenticate(ctx context.Context) (*Caller, error)
AuthenticatorType() string
AuthenticateRequest(req *http.Request) (*Caller, error)
}
func ExtractBearerToken(ctx context.Context) (string, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", fmt.Errorf("no metadata is attached")
}
authHeader, exists := md[authorizationMeta]
if !exists {
return "", fmt.Errorf("no HTTP authorization header exists")
}
for _, value := range authHeader {
if strings.HasPrefix(value, BearerTokenPrefix) {
return strings.TrimPrefix(value, BearerTokenPrefix), nil
}
}
return "", fmt.Errorf("no bearer token exists in HTTP authorization header")
}
func ExtractRequestToken(req *http.Request) (string, error) {
value := req.Header.Get(authorizationMeta)
if value == "" {
return "", fmt.Errorf("no HTTP authorization header exists")
}
if strings.HasPrefix(value, BearerTokenPrefix) {
return strings.TrimPrefix(value, BearerTokenPrefix), nil
}
if strings.HasPrefix(value, K8sTokenPrefix) {
return strings.TrimPrefix(value, K8sTokenPrefix), nil
}
return "", fmt.Errorf("no bearer token exists in HTTP authorization header")
}
// GetOSRootFilePath returns the first file path detected from a list of known CA certificate file paths.
// If none of the known CA certificate files are found, a warning in printed and an empty string is returned.
func GetOSRootFilePath() string {
// Get and store the OS CA certificate path for Linux systems
// Source of CA File Paths: https://golang.org/src/crypto/x509/root_linux.go
certFiles := []string{
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cacert.pem", // OpenELEC
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
"/etc/ssl/cert.pem", // Alpine Linux
"/usr/local/etc/ssl/cert.pem", // FreeBSD
}
for _, cert := range certFiles {
if _, err := os.Stat(cert); err == nil {
istiolog.Debugf("Using OS CA certificate for proxy: %s", cert)
return cert
}
}
istiolog.Warn("OS CA Cert could not be found for agent")
return ""
}
// CheckWorkloadCertificate returns true when the workload certificate
// files are present under the provided paths. Otherwise, return false.
func CheckWorkloadCertificate(certChainFilePath, keyFilePath, rootCertFilePath string) bool {
if _, err := os.Stat(certChainFilePath); err != nil {
return false
}
if _, err := os.Stat(keyFilePath); err != nil {
return false
}
if _, err := os.Stat(rootCertFilePath); err != nil {
return false
}
return true
}
type SdsCertificateConfig struct {
CertificatePath string
PrivateKeyPath string
CaCertificatePath string
}
const (
ResourceSeparator = "~"
)
// GetResourceName converts a SdsCertificateConfig to a string to be used as an SDS resource name
func (s SdsCertificateConfig) GetResourceName() string {
if s.IsKeyCertificate() {
return "file-cert:" + s.CertificatePath + ResourceSeparator + s.PrivateKeyPath // Format: file-cert:%s~%s
}
return ""
}
// GetRootResourceName converts a SdsCertificateConfig to a string to be used as an SDS resource name for the root
func (s SdsCertificateConfig) GetRootResourceName() string {
if s.IsRootCertificate() {
return "file-root:" + s.CaCertificatePath // Format: file-root:%s
}
return ""
}
// IsRootCertificate returns true if this config represents a root certificate config.
func (s SdsCertificateConfig) IsRootCertificate() bool {
return s.CaCertificatePath != ""
}
// IsKeyCertificate returns true if this config represents key certificate config.
func (s SdsCertificateConfig) IsKeyCertificate() bool {
return s.CertificatePath != "" && s.PrivateKeyPath != ""
}
// SdsCertificateConfigFromResourceName converts the provided resource name into a SdsCertificateConfig
// If the resource name is not valid, false is returned.
func SdsCertificateConfigFromResourceName(resource string) (SdsCertificateConfig, bool) {
if strings.HasPrefix(resource, "file-cert:") {
filesString := strings.TrimPrefix(resource, "file-cert:")
split := strings.Split(filesString, ResourceSeparator)
if len(split) != 2 {
return SdsCertificateConfig{}, false
}
return SdsCertificateConfig{split[0], split[1], ""}, true
} else if strings.HasPrefix(resource, "file-root:") {
filesString := strings.TrimPrefix(resource, "file-root:")
split := strings.Split(filesString, ResourceSeparator)
if len(split) != 1 {
return SdsCertificateConfig{}, false
}
return SdsCertificateConfig{"", "", split[0]}, true
} else {
return SdsCertificateConfig{}, false
}
}
// SdsCertificateConfigFromResourceNameForOSCACert converts the OS resource name into a SdsCertificateConfig
func SdsCertificateConfigFromResourceNameForOSCACert(resource string) (SdsCertificateConfig, bool) {
if resource == "" {
return SdsCertificateConfig{}, false
}
return SdsCertificateConfig{"", "", resource}, true
}