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  }