istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/bootstrap/istio_ca.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 bootstrap
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"os"
    23  	"path"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/fsnotify/fsnotify"
    28  	"google.golang.org/grpc"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  
    32  	"istio.io/api/security/v1beta1"
    33  	"istio.io/istio/pilot/pkg/features"
    34  	securityModel "istio.io/istio/pilot/pkg/security/model"
    35  	"istio.io/istio/pkg/config/constants"
    36  	"istio.io/istio/pkg/env"
    37  	"istio.io/istio/pkg/log"
    38  	"istio.io/istio/pkg/security"
    39  	"istio.io/istio/security/pkg/cmd"
    40  	"istio.io/istio/security/pkg/pki/ca"
    41  	"istio.io/istio/security/pkg/pki/ra"
    42  	caserver "istio.io/istio/security/pkg/server/ca"
    43  	"istio.io/istio/security/pkg/server/ca/authenticate"
    44  	"istio.io/istio/security/pkg/util"
    45  )
    46  
    47  type caOptions struct {
    48  	ExternalCAType   ra.CaExternalType
    49  	ExternalCASigner string
    50  	// domain to use in SPIFFE identity URLs
    51  	TrustDomain      string
    52  	Namespace        string
    53  	Authenticators   []security.Authenticator
    54  	CertSignerDomain string
    55  }
    56  
    57  // Based on istio_ca main - removing creation of Secrets with private keys in all namespaces and install complexity.
    58  //
    59  // For backward compat, will preserve support for the "cacerts" Secret used for self-signed certificates.
    60  // It is mounted in the same location, and if found will be used - creating the secret is sufficient, no need for
    61  // extra options.
    62  //
    63  // In old installer, the LocalCertDir is hardcoded to /etc/cacerts and mounted from "cacerts" secret.
    64  //
    65  // Support for signing other root CA has been removed - too dangerous, no clear use case.
    66  //
    67  // Default config, for backward compat with Citadel:
    68  // - if "cacerts" secret exists in istio-system, will be mounted. It may contain an optional "root-cert.pem",
    69  // with additional roots and optional {ca-key, ca-cert, cert-chain}.pem user-provided root CA.
    70  // - if user-provided root CA is not found, the Secret "istio-ca-secret" is used, with ca-cert.pem and ca-key.pem files.
    71  // - if neither is found, istio-ca-secret will be created.
    72  //
    73  // - a config map "istio-security" with a "caTLSRootCert" file will be used for root cert, and created if needed.
    74  //   The config map was used by node agent - no longer possible to use in sds-agent, but we still save it for
    75  //   backward compat. Will be removed with the node-agent. sds-agent is calling NewCitadelClient directly, using
    76  //   K8S root.
    77  
    78  var (
    79  	// LocalCertDir replaces the "cert-chain", "signing-cert" and "signing-key" flags in citadel - Istio installer is
    80  	// requires a secret named "cacerts" with specific files inside.
    81  	LocalCertDir = env.Register("ROOT_CA_DIR", "./etc/cacerts",
    82  		"Location of a local or mounted CA root")
    83  
    84  	useRemoteCerts = env.Register("USE_REMOTE_CERTS", false,
    85  		"Whether to try to load CA certs from config Kubernetes cluster. Used for external Istiod.")
    86  
    87  	workloadCertTTL = env.Register("DEFAULT_WORKLOAD_CERT_TTL",
    88  		cmd.DefaultWorkloadCertTTL,
    89  		"The default TTL of issued workload certificates. Applied when the client sets a "+
    90  			"non-positive TTL in the CSR.")
    91  
    92  	maxWorkloadCertTTL = env.Register("MAX_WORKLOAD_CERT_TTL",
    93  		cmd.DefaultMaxWorkloadCertTTL,
    94  		"The max TTL of issued workload certificates.")
    95  
    96  	SelfSignedCACertTTL = env.Register("CITADEL_SELF_SIGNED_CA_CERT_TTL",
    97  		cmd.DefaultSelfSignedCACertTTL,
    98  		"The TTL of self-signed CA root certificate.")
    99  
   100  	selfSignedRootCertCheckInterval = env.Register("CITADEL_SELF_SIGNED_ROOT_CERT_CHECK_INTERVAL",
   101  		cmd.DefaultSelfSignedRootCertCheckInterval,
   102  		"The interval that self-signed CA checks its root certificate "+
   103  			"expiration time and rotates root certificate. Setting this interval "+
   104  			"to zero or a negative value disables automated root cert check and "+
   105  			"rotation. This interval is suggested to be larger than 10 minutes.")
   106  
   107  	selfSignedRootCertGracePeriodPercentile = env.Register("CITADEL_SELF_SIGNED_ROOT_CERT_GRACE_PERIOD_PERCENTILE",
   108  		cmd.DefaultRootCertGracePeriodPercentile,
   109  		"Grace period percentile for self-signed root cert.")
   110  
   111  	enableJitterForRootCertRotator = env.Register("CITADEL_ENABLE_JITTER_FOR_ROOT_CERT_ROTATOR",
   112  		true,
   113  		"If true, set up a jitter to start root cert rotator. "+
   114  			"Jitter selects a backoff time in seconds to start root cert rotator, "+
   115  			"and the back off time is below root cert check interval.")
   116  
   117  	k8sInCluster = env.Register("KUBERNETES_SERVICE_HOST", "",
   118  		"Kubernetes service host, set automatically when running in-cluster")
   119  
   120  	// This value can also be extracted from the mounted token
   121  	trustedIssuer = env.Register("TOKEN_ISSUER", "",
   122  		"OIDC token issuer. If set, will be used to check the tokens.")
   123  
   124  	audience = env.Register("AUDIENCE", "",
   125  		"Expected audience in the tokens. ")
   126  
   127  	caRSAKeySize = env.Register("CITADEL_SELF_SIGNED_CA_RSA_KEY_SIZE", 2048,
   128  		"Specify the RSA key size to use for self-signed Istio CA certificates.")
   129  
   130  	// TODO: Likely to be removed and added to mesh config
   131  	externalCaType = env.Register("EXTERNAL_CA", "",
   132  		"External CA Integration Type. Permitted value is ISTIOD_RA_KUBERNETES_API.").Get()
   133  
   134  	// TODO: Likely to be removed and added to mesh config
   135  	k8sSigner = env.Register("K8S_SIGNER", "",
   136  		"Kubernetes CA Signer type. Valid from Kubernetes 1.18").Get()
   137  )
   138  
   139  // initCAServer create a CA Server. The CA API uses cert with the max workload cert TTL.
   140  // 'hostlist' must be non-empty - but is not used since CA Server will start on existing
   141  // grpc server. Adds client cert auth and kube (sds enabled)
   142  func (s *Server) initCAServer(ca caserver.CertificateAuthority, opts *caOptions) {
   143  	caServer, startErr := caserver.New(ca, maxWorkloadCertTTL.Get(), opts.Authenticators, s.multiclusterController)
   144  	if startErr != nil {
   145  		log.Fatalf("failed to create istio ca server: %v", startErr)
   146  	}
   147  	s.caServer = caServer
   148  }
   149  
   150  // RunCA will start the cert signing GRPC service on an existing server.
   151  // Protected by installer options: the CA will be started only if the JWT token in /var/run/secrets
   152  // is mounted. If it is missing - for example old versions of K8S that don't support such tokens -
   153  // we will not start the cert-signing server, since pods will have no way to authenticate.
   154  func (s *Server) RunCA(grpc *grpc.Server) {
   155  	iss := trustedIssuer.Get()
   156  	aud := audience.Get()
   157  
   158  	token, err := os.ReadFile(securityModel.ThirdPartyJwtPath)
   159  	if err == nil {
   160  		tok, err := detectAuthEnv(string(token))
   161  		if err != nil {
   162  			log.Warnf("Starting with invalid K8S JWT token: %v", err)
   163  		} else {
   164  			if iss == "" {
   165  				iss = tok.Iss
   166  			}
   167  			if len(tok.Aud) > 0 && len(aud) == 0 {
   168  				aud = tok.Aud[0]
   169  			}
   170  		}
   171  	}
   172  
   173  	// TODO: if not set, parse Istiod's own token (if present) and get the issuer. The same issuer is used
   174  	// for all tokens - no need to configure twice. The token may also include cluster info to auto-configure
   175  	// networking properties.
   176  	if iss != "" && // issuer set explicitly or extracted from our own JWT
   177  		k8sInCluster.Get() == "" { // not running in cluster - in cluster use direct call to apiserver
   178  		// Add a custom authenticator using standard JWT validation, if not running in K8S
   179  		// When running inside K8S - we can use the built-in validator, which also check pod removal (invalidation).
   180  		jwtRule := v1beta1.JWTRule{Issuer: iss, Audiences: []string{aud}}
   181  		oidcAuth, err := authenticate.NewJwtAuthenticator(&jwtRule)
   182  		if err == nil {
   183  			s.caServer.Authenticators = append(s.caServer.Authenticators, oidcAuth)
   184  			log.Info("Using out-of-cluster JWT authentication")
   185  		} else {
   186  			log.Info("K8S token doesn't support OIDC, using only in-cluster auth")
   187  		}
   188  	}
   189  
   190  	s.caServer.Register(grpc)
   191  
   192  	log.Info("Istiod CA has started")
   193  }
   194  
   195  // detectAuthEnv will use the JWT token that is mounted in istiod to set the default audience
   196  // and trust domain for Istiod, if not explicitly defined.
   197  // K8S will use the same kind of tokens for the pods, and the value in istiod's own token is
   198  // simplest and safest way to have things match.
   199  //
   200  // Note that K8S is not required to use JWT tokens - we will fallback to the defaults
   201  // or require explicit user option for K8S clusters using opaque tokens.
   202  func detectAuthEnv(jwt string) (*authenticate.JwtPayload, error) {
   203  	jwtSplit := strings.Split(jwt, ".")
   204  	if len(jwtSplit) != 3 {
   205  		return nil, fmt.Errorf("invalid JWT parts: %s", jwt)
   206  	}
   207  	payload := jwtSplit[1]
   208  
   209  	payloadBytes, err := util.DecodeJwtPart(payload)
   210  	if err != nil {
   211  		return nil, fmt.Errorf("failed to decode jwt: %v", err.Error())
   212  	}
   213  
   214  	structuredPayload := &authenticate.JwtPayload{}
   215  	err = json.Unmarshal(payloadBytes, &structuredPayload)
   216  	if err != nil {
   217  		return nil, fmt.Errorf("failed to unmarshal jwt: %v", err.Error())
   218  	}
   219  
   220  	return structuredPayload, nil
   221  }
   222  
   223  // detectSigningCABundle determines in which format the signing ca files are created.
   224  // kubernetes tls secrets mount files as tls.crt,tls.key,ca.crt
   225  // istiod secret is ca-cert.pem ca-key.pem cert-chain.pem root-cert.pem
   226  func detectSigningCABundle() (ca.SigningCAFileBundle, error) {
   227  	tlsSigningFile := path.Join(LocalCertDir.Get(), ca.TLSSecretCACertFile)
   228  
   229  	// looking for tls file format (tls.crt)
   230  	if _, err := os.Stat(tlsSigningFile); err == nil {
   231  		log.Info("Using kubernetes.io/tls secret type for signing ca files")
   232  		return ca.SigningCAFileBundle{
   233  			RootCertFile: path.Join(LocalCertDir.Get(), ca.TLSSecretRootCertFile),
   234  			CertChainFiles: []string{
   235  				tlsSigningFile,
   236  				path.Join(LocalCertDir.Get(), ca.TLSSecretRootCertFile),
   237  			},
   238  			SigningCertFile: tlsSigningFile,
   239  			SigningKeyFile:  path.Join(LocalCertDir.Get(), ca.TLSSecretCAPrivateKeyFile),
   240  		}, nil
   241  	} else if !os.IsNotExist(err) {
   242  		return ca.SigningCAFileBundle{}, err
   243  	}
   244  
   245  	log.Info("Using istiod file format for signing ca files")
   246  	// default ca file format
   247  	return ca.SigningCAFileBundle{
   248  		RootCertFile:    path.Join(LocalCertDir.Get(), ca.RootCertFile),
   249  		CertChainFiles:  []string{path.Join(LocalCertDir.Get(), ca.CertChainFile)},
   250  		SigningCertFile: path.Join(LocalCertDir.Get(), ca.CACertFile),
   251  		SigningKeyFile:  path.Join(LocalCertDir.Get(), ca.CAPrivateKeyFile),
   252  	}, nil
   253  }
   254  
   255  // loadCACerts loads an existing `cacerts` Secret if the files aren't mounted locally.
   256  // By default, a cacerts Secret would be mounted during pod startup due to the
   257  // Istiod Deployment configuration. But with external Istiod, we want to be
   258  // able to load cacerts from a remote cluster instead.
   259  func (s *Server) loadCACerts(caOpts *caOptions, dir string) error {
   260  	if s.kubeClient == nil {
   261  		return nil
   262  	}
   263  
   264  	signingKeyFile := path.Join(dir, ca.CAPrivateKeyFile)
   265  	if _, err := os.Stat(signingKeyFile); err == nil {
   266  		return nil
   267  	} else if !os.IsNotExist(err) {
   268  		return fmt.Errorf("signing key file %s already exists", signingKeyFile)
   269  	}
   270  
   271  	secret, err := s.kubeClient.Kube().CoreV1().Secrets(caOpts.Namespace).Get(
   272  		context.TODO(), ca.CACertsSecret, metav1.GetOptions{})
   273  	if err != nil {
   274  		if errors.IsNotFound(err) {
   275  			return nil
   276  		}
   277  		return err
   278  	}
   279  
   280  	log.Infof("cacerts Secret found in config cluster, saving contents to %s", dir)
   281  	if err := os.MkdirAll(dir, 0o700); err != nil {
   282  		return err
   283  	}
   284  	for key, data := range secret.Data {
   285  		filename := path.Join(dir, key)
   286  		if err := os.WriteFile(filename, data, 0o600); err != nil {
   287  			return err
   288  		}
   289  	}
   290  	return nil
   291  }
   292  
   293  // handleEvent handles the events on cacerts related files.
   294  // If create/write(modified) event occurs, then it verifies that
   295  // newly introduced cacerts are intermediate CA which is generated
   296  // from cuurent root-cert.pem. Then it updates and keycertbundle
   297  // and generates new dns certs.
   298  func handleEvent(s *Server) {
   299  	log.Info("Update Istiod cacerts")
   300  
   301  	var newCABundle []byte
   302  	var err error
   303  
   304  	currentCABundle := s.CA.GetCAKeyCertBundle().GetRootCertPem()
   305  
   306  	fileBundle, err := detectSigningCABundle()
   307  	if err != nil {
   308  		log.Errorf("unable to determine signing file format %v", err)
   309  		return
   310  	}
   311  	newCABundle, err = os.ReadFile(fileBundle.RootCertFile)
   312  	if err != nil {
   313  		log.Errorf("failed reading root-cert.pem: %v", err)
   314  		return
   315  	}
   316  
   317  	// Only updating intermediate CA is supported now
   318  	if !bytes.Equal(currentCABundle, newCABundle) {
   319  		if !features.MultiRootMesh {
   320  			log.Warn("Multi root is disabled, updating new ROOT-CA not supported")
   321  			return
   322  		}
   323  
   324  		// in order to support root ca rotation, or we are removing the old ca,
   325  		// we need to make the new CA bundle contain both old and new CA certs
   326  		if bytes.Contains(currentCABundle, newCABundle) ||
   327  			bytes.Contains(newCABundle, currentCABundle) {
   328  			log.Info("Updating new ROOT-CA")
   329  		} else {
   330  			log.Warn("Updating new ROOT-CA not supported")
   331  			return
   332  		}
   333  	}
   334  
   335  	err = s.CA.GetCAKeyCertBundle().UpdateVerifiedKeyCertBundleFromFile(
   336  		fileBundle.SigningCertFile,
   337  		fileBundle.SigningKeyFile,
   338  		fileBundle.CertChainFiles,
   339  		fileBundle.RootCertFile)
   340  	if err != nil {
   341  		log.Errorf("Failed to update new Plug-in CA certs: %v", err)
   342  		return
   343  	}
   344  
   345  	err = s.updateRootCertAndGenKeyCert()
   346  	if err != nil {
   347  		log.Errorf("Failed generating plugged-in istiod key cert: %v", err)
   348  		return
   349  	}
   350  
   351  	log.Info("Istiod has detected the newly added intermediate CA and updated its key and certs accordingly")
   352  }
   353  
   354  // handleCACertsFileWatch handles the events on cacerts files
   355  func (s *Server) handleCACertsFileWatch() {
   356  	var timerC <-chan time.Time
   357  	for {
   358  		select {
   359  		case <-timerC:
   360  			timerC = nil
   361  			handleEvent(s)
   362  
   363  		case event, ok := <-s.cacertsWatcher.Events:
   364  			if !ok {
   365  				log.Debug("plugin cacerts watch stopped")
   366  				return
   367  			}
   368  			if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) {
   369  				if timerC == nil {
   370  					timerC = time.After(100 * time.Millisecond)
   371  				}
   372  			}
   373  
   374  		case err := <-s.cacertsWatcher.Errors:
   375  			if err != nil {
   376  				log.Errorf("failed to catch events on cacerts file: %v", err)
   377  				return
   378  			}
   379  
   380  		case <-s.internalStop:
   381  			return
   382  		}
   383  	}
   384  }
   385  
   386  func (s *Server) addCACertsFileWatcher(dir string) error {
   387  	err := s.cacertsWatcher.Add(dir)
   388  	if err != nil {
   389  		log.Infof("failed to add cacerts file watcher for %s: %v", dir, err)
   390  		return err
   391  	}
   392  
   393  	log.Infof("Added cacerts files watcher at %v", dir)
   394  
   395  	return nil
   396  }
   397  
   398  // initCACertsWatcher initializes the cacerts (/etc/cacerts) directory.
   399  // In particular it monitors 'ca-key.pem', 'ca-cert.pem', 'root-cert.pem'
   400  // and 'cert-chain.pem'.
   401  func (s *Server) initCACertsWatcher() {
   402  	var err error
   403  
   404  	s.cacertsWatcher, err = fsnotify.NewWatcher()
   405  	if err != nil {
   406  		log.Warnf("failed to add CAcerts watcher: %v", err)
   407  		return
   408  	}
   409  
   410  	err = s.addCACertsFileWatcher(LocalCertDir.Get())
   411  	if err != nil {
   412  		log.Warnf("failed to add CAcerts file watcher: %v", err)
   413  		return
   414  	}
   415  
   416  	go s.handleCACertsFileWatch()
   417  }
   418  
   419  // createIstioCA initializes the Istio CA signing functionality.
   420  // - for 'plugged in', uses ./etc/cacert directory, mounted from 'cacerts' secret in k8s.
   421  //
   422  //	Inside, the key/cert are 'ca-key.pem' and 'ca-cert.pem'. The root cert signing the intermediate is root-cert.pem,
   423  //	which may contain multiple roots. A 'cert-chain.pem' file has the full cert chain.
   424  func (s *Server) createIstioCA(opts *caOptions) (*ca.IstioCA, error) {
   425  	var caOpts *ca.IstioCAOptions
   426  	var detectedSigningCABundle bool
   427  	var istioGenerated bool
   428  	var err error
   429  
   430  	fileBundle, err := detectSigningCABundle()
   431  	if err != nil {
   432  		return nil, fmt.Errorf("unable to determine signing file format %v", err)
   433  	}
   434  	if _, err := os.Stat(fileBundle.SigningKeyFile); err == nil {
   435  		detectedSigningCABundle = true
   436  		if _, err := os.Stat(path.Join(LocalCertDir.Get(), ca.IstioGenerated)); err == nil {
   437  			istioGenerated = true
   438  		}
   439  	}
   440  
   441  	if !detectedSigningCABundle || (features.UseCacertsForSelfSignedCA && istioGenerated) {
   442  		if features.UseCacertsForSelfSignedCA && istioGenerated {
   443  			log.Infof("IstioGenerated %s secret found, use it as the CA certificate", ca.CACertsSecret)
   444  
   445  			// TODO(jaellio): Currently, when the USE_CACERTS_FOR_SELF_SIGNED_CA flag is true istiod
   446  			// handles loading and updating the "cacerts" secret with the "istio-generated" key the
   447  			// same way it handles the "istio-ca-secret" secret. Isitod utilizes a secret watch instead
   448  			// of file watch to check for secret updates. This may change in the future, and istiod
   449  			// will watch the file mount instead.
   450  		}
   451  
   452  		// Either the secret is not mounted because it is named `istio-ca-secret`,
   453  		// or it is `cacerts` secret mounted with "istio-generated" key set.
   454  		caOpts, err = s.createSelfSignedCACertificateOptions(&fileBundle, opts)
   455  		if err != nil {
   456  			return nil, err
   457  		}
   458  		caOpts.OnRootCertUpdate = s.updateRootCertAndGenKeyCert
   459  	} else {
   460  		// The secret is mounted and the "istio-generated" key is not used.
   461  		log.Info("Use local CA certificate")
   462  
   463  		caOpts, err = ca.NewPluggedCertIstioCAOptions(fileBundle, workloadCertTTL.Get(), maxWorkloadCertTTL.Get(), caRSAKeySize.Get())
   464  		if err != nil {
   465  			return nil, fmt.Errorf("failed to create an istiod CA: %v", err)
   466  		}
   467  
   468  		s.initCACertsWatcher()
   469  	}
   470  	istioCA, err := ca.NewIstioCA(caOpts)
   471  	if err != nil {
   472  		return nil, fmt.Errorf("failed to create an istiod CA: %v", err)
   473  	}
   474  
   475  	// Start root cert rotator in a separate goroutine.
   476  	istioCA.Run(s.internalStop)
   477  	return istioCA, nil
   478  }
   479  
   480  func (s *Server) createSelfSignedCACertificateOptions(fileBundle *ca.SigningCAFileBundle, opts *caOptions) (*ca.IstioCAOptions, error) {
   481  	var caOpts *ca.IstioCAOptions
   482  	var err error
   483  	if s.kubeClient != nil {
   484  		log.Info("Use self-signed certificate as the CA certificate")
   485  
   486  		// Abort after 20 minutes.
   487  		ctx, cancel := context.WithTimeout(context.Background(), time.Minute*20)
   488  		defer cancel()
   489  		// rootCertFile will be added to "ca-cert.pem".
   490  		// readSigningCertOnly set to false - it doesn't seem to be used in Citadel, nor do we have a way
   491  		// to set it only for one job.
   492  		caOpts, err = ca.NewSelfSignedIstioCAOptions(ctx,
   493  			selfSignedRootCertGracePeriodPercentile.Get(), SelfSignedCACertTTL.Get(),
   494  			selfSignedRootCertCheckInterval.Get(), workloadCertTTL.Get(),
   495  			maxWorkloadCertTTL.Get(), opts.TrustDomain, features.UseCacertsForSelfSignedCA, true,
   496  			opts.Namespace, s.kubeClient.Kube().CoreV1(), fileBundle.RootCertFile,
   497  			enableJitterForRootCertRotator.Get(), caRSAKeySize.Get())
   498  	} else {
   499  		log.Warnf(
   500  			"Use local self-signed CA certificate for testing. Will use in-memory root CA, no K8S access and no ca key file %s",
   501  			fileBundle.SigningKeyFile)
   502  
   503  		caOpts, err = ca.NewSelfSignedDebugIstioCAOptions(fileBundle.RootCertFile, SelfSignedCACertTTL.Get(),
   504  			workloadCertTTL.Get(), maxWorkloadCertTTL.Get(), opts.TrustDomain, caRSAKeySize.Get())
   505  	}
   506  	if err != nil {
   507  		return nil, fmt.Errorf("failed to create a self-signed istiod CA: %v", err)
   508  	}
   509  
   510  	return caOpts, nil
   511  }
   512  
   513  // createIstioRA initializes the Istio RA signing functionality.
   514  // the caOptions defines the external provider
   515  // ca cert can come from three sources, order matters:
   516  // 1. Define ca cert via kubernetes secret and mount the secret through `external-ca-cert` volume
   517  // 2. Use kubernetes ca cert `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt` if signer is
   518  //
   519  //	kubernetes built-in `kubernetes.io/legacy-unknown" signer
   520  //
   521  // 3. Extract from the cert-chain signed by other CSR signer.
   522  func (s *Server) createIstioRA(opts *caOptions) (ra.RegistrationAuthority, error) {
   523  	caCertFile := path.Join(ra.DefaultExtCACertDir, constants.CACertNamespaceConfigMapDataName)
   524  	certSignerDomain := opts.CertSignerDomain
   525  	_, err := os.Stat(caCertFile)
   526  	if err != nil {
   527  		if !os.IsNotExist(err) {
   528  			return nil, fmt.Errorf("failed to get file info: %v", err)
   529  		}
   530  
   531  		// File does not exist.
   532  		if certSignerDomain == "" {
   533  			log.Infof("CA cert file %q not found, using %q.", caCertFile, defaultCACertPath)
   534  			caCertFile = defaultCACertPath
   535  		} else {
   536  			log.Infof("CA cert file %q not found - ignoring.", caCertFile)
   537  			caCertFile = ""
   538  		}
   539  	}
   540  
   541  	if s.kubeClient == nil {
   542  		return nil, fmt.Errorf("kubeClient is nil")
   543  	}
   544  	raOpts := &ra.IstioRAOptions{
   545  		ExternalCAType:   opts.ExternalCAType,
   546  		DefaultCertTTL:   workloadCertTTL.Get(),
   547  		MaxCertTTL:       maxWorkloadCertTTL.Get(),
   548  		CaSigner:         opts.ExternalCASigner,
   549  		CaCertFile:       caCertFile,
   550  		VerifyAppendCA:   true,
   551  		K8sClient:        s.kubeClient.Kube(),
   552  		TrustDomain:      opts.TrustDomain,
   553  		CertSignerDomain: opts.CertSignerDomain,
   554  	}
   555  	raServer, err := ra.NewIstioRA(raOpts)
   556  	if err != nil {
   557  		return nil, err
   558  	}
   559  	raServer.SetCACertificatesFromMeshConfig(s.environment.Mesh().CaCertificates)
   560  	s.environment.AddMeshHandler(func() {
   561  		meshConfig := s.environment.Mesh()
   562  		caCertificates := meshConfig.CaCertificates
   563  		s.RA.SetCACertificatesFromMeshConfig(caCertificates)
   564  	})
   565  	return raServer, err
   566  }