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 &paramConverter{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 &paramConverter{
   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  }