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  }