github.com/Venafi/vcert/v5@v5.10.2/pkg/playbook/app/service/service.go (about) 1 /* 2 * Copyright 2023 Venafi, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package service 18 19 import ( 20 "fmt" 21 "os" 22 "strings" 23 24 "go.uber.org/zap" 25 26 "github.com/Venafi/vcert/v5/pkg/certificate" 27 "github.com/Venafi/vcert/v5/pkg/playbook/app/domain" 28 "github.com/Venafi/vcert/v5/pkg/playbook/app/installer" 29 "github.com/Venafi/vcert/v5/pkg/playbook/app/vcertutil" 30 "github.com/Venafi/vcert/v5/pkg/venafi" 31 ) 32 33 // DefaultRenew represents the duration before certificate expiration in which renewal should be attempted 34 const ( 35 DefaultRenew = "10%" 36 37 envVarThumbprint = "thumbprint" 38 envVarSerial = "serial" 39 envVarBase64 = "base64" 40 ) 41 42 // Execute takes the task and requests the certificate specified, 43 // then it installs it in the locations defined by the installers. 44 // 45 // Config is used to make the connection to the Venafi platform for the certificate request. 46 func Execute(config domain.Config, task domain.CertificateTask) []error { 47 // Check if certificate needs action 48 changed, err := isCertificateChanged(config, task) 49 if err != nil { 50 zap.L().Error("error checking certificate in task", zap.String("task", task.Name), zap.Error(err)) 51 return []error{err} 52 } 53 54 // Config has not changed. Do nothing 55 if !changed { 56 zap.L().Info("certificate in good health. No actions needed", 57 zap.String("certificate", task.Request.Subject.CommonName)) 58 return nil 59 } 60 zap.L().Info("certificate needs action", zap.String("certificate", task.Request.Subject.CommonName)) 61 62 // Ensure there is a keyPassword in the request when origin is service 63 csrOrigin := certificate.ParseCSROrigin(task.Request.CsrOrigin) 64 if csrOrigin == certificate.ServiceGeneratedCSR { 65 zap.L().Info("csr option is 'service'. Generating random password for certificate request") 66 task.Request.KeyPassword = vcertutil.GeneratePassword() 67 } 68 69 // Config changed or certificate needs renewal. Do request 70 pcc, certRequest, err := vcertutil.EnrollCertificate(config, task.Request) 71 if err != nil { 72 return []error{fmt.Errorf("error requesting certificate %s: %w", task.Name, err)} 73 } 74 zap.L().Info("successfully enrolled certificate", zap.String("certificate", task.Request.Subject.CommonName)) 75 76 // Private Key should not be decrypted when csrOrigin is service and Platform is Firefly. 77 // Firefly does not support encryption of private keys 78 decryptPK := true 79 if config.Connection.Platform == venafi.Firefly && csrOrigin == certificate.ServiceGeneratedCSR { 80 decryptPK = false 81 } 82 83 // This function will add the private key to the PCC when csrOrigin is local. 84 // It will also decrypt the Private Key if it is encrypted 85 x509Certificate, prepedPcc, err := installer.CreateX509Cert(pcc, certRequest, decryptPK) 86 if err != nil { 87 e := "error preparing certificate for installation" 88 zap.L().Error(e, zap.Error(err)) 89 return []error{fmt.Errorf("%s: %w", e, err)} 90 } 91 zap.L().Info("successfully prepared certificate for installation") 92 93 // Set certificate to environment variables 94 if task.SetEnvVars != nil { 95 zap.L().Debug("setting environment variables") 96 setEnvVars(task, x509Certificate, prepedPcc) 97 } 98 99 // Install certificate on locations 100 errorList := make([]error, 0) 101 for _, installation := range task.Installations { 102 e := runInstaller(installation, prepedPcc) 103 if e != nil { 104 errorList = append(errorList, e) 105 } 106 } 107 return errorList 108 109 } 110 111 func isCertificateChanged(config domain.Config, task domain.CertificateTask) (bool, error) { 112 //If forceRenew is set, then no need to check the certificate status 113 if config.ForceRenew { 114 zap.L().Info("Flag [force-renew] is set. All certificates will be requested/renewed regardless of status") 115 return true, nil 116 } 117 renewBefore := DefaultRenew 118 if task.RenewBefore != "" { 119 renewBefore = task.RenewBefore 120 } 121 122 changed := false 123 // check if any installs have changed 124 for _, install := range task.Installations { 125 isChanged, err := installer.GetInstaller(install).Check(renewBefore, task.Request) 126 if err != nil { 127 return false, fmt.Errorf("error checking for certificate %s: %w", task.Name, err) 128 } 129 if isChanged { 130 changed = true 131 } 132 } 133 134 return changed, nil 135 } 136 137 func runInstaller(installation domain.Installation, prepedPcc *certificate.PEMCollection) error { 138 location := getInstallationLocationString(installation) 139 140 instlr := installer.GetInstaller(installation) 141 zap.L().Info("running Installer", zap.String("installer", installation.Type.String()), 142 zap.String("location", location)) 143 144 var err error 145 146 if installation.BackupFiles { 147 zap.L().Info("backing up certificate for Installer", zap.String("installer", installation.Type.String()), 148 zap.String("location", location)) 149 err = instlr.Backup() 150 if err != nil { 151 e := "error backing up certificate" 152 zap.L().Error(e, zap.String("location", location), zap.Error(err)) 153 return fmt.Errorf("%s at location %s: %w", e, location, err) 154 } 155 } 156 157 err = instlr.Install(*prepedPcc) 158 if err != nil { 159 e := "error installing certificate" 160 zap.L().Error(e, zap.String("location", location), zap.Error(err)) 161 return fmt.Errorf("%s at location %s: %w", e, location, err) 162 } 163 zap.L().Info("successfully installed certificate", zap.String("location", location)) 164 165 if installation.AfterAction == "" { 166 return nil 167 } 168 169 result, err := instlr.AfterInstallActions() 170 if err != nil { 171 e := "error running after-install actions" 172 zap.L().Error(e, zap.String("location", location), zap.Error(err)) 173 return fmt.Errorf("%s at location %s: %w", e, location, err) 174 } else if strings.TrimSpace(result) == "1" { 175 zap.L().Info("after-install actions failed") 176 } 177 zap.L().Info("successfully executed after-install actions") 178 179 if installation.InstallValidation == "" { 180 return nil 181 } 182 183 validationResults, err := instlr.InstallValidationActions() 184 185 if err != nil { 186 e := "error running installation validation actions" 187 zap.L().Error(e, zap.String("location", location), zap.Error(err)) 188 return fmt.Errorf("%s at location %s: %w", e, location, err) 189 } else if strings.TrimSpace(validationResults) == "1" { 190 zap.L().Info("installation validation actions failed") 191 } 192 zap.L().Info("successfully executed installation validation actions") 193 194 return nil 195 } 196 197 func setEnvVars(task domain.CertificateTask, cert *installer.Certificate, prepedPcc *certificate.PEMCollection) { 198 //todo case sensitivity. upper the name 199 for _, envVar := range task.SetEnvVars { 200 varName := "" 201 varValue := "" 202 switch strings.ToLower(envVar) { 203 case envVarThumbprint: 204 varName = fmt.Sprintf("VCERT_%s_THUMBPRINT", strings.ToUpper(task.Name)) 205 varValue = cert.Thumbprint 206 case envVarSerial: 207 varName = fmt.Sprintf("VCERT_%s_SERIAL", strings.ToUpper(task.Name)) 208 varValue = cert.X509cert.SerialNumber.String() 209 case envVarBase64: 210 varName = fmt.Sprintf("VCERT_%s_BASE64", strings.ToUpper(task.Name)) 211 varValue = prepedPcc.Certificate 212 default: 213 zap.L().Error("environment variable not supported", zap.String("envVar", envVar)) 214 continue 215 } 216 217 if varValue == "" { 218 zap.L().Error("environment variable value not found", zap.String("envVar", varName)) 219 continue 220 } 221 222 err := os.Setenv(varName, varValue) 223 if err != nil { 224 zap.L().Error("failed to set environment variable", zap.String("envVar", varName), zap.Error(err)) 225 } 226 } 227 } 228 229 func getInstallationLocationString(installation domain.Installation) string { 230 if installation.Type != domain.FormatCAPI { 231 return installation.File 232 } 233 234 if installation.CAPILocation != "" { 235 return installation.CAPILocation 236 } 237 return installation.Location //nolint:staticcheck 238 }