github.com/opendevstack/tailor@v1.3.5-0.20220119161809-cab064e60a67/pkg/openshift/params.go (about) 1 package openshift 2 3 import ( 4 "encoding/base64" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "regexp" 9 "strings" 10 11 "github.com/opendevstack/tailor/pkg/cli" 12 "github.com/opendevstack/tailor/pkg/utils" 13 "golang.org/x/crypto/openpgp" 14 ) 15 16 // DecryptedParams is used to edit/reveal secrets 17 func DecryptedParams(input, privateKey, passphrase string) (string, error) { 18 c, err := newReadConverter(privateKey, passphrase) 19 if err != nil { 20 return "", err 21 } 22 return transformValues(input, []converterFunc{c.decrypt}) 23 } 24 25 // EncodedParams is used to pass params to oc 26 func EncodedParams(input, privateKey, passphrase string) (string, error) { 27 c, err := newReadConverter(privateKey, passphrase) 28 if err != nil { 29 return "", err 30 } 31 return transformValues(input, []converterFunc{c.decrypt, c.encode}) 32 } 33 34 // EncryptedParams is used to save cleartext params to file 35 func EncryptedParams(input, previous, publicKeyDir, privateKey, passphrase string) (string, error) { 36 c, err := newWriteConverter(previous, publicKeyDir, privateKey, passphrase) 37 if err != nil { 38 return "", err 39 } 40 return transformValues(input, []converterFunc{c.encrypt}) 41 } 42 43 type paramConverter struct { 44 PublicEntityList openpgp.EntityList 45 PrivateEntityList openpgp.EntityList 46 PreviousParams map[string]string 47 } 48 49 func (c *paramConverter) encode(key, val string) (string, string, error) { 50 // If the value is already base64-encoded, we pass it through 51 if strings.HasSuffix(key, ".B64") { 52 return strings.TrimSuffix(key, ".B64"), val, nil 53 } 54 return key, base64.StdEncoding.EncodeToString([]byte(val)), nil 55 } 56 57 // Decrypt given string 58 func (c *paramConverter) decrypt(key, val string) (string, string, error) { 59 newVal, err := utils.Decrypt(val, c.PrivateEntityList) 60 return key, newVal, err 61 } 62 63 // Encrypt encrypts given value. If the key was already present previously 64 // and the cleartext value did not change, then the previous encrypted string 65 // is returned. 66 func (c *paramConverter) encrypt(key, val string) (string, string, error) { 67 if c.PreviousParams != nil { 68 if _, exists := c.PreviousParams[key]; exists { 69 previousEncryptedValue := c.PreviousParams[key] 70 key, previousDecryptedValue, err := c.decrypt(key, previousEncryptedValue) 71 if err != nil { 72 // When decrypting fails, we display the error, but continue 73 // as we can still encrypt ... 74 cli.DebugMsg(err.Error()) 75 } 76 if previousDecryptedValue == val { 77 return key, previousEncryptedValue, nil 78 } 79 } 80 } 81 newVal, err := utils.Encrypt(val, c.PublicEntityList) 82 return key, newVal, err 83 } 84 85 type converterFunc func(key, val string) (string, string, error) 86 87 func newReadConverter(privateKey, passphrase string) (*paramConverter, error) { 88 el, err := utils.GetEntityList([]string{privateKey}, passphrase) 89 if err != nil { 90 return nil, err 91 } 92 return ¶mConverter{PrivateEntityList: el}, nil 93 } 94 95 func newWriteConverter(previous, publicKeyDir, privateKey, passphrase string) (*paramConverter, error) { 96 // Read previous params 97 previousParams := map[string]string{} 98 err := extractKeyValuePairs(previous, func(key, val string) error { 99 previousParams[key] = val 100 return nil 101 }, func(line string) {}) 102 if err != nil { 103 return nil, err 104 } 105 106 // Prefer "public-keys" folder over current directory 107 if publicKeyDir == "." { 108 if _, err := os.Stat("public-keys"); err == nil { 109 publicKeyDir = "public-keys" 110 } 111 } 112 113 // Read public keys 114 cli.DebugMsg(fmt.Sprintf("Looking for public keys in '%s'", publicKeyDir)) 115 files, err := ioutil.ReadDir(publicKeyDir) 116 if err != nil { 117 return nil, err 118 } 119 filePattern := ".*\\.key$" 120 re := regexp.MustCompile(filePattern) 121 keyFiles := []string{} 122 for _, file := range files { 123 if strings.HasSuffix(file.Name(), "private.key") { 124 continue 125 } 126 matched := re.MatchString(file.Name()) 127 if !matched { 128 continue 129 } 130 keyFiles = append(keyFiles, publicKeyDir+string(os.PathSeparator)+file.Name()) 131 } 132 if len(keyFiles) == 0 { 133 return nil, fmt.Errorf( 134 "No public key files found in '%s'. Files need to end in '.key'", 135 publicKeyDir, 136 ) 137 } 138 139 publicEntityList, err := utils.GetEntityList(keyFiles, "") 140 if err != nil { 141 return nil, err 142 } 143 144 privateEntityList, err := utils.GetEntityList([]string{privateKey}, passphrase) 145 if err != nil { 146 return nil, err 147 } 148 149 return ¶mConverter{ 150 PublicEntityList: publicEntityList, 151 PrivateEntityList: privateEntityList, 152 PreviousParams: previousParams, 153 }, nil 154 } 155 156 func extractKeyValuePairs(input string, consumer func(key, val string) error, passthrough func(line string)) error { 157 text := strings.TrimSuffix(input, "\n") 158 lines := strings.Split(text, "\n") 159 for _, line := range lines { 160 line = strings.TrimSpace(line) 161 if len(line) == 0 { 162 passthrough(line) 163 continue 164 } 165 if strings.HasPrefix(line, "#") { 166 cli.DebugMsg("Skipping comment:", line) 167 passthrough(line) 168 continue 169 } 170 pair := strings.SplitN(line, "=", 2) 171 key := pair[0] 172 val := "" 173 if len(pair) > 1 { 174 val = pair[1] 175 } 176 if err := consumer(key, val); err != nil { 177 return err 178 } 179 } 180 return nil 181 } 182 183 func transformValues(input string, converters []converterFunc) (string, error) { 184 output := "" 185 err := extractKeyValuePairs(input, func(key, val string) error { 186 var err error 187 for _, converter := range converters { 188 key, val, err = converter(key, val) 189 if err != nil { 190 return err 191 } 192 } 193 output = output + key + "=" + val + "\n" 194 return nil 195 }, func(line string) { 196 output = output + line + "\n" 197 }) 198 if err != nil { 199 return "", err 200 } 201 return output, nil 202 }