istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/spiffe/spiffe.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 spiffe 16 17 import ( 18 "crypto/tls" 19 "crypto/x509" 20 "encoding/json" 21 "encoding/pem" 22 "fmt" 23 "net" 24 "net/http" 25 "net/url" 26 "strings" 27 "sync" 28 "time" 29 30 jose "github.com/go-jose/go-jose/v3" 31 32 "istio.io/istio/pkg/config/constants" 33 "istio.io/istio/pkg/log" 34 "istio.io/istio/pkg/util/sets" 35 ) 36 37 const ( 38 Scheme = "spiffe" 39 40 URIPrefix = Scheme + "://" 41 URIPrefixLen = len(URIPrefix) 42 43 // The default SPIFFE URL value for trust domain 44 defaultTrustDomain = constants.DefaultClusterLocalDomain 45 46 ServiceAccountSegment = "sa" 47 NamespaceSegment = "ns" 48 ) 49 50 var ( 51 trustDomain = defaultTrustDomain 52 trustDomainMutex sync.RWMutex 53 54 firstRetryBackOffTime = time.Millisecond * 50 55 56 spiffeLog = log.RegisterScope("spiffe", "SPIFFE library logging") 57 ) 58 59 type Identity struct { 60 TrustDomain string 61 Namespace string 62 ServiceAccount string 63 } 64 65 func ParseIdentity(s string) (Identity, error) { 66 if !strings.HasPrefix(s, URIPrefix) { 67 return Identity{}, fmt.Errorf("identity is not a spiffe format") 68 } 69 split := strings.Split(s[URIPrefixLen:], "/") 70 if len(split) != 5 { 71 return Identity{}, fmt.Errorf("identity is not a spiffe format") 72 } 73 if split[1] != NamespaceSegment || split[3] != ServiceAccountSegment { 74 return Identity{}, fmt.Errorf("identity is not a spiffe format") 75 } 76 return Identity{ 77 TrustDomain: split[0], 78 Namespace: split[2], 79 ServiceAccount: split[4], 80 }, nil 81 } 82 83 func (i Identity) String() string { 84 return URIPrefix + i.TrustDomain + "/ns/" + i.Namespace + "/sa/" + i.ServiceAccount 85 } 86 87 type bundleDoc struct { 88 jose.JSONWebKeySet 89 Sequence uint64 `json:"spiffe_sequence,omitempty"` 90 RefreshHint int `json:"spiffe_refresh_hint,omitempty"` 91 } 92 93 func SetTrustDomain(value string) { 94 // Replace special characters in spiffe 95 v := strings.Replace(value, "@", ".", -1) 96 trustDomainMutex.Lock() 97 trustDomain = v 98 trustDomainMutex.Unlock() 99 } 100 101 func GetTrustDomain() string { 102 trustDomainMutex.RLock() 103 defer trustDomainMutex.RUnlock() 104 return trustDomain 105 } 106 107 // GenSpiffeURI returns the formatted uri(SPIFFE format for now) for the certificate. 108 func GenSpiffeURI(ns, serviceAccount string) (string, error) { 109 var err error 110 if ns == "" || serviceAccount == "" { 111 err = fmt.Errorf( 112 "namespace or service account empty for SPIFFE uri ns=%v serviceAccount=%v", ns, serviceAccount) 113 } 114 return URIPrefix + GetTrustDomain() + "/ns/" + ns + "/sa/" + serviceAccount, err 115 } 116 117 // MustGenSpiffeURI returns the formatted uri(SPIFFE format for now) for the certificate and logs if there was an error. 118 func MustGenSpiffeURI(ns, serviceAccount string) string { 119 uri, err := GenSpiffeURI(ns, serviceAccount) 120 if err != nil { 121 spiffeLog.Debug(err.Error()) 122 } 123 return uri 124 } 125 126 // ExpandWithTrustDomains expands a given spiffe identities, plus a list of trust domain aliases. 127 // We ensure the returned list does not contain duplicates; the original input is always retained. 128 // For example, 129 // ExpandWithTrustDomains({"spiffe://td1/ns/def/sa/def"}, {"td1", "td2"}) returns 130 // 131 // {"spiffe://td1/ns/def/sa/def", "spiffe://td2/ns/def/sa/def"}. 132 // 133 // ExpandWithTrustDomains({"spiffe://td1/ns/def/sa/a", "spiffe://td1/ns/def/sa/b"}, {"td2"}) returns 134 // 135 // {"spiffe://td1/ns/def/sa/a", "spiffe://td2/ns/def/sa/a", "spiffe://td1/ns/def/sa/b", "spiffe://td2/ns/def/sa/b"}. 136 func ExpandWithTrustDomains(spiffeIdentities sets.String, trustDomainAliases []string) sets.String { 137 if len(trustDomainAliases) == 0 { 138 return spiffeIdentities 139 } 140 out := sets.New[string]() 141 for id := range spiffeIdentities { 142 out.Insert(id) 143 // Skip if not a SPIFFE identity - This can happen for example if the identity is a DNS name. 144 if !strings.HasPrefix(id, URIPrefix) { 145 continue 146 } 147 // Expand with aliases set. 148 m, err := ParseIdentity(id) 149 if err != nil { 150 spiffeLog.Errorf("Failed to extract SPIFFE trust domain from %v: %v", id, err) 151 continue 152 } 153 for _, td := range trustDomainAliases { 154 m.TrustDomain = td 155 out.Insert(m.String()) 156 } 157 } 158 return out 159 } 160 161 // GetTrustDomainFromURISAN extracts the trust domain part from the URI SAN in the X.509 certificate. 162 func GetTrustDomainFromURISAN(uriSan string) (string, error) { 163 parsed, err := ParseIdentity(uriSan) 164 if err != nil { 165 return "", fmt.Errorf("failed to parse URI SAN %s. Error: %v", uriSan, err) 166 } 167 return parsed.TrustDomain, nil 168 } 169 170 // RetrieveSpiffeBundleRootCerts retrieves the trusted CA certificates from a list of SPIFFE bundle endpoints. 171 // It can use the system cert pool and the supplied certificates to validate the endpoints. 172 func RetrieveSpiffeBundleRootCerts(config map[string]string, caCertPool *x509.CertPool, retryTimeout time.Duration) ( 173 map[string][]*x509.Certificate, error, 174 ) { 175 httpClient := &http.Client{ 176 Timeout: time.Second * 10, 177 } 178 179 ret := map[string][]*x509.Certificate{} 180 for trustDomain, endpoint := range config { 181 if !strings.HasPrefix(endpoint, "https://") { 182 endpoint = "https://" + endpoint 183 } 184 u, err := url.Parse(endpoint) 185 if err != nil { 186 return nil, fmt.Errorf("failed to split the SPIFFE bundle URL: %v", err) 187 } 188 189 config := &tls.Config{ 190 ServerName: u.Hostname(), 191 RootCAs: caCertPool, 192 MinVersion: tls.VersionTLS12, 193 } 194 195 httpClient.Transport = &http.Transport{ 196 Proxy: http.ProxyFromEnvironment, 197 TLSClientConfig: config, 198 DialContext: (&net.Dialer{ 199 Timeout: time.Second * 10, 200 }).DialContext, 201 IdleConnTimeout: 90 * time.Second, 202 TLSHandshakeTimeout: 10 * time.Second, 203 ExpectContinueTimeout: 1 * time.Second, 204 } 205 206 retryBackoffTime := firstRetryBackOffTime 207 startTime := time.Now() 208 var resp *http.Response 209 for { 210 resp, err = httpClient.Get(endpoint) 211 var errMsg string 212 if err != nil { 213 errMsg = fmt.Sprintf("Calling %s failed with error: %v", endpoint, err) 214 } else if resp == nil { 215 errMsg = fmt.Sprintf("Calling %s failed with nil response", endpoint) 216 } else if resp.StatusCode != http.StatusOK { 217 b := make([]byte, 1024) 218 n, _ := resp.Body.Read(b) 219 errMsg = fmt.Sprintf("Calling %s failed with unexpected status: %v, fetching bundle: %s", 220 endpoint, resp.StatusCode, string(b[:n])) 221 } else { 222 break 223 } 224 225 if startTime.Add(retryTimeout).Before(time.Now()) { 226 return nil, fmt.Errorf("exhausted retries to fetch the SPIFFE bundle %s from url %s. Latest error: %v", 227 trustDomain, endpoint, errMsg) 228 } 229 230 spiffeLog.Warnf("%s, retry in %v", errMsg, retryBackoffTime) 231 time.Sleep(retryBackoffTime) 232 retryBackoffTime *= 2 // Exponentially increase the retry backoff time. 233 } 234 defer resp.Body.Close() 235 236 doc := new(bundleDoc) 237 if err := json.NewDecoder(resp.Body).Decode(doc); err != nil { 238 return nil, fmt.Errorf("trust domain [%s] at URL [%s] failed to decode bundle: %v", trustDomain, endpoint, err) 239 } 240 241 var certs []*x509.Certificate 242 for i, key := range doc.Keys { 243 if key.Use == "x509-svid" { 244 if len(key.Certificates) != 1 { 245 return nil, fmt.Errorf("trust domain [%s] at URL [%s] expected 1 certificate in x509-svid entry %d; got %d", 246 trustDomain, endpoint, i, len(key.Certificates)) 247 } 248 certs = append(certs, key.Certificates[0]) 249 } 250 } 251 if len(certs) == 0 { 252 return nil, fmt.Errorf("trust domain [%s] at URL [%s] does not provide a X509 SVID", trustDomain, endpoint) 253 } 254 ret[trustDomain] = certs 255 } 256 for trustDomain, certs := range ret { 257 spiffeLog.Infof("Loaded SPIFFE trust bundle for: %v, containing %d certs", trustDomain, len(certs)) 258 } 259 return ret, nil 260 } 261 262 // PeerCertVerifier is an instance to verify the peer certificate in the SPIFFE way using the retrieved root certificates. 263 type PeerCertVerifier struct { 264 generalCertPool *x509.CertPool 265 certPools map[string]*x509.CertPool 266 } 267 268 // NewPeerCertVerifier returns a new PeerCertVerifier. 269 func NewPeerCertVerifier() *PeerCertVerifier { 270 return &PeerCertVerifier{ 271 generalCertPool: x509.NewCertPool(), 272 certPools: make(map[string]*x509.CertPool), 273 } 274 } 275 276 // GetGeneralCertPool returns generalCertPool containing all root certs. 277 func (v *PeerCertVerifier) GetGeneralCertPool() *x509.CertPool { 278 return v.generalCertPool 279 } 280 281 // AddMapping adds a new trust domain to certificates mapping to the certPools map. 282 func (v *PeerCertVerifier) AddMapping(trustDomain string, certs []*x509.Certificate) { 283 if v.certPools[trustDomain] == nil { 284 v.certPools[trustDomain] = x509.NewCertPool() 285 } 286 for _, cert := range certs { 287 v.certPools[trustDomain].AddCert(cert) 288 v.generalCertPool.AddCert(cert) 289 } 290 spiffeLog.Infof("Added %d certs to trust domain %s in peer cert verifier", len(certs), trustDomain) 291 } 292 293 // AddMappingFromPEM adds multiple RootCA's to the spiffe Trust bundle in the trustDomain namespace 294 func (v *PeerCertVerifier) AddMappingFromPEM(trustDomain string, rootCertBytes []byte) error { 295 block, rest := pem.Decode(rootCertBytes) 296 var blockBytes []byte 297 298 // Loop while there are no block are found 299 for block != nil { 300 blockBytes = append(blockBytes, block.Bytes...) 301 block, rest = pem.Decode(rest) 302 } 303 304 rootCAs, err := x509.ParseCertificates(blockBytes) 305 if err != nil { 306 spiffeLog.Errorf("parse certificate from rootPEM got error: %v", err) 307 return fmt.Errorf("parse certificate from rootPEM got error: %v", err) 308 } 309 310 v.AddMapping(trustDomain, rootCAs) 311 return nil 312 } 313 314 // AddMappings merges a trust domain to certs map to the certPools map. 315 func (v *PeerCertVerifier) AddMappings(certMap map[string][]*x509.Certificate) { 316 for trustDomain, certs := range certMap { 317 v.AddMapping(trustDomain, certs) 318 } 319 } 320 321 // VerifyPeerCert is an implementation of tls.Config.VerifyPeerCertificate. 322 // It verifies the peer certificate using the root certificates associated with its trust domain. 323 func (v *PeerCertVerifier) VerifyPeerCert(rawCerts [][]byte, _ [][]*x509.Certificate) error { 324 if len(rawCerts) == 0 { 325 // Peer doesn't present a certificate. Just skip. Other authn methods may be used. 326 return nil 327 } 328 var peerCert *x509.Certificate 329 intCertPool := x509.NewCertPool() 330 for id, rawCert := range rawCerts { 331 cert, err := x509.ParseCertificate(rawCert) 332 if err != nil { 333 return err 334 } 335 if id == 0 { 336 peerCert = cert 337 } else { 338 intCertPool.AddCert(cert) 339 } 340 } 341 if len(peerCert.URIs) != 1 { 342 return fmt.Errorf("peer certificate does not contain 1 URI type SAN, detected %d", len(peerCert.URIs)) 343 } 344 trustDomain, err := GetTrustDomainFromURISAN(peerCert.URIs[0].String()) 345 if err != nil { 346 return err 347 } 348 rootCertPool, ok := v.certPools[trustDomain] 349 if !ok { 350 return fmt.Errorf("no cert pool found for trust domain %s", trustDomain) 351 } 352 353 _, err = peerCert.Verify(x509.VerifyOptions{ 354 Roots: rootCertPool, 355 Intermediates: intCertPool, 356 }) 357 return err 358 }