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 }