istio.io/istio@v0.0.0-20240520182934-d79c90f27776/security/pkg/nodeagent/test/mock/caserver.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 mock
    16  
    17  import (
    18  	"context"
    19  	"encoding/pem"
    20  	"fmt"
    21  	"net"
    22  	"sync"
    23  	"time"
    24  
    25  	"google.golang.org/grpc"
    26  	"google.golang.org/grpc/codes"
    27  	ghc "google.golang.org/grpc/health/grpc_health_v1"
    28  	"google.golang.org/grpc/status"
    29  
    30  	pb "istio.io/api/security/v1alpha1"
    31  	"istio.io/istio/pkg/log"
    32  	"istio.io/istio/pkg/security"
    33  	"istio.io/istio/pkg/spiffe"
    34  	caerror "istio.io/istio/security/pkg/pki/error"
    35  	"istio.io/istio/security/pkg/pki/util"
    36  )
    37  
    38  var caServerLog = log.RegisterScope("ca", "CA service debugging")
    39  
    40  // CAServer is a mock CA server.
    41  type CAServer struct {
    42  	pb.UnimplementedIstioCertificateServiceServer
    43  	URL            string
    44  	GRPCServer     *grpc.Server
    45  	Authenticators []security.Authenticator
    46  
    47  	certPem       []byte
    48  	keyPem        []byte
    49  	KeyCertBundle *util.KeyCertBundle
    50  	certLifetime  time.Duration
    51  
    52  	rejectCSR       bool
    53  	emptyCert       bool
    54  	faultInjectLock *sync.Mutex
    55  }
    56  
    57  func NewCAServerWithKeyCert(port int, key, cert []byte, opts ...grpc.ServerOption) (*CAServer, error) {
    58  	keyCertBundle, err := util.NewVerifiedKeyCertBundleFromPem(cert, key, nil, cert)
    59  	if err != nil {
    60  		caServerLog.Errorf("failed to create CA KeyCertBundle: %+v", err)
    61  		return nil, err
    62  	}
    63  
    64  	server := &CAServer{
    65  		certPem:         cert,
    66  		keyPem:          key,
    67  		certLifetime:    24 * time.Hour,
    68  		KeyCertBundle:   keyCertBundle,
    69  		GRPCServer:      grpc.NewServer(opts...),
    70  		faultInjectLock: &sync.Mutex{},
    71  	}
    72  	// Register CA service at gRPC server.
    73  	pb.RegisterIstioCertificateServiceServer(server.GRPCServer, server)
    74  	ghc.RegisterHealthServer(server.GRPCServer, server)
    75  	return server, server.start(port)
    76  }
    77  
    78  // NewCAServer creates a new CA server that listens on port.
    79  func NewCAServer(port int, opts ...grpc.ServerOption) (*CAServer, error) {
    80  	// Create root cert and private key.
    81  	options := util.CertOptions{
    82  		TTL:          3650 * 24 * time.Hour,
    83  		Org:          spiffe.GetTrustDomain(),
    84  		IsCA:         true,
    85  		IsSelfSigned: true,
    86  		RSAKeySize:   2048,
    87  		IsDualUse:    true,
    88  	}
    89  	cert, key, err := util.GenCertKeyFromOptions(options)
    90  	if err != nil {
    91  		caServerLog.Errorf("cannot create CA cert and private key: %+v", err)
    92  		return nil, err
    93  	}
    94  	return NewCAServerWithKeyCert(port, key, cert, opts...)
    95  }
    96  
    97  func (s *CAServer) start(port int) error {
    98  	listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
    99  	if err != nil {
   100  		caServerLog.Errorf("cannot listen on port %d (error: %v)", port, err)
   101  		return err
   102  	}
   103  
   104  	// If passed in port is 0, get the actual chosen port.
   105  	port = listener.Addr().(*net.TCPAddr).Port
   106  	s.URL = fmt.Sprintf("localhost:%d", port)
   107  	go func() {
   108  		caServerLog.Infof("start CA server on %s", s.URL)
   109  		if err := s.GRPCServer.Serve(listener); err != nil && (err != grpc.ErrServerStopped) {
   110  			caServerLog.Errorf("CA Server failed to serve in %q: %v", s.URL, err)
   111  		}
   112  	}()
   113  	return nil
   114  }
   115  
   116  // RejectCSR specifies whether to send error response to CSR.
   117  func (s *CAServer) RejectCSR(reject bool) {
   118  	s.faultInjectLock.Lock()
   119  	s.rejectCSR = reject
   120  	s.faultInjectLock.Unlock()
   121  	if reject {
   122  		caServerLog.Info("force CA server to return error to CSR")
   123  	}
   124  }
   125  
   126  func (s *CAServer) shouldReject() bool {
   127  	var reject bool
   128  	s.faultInjectLock.Lock()
   129  	reject = s.rejectCSR
   130  	s.faultInjectLock.Unlock()
   131  	return reject
   132  }
   133  
   134  // SendEmptyCert force CA server send empty cert chain.
   135  func (s *CAServer) SendEmptyCert() {
   136  	s.faultInjectLock.Lock()
   137  	s.emptyCert = true
   138  	s.faultInjectLock.Unlock()
   139  	caServerLog.Info("force CA server to send empty cert chain")
   140  }
   141  
   142  func (s *CAServer) sendEmpty() bool {
   143  	var empty bool
   144  	s.faultInjectLock.Lock()
   145  	empty = s.emptyCert
   146  	s.faultInjectLock.Unlock()
   147  	return empty
   148  }
   149  
   150  // CreateCertificate handles CSR.
   151  func (s *CAServer) CreateCertificate(ctx context.Context, request *pb.IstioCertificateRequest) (
   152  	*pb.IstioCertificateResponse, error,
   153  ) {
   154  	caServerLog.Infof("received CSR request")
   155  	if s.shouldReject() {
   156  		caServerLog.Info("force rejecting CSR request")
   157  		return nil, status.Error(codes.Unavailable, "CA server is not available")
   158  	}
   159  	if s.sendEmpty() {
   160  		caServerLog.Info("force sending empty cert chain in CSR response")
   161  		response := &pb.IstioCertificateResponse{
   162  			CertChain: []string{},
   163  		}
   164  		return response, nil
   165  	}
   166  	id := []string{"client-identity"}
   167  	if len(s.Authenticators) > 0 {
   168  		caller, err := security.Authenticate(ctx, s.Authenticators)
   169  		if caller == nil || err != nil {
   170  			return nil, status.Error(codes.Unauthenticated, "request authenticate failure")
   171  		}
   172  		id = caller.Identities
   173  	}
   174  	cert, err := s.sign([]byte(request.Csr), id, time.Duration(request.ValidityDuration)*time.Second, false)
   175  	if err != nil {
   176  		caServerLog.Errorf("failed to sign CSR: %+v", err)
   177  		return nil, status.Errorf(err.(*caerror.Error).HTTPErrorCode(), "CSR signing error: %+v", err.(*caerror.Error))
   178  	}
   179  	respCertChain := []string{string(cert)}
   180  	respCertChain = append(respCertChain, string(s.certPem))
   181  	response := &pb.IstioCertificateResponse{
   182  		CertChain: respCertChain,
   183  	}
   184  	caServerLog.Info("send back CSR success response")
   185  	return response, nil
   186  }
   187  
   188  func (s *CAServer) sign(csrPEM []byte, subjectIDs []string, _ time.Duration, forCA bool) ([]byte, error) {
   189  	csr, err := util.ParsePemEncodedCSR(csrPEM)
   190  	if err != nil {
   191  		caServerLog.Errorf("failed to parse CSR: %+v", err)
   192  		return nil, caerror.NewError(caerror.CSRError, err)
   193  	}
   194  	signingCert, signingKey, _, _ := s.KeyCertBundle.GetAll()
   195  	certBytes, err := util.GenCertFromCSR(csr, signingCert, csr.PublicKey, *signingKey, subjectIDs, s.certLifetime, forCA)
   196  	if err != nil {
   197  		caServerLog.Errorf("failed to generate cert from CSR: %+v", err)
   198  		return nil, caerror.NewError(caerror.CertGenError, err)
   199  	}
   200  	block := &pem.Block{
   201  		Type:  "CERTIFICATE",
   202  		Bytes: certBytes,
   203  	}
   204  	cert := pem.EncodeToMemory(block)
   205  
   206  	return cert, nil
   207  }
   208  
   209  // Check handles health check requests.
   210  func (s *CAServer) Check(ctx context.Context, in *ghc.HealthCheckRequest) (*ghc.HealthCheckResponse, error) {
   211  	return &ghc.HealthCheckResponse{
   212  		Status: ghc.HealthCheckResponse_SERVING,
   213  	}, nil
   214  }
   215  
   216  // Watch handles health check streams.
   217  func (s *CAServer) Watch(_ *ghc.HealthCheckRequest, _ ghc.Health_WatchServer) error {
   218  	return nil
   219  }