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 }