istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/server/ca/server.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 ca
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"google.golang.org/grpc"
    22  	"google.golang.org/grpc/codes"
    23  	"google.golang.org/grpc/status"
    24  	"k8s.io/apimachinery/pkg/types"
    25  
    26  	pb "istio.io/api/security/v1alpha1"
    27  	"istio.io/istio/pilot/pkg/features"
    28  	"istio.io/istio/pkg/kube/multicluster"
    29  	"istio.io/istio/pkg/log"
    30  	"istio.io/istio/pkg/security"
    31  	"istio.io/istio/security/pkg/pki/ca"
    32  	caerror "istio.io/istio/security/pkg/pki/error"
    33  	"istio.io/istio/security/pkg/pki/util"
    34  )
    35  
    36  var serverCaLog = log.RegisterScope("serverca", "Citadel server log")
    37  
    38  // CertificateAuthority contains methods to be supported by a CA.
    39  type CertificateAuthority interface {
    40  	// Sign generates a certificate for a workload or CA, from the given CSR and cert opts.
    41  	Sign(csrPEM []byte, opts ca.CertOpts) ([]byte, error)
    42  	// SignWithCertChain is similar to Sign but returns the leaf cert and the entire cert chain.
    43  	SignWithCertChain(csrPEM []byte, opts ca.CertOpts) ([]string, error)
    44  	// GetCAKeyCertBundle returns the KeyCertBundle used by CA.
    45  	GetCAKeyCertBundle() *util.KeyCertBundle
    46  }
    47  
    48  // Server implements IstioCAService and IstioCertificateService and provides the services on the
    49  // specified port.
    50  type Server struct {
    51  	pb.UnimplementedIstioCertificateServiceServer
    52  	monitoring     monitoringMetrics
    53  	Authenticators []security.Authenticator
    54  	ca             CertificateAuthority
    55  	serverCertTTL  time.Duration
    56  
    57  	nodeAuthorizer *MulticlusterNodeAuthorizor
    58  }
    59  
    60  type SaNode struct {
    61  	ServiceAccount types.NamespacedName
    62  	Node           string
    63  }
    64  
    65  func (s SaNode) String() string {
    66  	return s.Node + "/" + s.ServiceAccount.String()
    67  }
    68  
    69  // CreateCertificate handles an incoming certificate signing request (CSR). It does
    70  // authentication and authorization. Upon validated, signs a certificate that:
    71  // the SAN is the identity of the caller in authentication result.
    72  // the subject public key is the public key in the CSR.
    73  // the validity duration is the ValidityDuration in request, or default value if the given duration is invalid.
    74  // it is signed by the CA signing key.
    75  func (s *Server) CreateCertificate(ctx context.Context, request *pb.IstioCertificateRequest) (
    76  	*pb.IstioCertificateResponse, error,
    77  ) {
    78  	s.monitoring.CSR.Increment()
    79  	caller, err := security.Authenticate(ctx, s.Authenticators)
    80  	if caller == nil || err != nil {
    81  		s.monitoring.AuthnError.Increment()
    82  		return nil, status.Error(codes.Unauthenticated, "request authenticate failure")
    83  	}
    84  
    85  	serverCaLog := serverCaLog.WithLabels("client", security.GetConnectionAddress(ctx))
    86  	// By default, we will use the callers identity for the certificate
    87  	sans := caller.Identities
    88  	crMetadata := request.Metadata.GetFields()
    89  	impersonatedIdentity := crMetadata[security.ImpersonatedIdentity].GetStringValue()
    90  	if impersonatedIdentity != "" {
    91  		serverCaLog.Debugf("impersonated identity: %s", impersonatedIdentity)
    92  		// If there is an impersonated identity, we will override to use that identity (only single value
    93  		// supported), if the real caller is authorized.
    94  		if s.nodeAuthorizer == nil {
    95  			s.monitoring.AuthnError.Increment()
    96  			// Return an opaque error (for security purposes) but log the full reason
    97  			serverCaLog.Warnf("impersonation not allowed, as node authorizer is not configured")
    98  			return nil, status.Error(codes.Unauthenticated, "request impersonation authentication failure")
    99  
   100  		}
   101  		if err := s.nodeAuthorizer.authenticateImpersonation(ctx, caller.KubernetesInfo, impersonatedIdentity); err != nil {
   102  			s.monitoring.AuthnError.Increment()
   103  			// Return an opaque error (for security purposes) but log the full reason
   104  			serverCaLog.Warnf("impersonation failed for identity %s, error: %v", impersonatedIdentity, err)
   105  			return nil, status.Error(codes.Unauthenticated, "request impersonation authentication failure")
   106  		}
   107  		// Node is authorized to impersonate; overwrite the SAN to the impersonated identity.
   108  		sans = []string{impersonatedIdentity}
   109  	}
   110  	serverCaLog.Debugf("generating a certificate, sans: %v, requested ttl: %s", sans, time.Duration(request.ValidityDuration*int64(time.Second)))
   111  	certSigner := crMetadata[security.CertSigner].GetStringValue()
   112  	_, _, certChainBytes, rootCertBytes := s.ca.GetCAKeyCertBundle().GetAll()
   113  	certOpts := ca.CertOpts{
   114  		SubjectIDs: sans,
   115  		TTL:        time.Duration(request.ValidityDuration) * time.Second,
   116  		ForCA:      false,
   117  		CertSigner: certSigner,
   118  	}
   119  	var signErr error
   120  	var cert []byte
   121  	var respCertChain []string
   122  	if certSigner == "" {
   123  		cert, signErr = s.ca.Sign([]byte(request.Csr), certOpts)
   124  	} else {
   125  		serverCaLog.Debugf("signing CSR with cert chain")
   126  		respCertChain, signErr = s.ca.SignWithCertChain([]byte(request.Csr), certOpts)
   127  	}
   128  	if signErr != nil {
   129  		serverCaLog.Errorf("CSR signing error: %v", signErr.Error())
   130  		s.monitoring.GetCertSignError(signErr.(*caerror.Error).ErrorType()).Increment()
   131  		return nil, status.Errorf(signErr.(*caerror.Error).HTTPErrorCode(), "CSR signing error (%v)", signErr.(*caerror.Error))
   132  	}
   133  	if certSigner == "" {
   134  		respCertChain = []string{string(cert)}
   135  		if len(certChainBytes) != 0 {
   136  			respCertChain = append(respCertChain, string(certChainBytes))
   137  			serverCaLog.Debugf("Append cert chain to response, %s", string(certChainBytes))
   138  		}
   139  	}
   140  	if len(rootCertBytes) != 0 {
   141  		respCertChain = append(respCertChain, string(rootCertBytes))
   142  	}
   143  	response := &pb.IstioCertificateResponse{
   144  		CertChain: respCertChain,
   145  	}
   146  	s.monitoring.Success.Increment()
   147  	serverCaLog.Debugf("CSR successfully signed, sans %v.", caller.Identities)
   148  	return response, nil
   149  }
   150  
   151  func recordCertsExpiry(keyCertBundle *util.KeyCertBundle) {
   152  	rootCertExpiry, err := keyCertBundle.ExtractRootCertExpiryTimestamp()
   153  	if err != nil {
   154  		serverCaLog.Errorf("failed to extract root cert expiry timestamp (error %v)", err)
   155  	}
   156  	rootCertExpiryTimestamp.Record(rootCertExpiry)
   157  
   158  	if len(keyCertBundle.GetCertChainPem()) == 0 {
   159  		return
   160  	}
   161  
   162  	certChainExpiry, err := keyCertBundle.ExtractCACertExpiryTimestamp()
   163  	if err != nil {
   164  		serverCaLog.Errorf("failed to extract CA cert expiry timestamp (error %v)", err)
   165  	}
   166  	certChainExpiryTimestamp.Record(certChainExpiry)
   167  }
   168  
   169  // Register registers a GRPC server on the specified port.
   170  func (s *Server) Register(grpcServer *grpc.Server) {
   171  	pb.RegisterIstioCertificateServiceServer(grpcServer, s)
   172  }
   173  
   174  // New creates a new instance of `IstioCAServiceServer`
   175  func New(
   176  	ca CertificateAuthority,
   177  	ttl time.Duration,
   178  	authenticators []security.Authenticator,
   179  	controller multicluster.ComponentBuilder,
   180  ) (*Server, error) {
   181  	certBundle := ca.GetCAKeyCertBundle()
   182  	if len(certBundle.GetRootCertPem()) != 0 {
   183  		recordCertsExpiry(certBundle)
   184  	}
   185  
   186  	server := &Server{
   187  		Authenticators: authenticators,
   188  		serverCertTTL:  ttl,
   189  		ca:             ca,
   190  		monitoring:     newMonitoringMetrics(),
   191  	}
   192  
   193  	if len(features.CATrustedNodeAccounts) > 0 {
   194  		// TODO: do we need some way to delayed readiness until this is synced? Probably
   195  		// Worst case is we deny some requests though which are retried
   196  		server.nodeAuthorizer = NewMulticlusterNodeAuthenticator(features.CATrustedNodeAccounts, controller)
   197  	}
   198  	return server, nil
   199  }