istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/pki/ra/k8s_ra.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 ra 16 17 import ( 18 "bytes" 19 "fmt" 20 "strings" 21 "sync" 22 "time" 23 24 cert "k8s.io/api/certificates/v1" 25 clientset "k8s.io/client-go/kubernetes" 26 27 meshconfig "istio.io/api/mesh/v1alpha1" 28 "istio.io/istio/pkg/log" 29 "istio.io/istio/security/pkg/k8s/chiron" 30 "istio.io/istio/security/pkg/pki/ca" 31 raerror "istio.io/istio/security/pkg/pki/error" 32 "istio.io/istio/security/pkg/pki/util" 33 ) 34 35 // KubernetesRA integrated with an external CA using Kubernetes CSR API 36 type KubernetesRA struct { 37 csrInterface clientset.Interface 38 keyCertBundle *util.KeyCertBundle 39 raOpts *IstioRAOptions 40 caCertificatesFromMeshConfig map[string]string 41 certSignerDomain string 42 // mutex protects the R/W to caCertificatesFromMeshConfig. 43 mutex sync.RWMutex 44 } 45 46 var pkiRaLog = log.RegisterScope("pkira", "Istiod RA log") 47 48 // NewKubernetesRA : Create a RA that interfaces with K8S CSR CA 49 func NewKubernetesRA(raOpts *IstioRAOptions) (*KubernetesRA, error) { 50 keyCertBundle, err := util.NewKeyCertBundleWithRootCertFromFile(raOpts.CaCertFile) 51 if err != nil { 52 return nil, raerror.NewError(raerror.CAInitFail, fmt.Errorf("error processing Certificate Bundle for Kubernetes RA")) 53 } 54 istioRA := &KubernetesRA{ 55 csrInterface: raOpts.K8sClient, 56 raOpts: raOpts, 57 keyCertBundle: keyCertBundle, 58 certSignerDomain: raOpts.CertSignerDomain, 59 caCertificatesFromMeshConfig: make(map[string]string), 60 } 61 return istioRA, nil 62 } 63 64 func (r *KubernetesRA) kubernetesSign(csrPEM []byte, caCertFile string, certSigner string, 65 requestedLifetime time.Duration, 66 ) ([]byte, error) { 67 certSignerDomain := r.certSignerDomain 68 if certSignerDomain == "" && certSigner != "" { 69 return nil, raerror.NewError(raerror.CertGenError, fmt.Errorf("certSignerDomain is required for signer %s", certSigner)) 70 } 71 if certSignerDomain != "" && certSigner != "" { 72 certSigner = certSignerDomain + "/" + certSigner 73 } else { 74 certSigner = r.raOpts.CaSigner 75 } 76 usages := []cert.KeyUsage{ 77 cert.UsageDigitalSignature, 78 cert.UsageKeyEncipherment, 79 cert.UsageServerAuth, 80 cert.UsageClientAuth, 81 } 82 certChain, _, err := chiron.SignCSRK8s(r.csrInterface, csrPEM, certSigner, usages, "", caCertFile, true, false, requestedLifetime) 83 if err != nil { 84 return nil, raerror.NewError(raerror.CertGenError, err) 85 } 86 return certChain, err 87 } 88 89 // Sign takes a PEM-encoded CSR and cert opts, and returns a certificate signed by k8s CA. 90 func (r *KubernetesRA) Sign(csrPEM []byte, certOpts ca.CertOpts) ([]byte, error) { 91 _, err := preSign(r.raOpts, csrPEM, certOpts.SubjectIDs, certOpts.TTL, certOpts.ForCA) 92 if err != nil { 93 return nil, err 94 } 95 certSigner := certOpts.CertSigner 96 97 return r.kubernetesSign(csrPEM, r.raOpts.CaCertFile, certSigner, certOpts.TTL) 98 } 99 100 // SignWithCertChain is similar to Sign but returns the leaf cert and the entire cert chain. 101 // root cert comes from two sources, order matters: 102 // 1. Specified in mesh config 103 // 2. Extract from the cert-chain signed by the CSR signer. 104 // If no root cert can be found from either of the two sources, error returned. 105 // There are several possible situations: 106 // 1. root cert is specified in mesh config and is empty in signed cert chain, in this case 107 // we verify the signed cert chain against the root cert from mesh config and append the 108 // root cert into the cert chain. 109 // 2. root cert is specified in mesh config and also can be extracted in signed cert chain, in this 110 // case we verify the signed cert chain against the root cert from mesh config and append it 111 // into the cert chain if the two root certs are different. This is typical when 112 // the returned cert chain only contains the intermediate CA. 113 // 3. root cert is not specified in mesh config but can be extracted in signed cert chain, in this case 114 // we verify the signed cert chain against the root cert and return the cert chain directly. 115 func (r *KubernetesRA) SignWithCertChain(csrPEM []byte, certOpts ca.CertOpts) ([]string, error) { 116 cert, err := r.Sign(csrPEM, certOpts) 117 if err != nil { 118 return nil, err 119 } 120 chainPem := r.GetCAKeyCertBundle().GetCertChainPem() 121 if len(chainPem) > 0 { 122 cert = append(cert, chainPem...) 123 } 124 respCertChain := []string{string(cert)} 125 var possibleRootCert, rootCertFromMeshConfig, rootCertFromCertChain []byte 126 certSigner := r.certSignerDomain + "/" + certOpts.CertSigner 127 if len(r.GetCAKeyCertBundle().GetRootCertPem()) == 0 { 128 rootCertFromCertChain, err = util.FindRootCertFromCertificateChainBytes(cert) 129 if err != nil { 130 pkiRaLog.Infof("failed to find root cert from signed cert-chain (%v)", err.Error()) 131 } 132 rootCertFromMeshConfig, err = r.GetRootCertFromMeshConfig(certSigner) 133 if err != nil { 134 pkiRaLog.Infof("failed to find root cert from mesh config (%v)", err.Error()) 135 } 136 if rootCertFromMeshConfig != nil { 137 possibleRootCert = rootCertFromMeshConfig 138 } else if rootCertFromCertChain != nil { 139 possibleRootCert = rootCertFromCertChain 140 } 141 if possibleRootCert == nil { 142 return nil, raerror.NewError(raerror.CSRError, fmt.Errorf("failed to find root cert from either signed cert-chain or mesh config")) 143 } 144 if verifyErr := util.VerifyCertificate(nil, cert, possibleRootCert, nil); verifyErr != nil { 145 return nil, raerror.NewError(raerror.CSRError, fmt.Errorf("root cert from signed cert-chain is invalid (%v)", verifyErr)) 146 } 147 if !bytes.Equal(possibleRootCert, rootCertFromCertChain) { 148 respCertChain = append(respCertChain, string(possibleRootCert)) 149 } 150 } 151 return respCertChain, nil 152 } 153 154 // GetCAKeyCertBundle returns the KeyCertBundle for the CA. 155 func (r *KubernetesRA) GetCAKeyCertBundle() *util.KeyCertBundle { 156 return r.keyCertBundle 157 } 158 159 func (r *KubernetesRA) SetCACertificatesFromMeshConfig(caCertificates []*meshconfig.MeshConfig_CertificateData) { 160 r.mutex.Lock() 161 for _, pemCert := range caCertificates { 162 // TODO: take care of spiffe bundle format as well 163 cert := pemCert.GetPem() 164 certSigners := pemCert.CertSigners 165 if len(certSigners) != 0 { 166 certSigner := strings.Join(certSigners, ",") 167 if cert != "" { 168 r.caCertificatesFromMeshConfig[certSigner] = cert 169 } 170 } 171 } 172 r.mutex.Unlock() 173 } 174 175 func (r *KubernetesRA) GetRootCertFromMeshConfig(signerName string) ([]byte, error) { 176 r.mutex.RLock() 177 defer r.mutex.RUnlock() 178 caCertificates := r.caCertificatesFromMeshConfig 179 if len(caCertificates) == 0 { 180 return nil, fmt.Errorf("no caCertificates defined in mesh config") 181 } 182 for signers, caCertificate := range caCertificates { 183 signerList := strings.Split(signers, ",") 184 if len(signerList) == 0 { 185 continue 186 } 187 for _, signer := range signerList { 188 if signer == signerName { 189 return []byte(caCertificate), nil 190 } 191 } 192 } 193 return nil, fmt.Errorf("failed to find root cert for signer: %v in mesh config", signerName) 194 }