github.com/microsoft/moc@v0.17.1/pkg/auth/certrenew.go (about)

     1  // Copyright (c) Microsoft Corporation. All rights reserved.
     2  // Licensed under the Apache v2.0 license.
     3  package auth
     4  
     5  import (
     6  	context "context"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"fmt"
    10  	"log"
    11  	"os"
    12  	"time"
    13  
    14  	"github.com/microsoft/moc/pkg/certs"
    15  	"github.com/microsoft/moc/pkg/errors"
    16  	"github.com/microsoft/moc/pkg/marshal"
    17  	"github.com/microsoft/moc/rpc/cloudagent/security"
    18  	"github.com/microsoft/moc/rpc/common"
    19  	"google.golang.org/grpc"
    20  )
    21  
    22  const (
    23  	CloudAgentServerPort         int     = 55000
    24  	CertificateValidityThreshold float64 = (30.0 / 100.0)
    25  	DefaultServerContextTimeout          = 10 * time.Minute
    26  )
    27  
    28  func getServerEndpoint(serverAddress *string) string {
    29  	return fmt.Sprintf("%s:%d", *serverAddress, CloudAgentServerPort)
    30  }
    31  
    32  // getRenewClient returns the renew client to communicate with the wssdcloudagent
    33  func getRenewClient(serverAddress *string, authorizer Authorizer) (security.IdentityAgentClient, error) {
    34  	var opts []grpc.DialOption
    35  	opts = append(opts, grpc.WithTransportCredentials(authorizer.WithTransportAuthorization()))
    36  	opts = append(opts, grpc.WithPerRPCCredentials(authorizer.WithRPCAuthorization()))
    37  
    38  	conn, err := grpc.Dial(getServerEndpoint(serverAddress), opts...)
    39  	if err != nil {
    40  		log.Fatalf("Unable to get AuthenticationClient. Failed to dial: %v", err)
    41  	}
    42  
    43  	return security.NewIdentityAgentClient(conn), nil
    44  }
    45  
    46  // fromBase64 converts the base64 encoded cert and key to pem encoded
    47  func fromBase64(cert, key string) (pemCert, pemKey []byte, err error) {
    48  	pemCert, err = marshal.FromBase64(cert)
    49  	if err != nil {
    50  		return
    51  	}
    52  	pemKey, err = marshal.FromBase64(key)
    53  	if err != nil {
    54  		return
    55  	}
    56  	return
    57  }
    58  
    59  // renewRequired check the cert is it needs a renewal
    60  // If the certificate is within threshold time the renewal is required.
    61  func renewRequired(x509Cert *x509.Certificate) bool {
    62  	validity := x509Cert.NotAfter.Sub(x509Cert.NotBefore)
    63  
    64  	// Threshold to renew is 30% of validity
    65  	thresholdDuration := time.Duration(float64(validity.Nanoseconds()) * CertificateValidityThreshold)
    66  
    67  	thresholdTime := time.Now().Add(thresholdDuration)
    68  	if x509Cert.NotAfter.After(thresholdTime) {
    69  		return false
    70  	}
    71  	return true
    72  }
    73  
    74  func CertCheck(pemCert []byte) error {
    75  
    76  	x509Cert, err := certs.DecodeCertPEM([]byte(pemCert))
    77  	if err != nil {
    78  		return err
    79  	}
    80  
    81  	if x509Cert == nil {
    82  		return errors.Wrapf(errors.InvalidInput, "Invalid certificate PEM block")
    83  	}
    84  
    85  	if time.Now().After(x509Cert.NotAfter) {
    86  		return errors.Wrapf(errors.Expired, "Certificate has expired")
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  // accessFiletoRenewClient creates a renew client from wssdconfig and server
    93  func accessFiletoRenewClient(server string, wssdConfig *WssdConfig) (security.IdentityAgentClient, error) {
    94  	serverPem, tlsCert, err := AccessFileToTls(*wssdConfig)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	authorizer, err := NewAuthorizerFromInput(tlsCert, serverPem, server)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	return getRenewClient(&server, authorizer)
   105  }
   106  
   107  // renewCertificate renews the cert and key in wssdconfig.
   108  // If it is to early for renewal the same cert and key are returned in the wssdconfig
   109  func renewCertificate(server string, wssdConfig *WssdConfig) (retConfig *WssdConfig, renewed bool, err error) {
   110  	renewed = false
   111  	pemCert, pemKey, err := fromBase64(wssdConfig.ClientCertificate, wssdConfig.ClientKey)
   112  	if err != nil {
   113  		return
   114  	}
   115  
   116  	if err = CertCheck(pemCert); err != nil {
   117  		return
   118  	}
   119  
   120  	x509Cert, err := certs.DecodeCertPEM([]byte(pemCert))
   121  	if err != nil {
   122  		return
   123  	}
   124  
   125  	if !renewRequired(x509Cert) {
   126  		return wssdConfig, renewed, nil
   127  	}
   128  
   129  	tlsCert, err := tls.X509KeyPair(pemCert, pemKey)
   130  	if err != nil {
   131  		return
   132  	}
   133  	newCsr, newKey, err := certs.GenerateCertificateRenewRequest(&tlsCert)
   134  	if err != nil {
   135  		return
   136  	}
   137  
   138  	csr := &security.CertificateSigningRequest{
   139  		Name:           x509Cert.Subject.CommonName,
   140  		Csr:            string(newCsr),
   141  		OldCertificate: wssdConfig.ClientCertificate,
   142  	}
   143  
   144  	renewRequest := &security.IdentityCertificateRequest{
   145  		OperationType: common.ProviderAccessOperation_IdentityCertificate_Renew,
   146  		IdentityName:  wssdConfig.IdentityName,
   147  		CSR:           []*security.CertificateSigningRequest{csr},
   148  	}
   149  	authClient, err := accessFiletoRenewClient(server, wssdConfig)
   150  	if err != nil {
   151  		return
   152  	}
   153  	ctx, cancel := context.WithTimeout(context.Background(), DefaultServerContextTimeout)
   154  	defer cancel()
   155  	response, err := authClient.OperateCertificates(ctx, renewRequest)
   156  	if err != nil {
   157  		return
   158  	}
   159  	if len(response.Certificates) == 0 {
   160  		return nil, false, errors.Wrapf(errors.NotFound, "Missing certificates from renewal response")
   161  	}
   162  	renewed = true
   163  
   164  	newWssdConfig := &WssdConfig{
   165  		CloudCertificate:  wssdConfig.CloudCertificate,
   166  		ClientCertificate: marshal.ToBase64(response.Certificates[0].Certificate),
   167  		ClientKey:         marshal.ToBase64(string(newKey)),
   168  		IdentityName:      wssdConfig.IdentityName,
   169  	}
   170  	return newWssdConfig, renewed, nil
   171  }
   172  
   173  // renewCertificates picks the wssdconfig from the location performs a renewal if close to expiry and stores the same back to the location
   174  func RenewCertificates(server string, wssdConfigLocation string) error {
   175  	accessFile := WssdConfig{}
   176  	err := marshal.FromJSONFile(wssdConfigLocation, &accessFile)
   177  	if err != nil {
   178  		if err != os.ErrNotExist {
   179  			return nil
   180  		}
   181  		return err
   182  	}
   183  	retAccessFile, renewed, err := renewCertificate(server, &accessFile)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	if renewed {
   188  		if err = marshal.ToJSONFile(*retAccessFile, wssdConfigLocation); err != nil {
   189  			return err
   190  		}
   191  	}
   192  
   193  	return nil
   194  }