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 }