github.com/Venafi/vcert/v5@v5.10.2/pkg/playbook/app/installer/pem.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 installer
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  
    23  	"go.uber.org/zap"
    24  
    25  	"github.com/Venafi/vcert/v5/pkg/certificate"
    26  	"github.com/Venafi/vcert/v5/pkg/playbook/app/domain"
    27  	"github.com/Venafi/vcert/v5/pkg/playbook/app/vcertutil"
    28  	"github.com/Venafi/vcert/v5/pkg/playbook/util"
    29  )
    30  
    31  // PEMInstaller represents an installation that will use the PEM format for the certificate bundle
    32  type PEMInstaller struct {
    33  	domain.Installation
    34  }
    35  
    36  // NewPEMInstaller returns a new installer of type PEM with the values defined in inst
    37  func NewPEMInstaller(inst domain.Installation) PEMInstaller {
    38  	return PEMInstaller{inst}
    39  }
    40  
    41  // Check is the method in charge of making the validations to install a new certificate:
    42  // 1. Does the certificate exists? > Install if it doesn't.
    43  // 2. Does the certificate is about to expire? Renew if about to expire.
    44  // Returns true if the certificate needs to be installed.
    45  func (r PEMInstaller) Check(renewBefore string, _ domain.PlaybookRequest) (bool, error) {
    46  	zap.L().Info("checking certificate health", zap.String("format", r.Type.String()), zap.String("location", r.File))
    47  
    48  	// Check certificate bundle file exists
    49  	certExists, err := util.FileExists(r.File)
    50  	if err != nil {
    51  		return false, err
    52  	}
    53  	if !certExists {
    54  		return true, nil
    55  	}
    56  
    57  	// Load Certificate
    58  	cert, err := loadPEMCertificate(r.File)
    59  	if err != nil {
    60  		return false, err
    61  	}
    62  
    63  	// Check certificate expiration
    64  	renew := needRenewal(cert, renewBefore)
    65  
    66  	return renew, nil
    67  }
    68  
    69  // Backup takes the certificate request and backs up the current version prior to overwriting
    70  func (r PEMInstaller) Backup() error {
    71  	zap.L().Debug("backing up certificate", zap.String("location", r.File))
    72  
    73  	// Check certificate file exists
    74  	certExists, err := util.FileExists(r.File)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	// No cert file
    80  	if !certExists {
    81  		zap.L().Info("new certificate location specified, no back up taken")
    82  		return nil
    83  	}
    84  
    85  	resources := []struct {
    86  		oldLocation string
    87  		newLocation string
    88  	}{
    89  		{oldLocation: r.File, newLocation: fmt.Sprintf("%s.bak", r.File)},
    90  		{oldLocation: r.KeyFile, newLocation: fmt.Sprintf("%s.bak", r.KeyFile)},
    91  		{oldLocation: r.ChainFile, newLocation: fmt.Sprintf("%s.bak", r.ChainFile)},
    92  	}
    93  
    94  	for _, resource := range resources {
    95  		fileExists, err := util.FileExists(resource.oldLocation)
    96  		if err != nil {
    97  			return err
    98  		} else if !fileExists {
    99  			zap.L().Info(fmt.Sprintf("file %s does not exist, no backup taken", resource.oldLocation))
   100  			continue
   101  		}
   102  		err = util.CopyFile(resource.oldLocation, resource.newLocation)
   103  		if err != nil {
   104  			return err
   105  		}
   106  		zap.L().Info("certificate resource backed up", zap.String("location", resource.oldLocation),
   107  			zap.String("backupLocation", resource.newLocation))
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  // Install takes the certificate bundle and moves it to the location specified in the installer
   114  func (r PEMInstaller) Install(pcc certificate.PEMCollection) error {
   115  	zap.L().Debug("installing certificate", zap.String("location", r.File))
   116  
   117  	//TODO: should we add support for PEM bundle?
   118  
   119  	preppedPK := pcc.PrivateKey
   120  	var err error
   121  	// Needs to be encrypted again using legacy PEM
   122  	if r.KeyPassword != "" {
   123  		preppedPK, err = vcertutil.EncryptPrivateKeyPKCS1(pcc.PrivateKey, r.KeyPassword)
   124  		if err != nil {
   125  			zap.L().Error("failed to encrypt PrivateKey", zap.Error(err))
   126  			return err
   127  		}
   128  	}
   129  
   130  	resources := []struct {
   131  		path    string
   132  		content []byte
   133  	}{
   134  		{path: r.File, content: []byte(pcc.Certificate)},
   135  		{path: r.KeyFile, content: []byte(preppedPK)},
   136  		{path: r.ChainFile, content: []byte(strings.Join(pcc.Chain, ""))},
   137  	}
   138  
   139  	for _, resource := range resources {
   140  		if len(resource.content) == 0 {
   141  			continue
   142  		}
   143  		err = util.WriteFile(resource.path, resource.content)
   144  		if err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	return nil
   150  }
   151  
   152  // AfterInstallActions runs any instructions declared in the Installer on a terminal.
   153  //
   154  // No validations happen over the content of the AfterAction string, so caution is advised
   155  func (r PEMInstaller) AfterInstallActions() (string, error) {
   156  	zap.L().Debug("running after-install actions", zap.String("location", r.File))
   157  
   158  	result, err := util.ExecuteScript(r.AfterAction)
   159  	return result, err
   160  }
   161  
   162  // InstallValidationActions runs any instructions declared in the Installer on a terminal and expects
   163  // "0" for successful validation and "1" for a validation failure
   164  // No validations happen over the content of the InstallValidation string, so caution is advised
   165  func (r PEMInstaller) InstallValidationActions() (string, error) {
   166  	zap.L().Debug("running install validation actions", zap.String("location", r.File))
   167  
   168  	validationResult, err := util.ExecuteScript(r.InstallValidation)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  
   173  	return validationResult, err
   174  }
   175  
   176  func (r PEMInstaller) installAsBundle() bool {
   177  	if r.KeyFile != "" && r.ChainFile != "" {
   178  		return true
   179  	}
   180  	return false
   181  }