Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: obfuscate passwords by base64 encoding them before writing to config and cache #952

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func GetTransformCommand() *cobra.Command {
transformCmd.Flags().StringVar(&flags.qaCacheOut, qaCacheOutFlag, ".", "Specify cache file output location.")
transformCmd.Flags().StringSliceVarP(&flags.configs, configFlag, "f", []string{}, "Specify config file locations. By default we look for "+common.DefaultConfigFilePath)
transformCmd.Flags().StringSliceVar(&flags.preSets, preSetFlag, []string{}, "Specify preset config to use.")
transformCmd.Flags().BoolVar(&flags.persistPasswords, qaPersistPasswords, false, "Stores passwords too in the config.")
transformCmd.Flags().BoolVar(&flags.persistPasswords, qaPersistPasswords, false, "Store passwords in the config and cache. By default passwords are not persisted.")
transformCmd.Flags().StringArrayVar(&flags.setconfigs, setConfigFlag, []string{}, "Specify config key-value pairs.")
transformCmd.Flags().StringVarP(&flags.customizationsPath, customizationsFlag, "c", "", "Specify directory where customizations are stored. By default we look for "+common.DefaultCustomizationDir)
transformCmd.Flags().StringVarP(&flags.transformerSelector, transformerSelectorFlag, "t", "", "Specify the transformer selector.")
Expand Down
14 changes: 7 additions & 7 deletions qaengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Engine interface {

var (
engines []Engine
writeStores []qatypes.Store
stores []qatypes.Store
defaultEngine = NewDefaultEngine()
)

Expand Down Expand Up @@ -87,7 +87,7 @@ func AddCaches(cacheFiles ...string) {
func SetupWriteCacheFile(writeCachePath string, persistPasswords bool) {
cache := qatypes.NewCache(writeCachePath, persistPasswords)
cache.Write()
writeStores = append(writeStores, cache)
stores = append(stores, cache)
AddCaches(writeCachePath)
}

Expand All @@ -101,7 +101,7 @@ func SetupConfigFile(writeConfigFile string, configStrings, configFiles, presets
configFiles = append(presetPaths, configFiles...)
writeConfig := qatypes.NewConfig(writeConfigFile, configStrings, configFiles, persistPasswords)
if writeConfigFile != "" {
writeStores = append(writeStores, writeConfig)
stores = append(stores, writeConfig)
}
e := &StoreEngine{store: writeConfig}
if err := AddEngineHighestPriority(e); err != nil {
Expand Down Expand Up @@ -161,17 +161,17 @@ func FetchAnswer(prob qatypes.Problem) (qatypes.Problem, error) {
}
}
}
for _, writeStore := range writeStores {
writeStore.AddSolution(prob)
for _, store := range stores {
store.AddSolution(prob)
}
return prob, err
}

// WriteStoresToDisk forces all the stores to write their contents out to disk
func WriteStoresToDisk() error {
var err error
for _, writeStore := range writeStores {
cerr := writeStore.Write()
for _, store := range stores {
cerr := store.Write()
if cerr != nil {
if err == nil {
err = cerr
Expand Down
40 changes: 20 additions & 20 deletions types/qaengine/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,33 +60,31 @@ func NewCache(file string, persistPasswords bool) (cache *Cache) {
func (cache *Cache) Load() error {
c := Cache{}
if err := common.ReadMove2KubeYaml(cache.Spec.file, &c); err != nil {
logrus.Errorf("Unable to load the cache file at path %s Error: %q", cache.Spec.file, err)
return err
return fmt.Errorf("failed to load the cache file at path '%s' . Error: %w", cache.Spec.file, err)
}
cache.merge(c)
return nil
}

// Write writes cache to disk
func (cache *Cache) Write() error {
err := common.WriteYaml(cache.Spec.file, cache)
if err != nil {
logrus.Warnf("Unable to write cache : %s", err)
if err := common.WriteYaml(cache.Spec.file, cache); err != nil {
return fmt.Errorf("failed to write to the cache. Error: %w", err)
}
return err
return nil
}

// AddSolution adds a problem to solution cache
func (cache *Cache) AddSolution(p Problem) error {
if !cache.Spec.persistPasswords && p.Type == PasswordSolutionFormType {
err := fmt.Errorf("passwords are not added to the cache")
logrus.Debug(err)
return err
func (cache *Cache) AddSolution(problem Problem) error {
if problem.Type == PasswordSolutionFormType && !cache.Spec.persistPasswords {
return fmt.Errorf("passwords won't be added to the cache")
}
if p.Answer == nil {
err := fmt.Errorf("unresolved problem. Not going to be added to cache")
logrus.Warn(err)
return err
if problem.Answer == nil {
return fmt.Errorf("unresolved problem. Not going to be added to cache")
}
p, err := Serialize(problem)
if err != nil {
return fmt.Errorf("failed to serialize the problem. Error: %w", err)
}
added := false
for i, cp := range cache.Spec.Problems {
Expand All @@ -101,8 +99,7 @@ func (cache *Cache) AddSolution(p Problem) error {
cache.Spec.Problems = append(cache.Spec.Problems, p)
}
if err := cache.Write(); err != nil {
logrus.Errorf("Failed to write to the cache file. Error: %q", err)
return err
return fmt.Errorf("failed to write to the cache file. Error: %w", err)
}
return nil
}
Expand All @@ -115,8 +112,11 @@ func (cache *Cache) GetSolution(p Problem) (Problem, error) {
}
for _, cp := range cache.Spec.Problems {
if (cp.ID == p.ID || cp.matches(p)) && cp.Answer != nil {
p.Answer = cp.Answer
return p, nil
problem, err := Deserialize(cp)
if err != nil {
return cp, fmt.Errorf("failed to deserialize the problem. Error: %w", err)
}
return problem, nil
}
}
return p, fmt.Errorf("the problem %+v was not found in the cache", p)
Expand All @@ -127,7 +127,7 @@ func (cache *Cache) merge(c Cache) {
found := false
for _, op := range cache.Spec.Problems {
if op.matches(p) {
logrus.Warnf("There are two or more answers for %s in cache. Ignoring latter ones.", p.Desc)
logrus.Warnf("There are two or more answers for '%s' in cache. Ignoring latter ones.", p.Desc)
found = true
break
}
Expand Down
59 changes: 39 additions & 20 deletions types/qaengine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,27 @@ func (c *Config) specialGetSolution(p Problem) (Problem, error) {
func (c *Config) GetSolution(p Problem) (Problem, error) {
if strings.Contains(p.ID, common.Special) {
if p.Type != MultiSelectSolutionFormType {
return p, fmt.Errorf("cannot use the %s selector with non multi select problems:%+v", common.Special, p)
return p, fmt.Errorf("cannot use the '%s' selector with non multi-select problems: %+v", common.Special, p)
}
return c.specialGetSolution(p)
p, err := c.specialGetSolution(p)
if err != nil {
return p, fmt.Errorf("failed to get the solution for the problem using special logic. Error: %w", err)
}
problem, err := Deserialize(p)
if err != nil {
return problem, fmt.Errorf("failed to deserialize the problem. Error: %w", err)
}
return problem, nil
}
p, err := c.normalGetSolution(p)
if err != nil {
return p, fmt.Errorf("failed to get the solution for the problem using normal logic. Error: %w", err)
}
return c.normalGetSolution(p)
problem, err := Deserialize(p)
if err != nil {
return problem, fmt.Errorf("failed to deserialize the problem. Error: %w", err)
}
return problem, nil
}

// Write writes the config to disk
Expand All @@ -189,24 +205,27 @@ func (c *Config) Write() error {
}

// AddSolution adds a problem to the config
func (c *Config) AddSolution(p Problem) error {
logrus.Debugf("Config.AddSolution the problem is:\n%+v", p)
if p.Answer == nil {
err := fmt.Errorf("unresolved problem. Not going to be added to config")
logrus.Warn(err)
return err
func (c *Config) AddSolution(problem Problem) error {
logrus.Trace("Config.AddSolution start")
defer logrus.Trace("Config.AddSolution end")
logrus.Debugf("Config.AddSolution the problem is: %+v", problem)
if problem.Answer == nil {
return fmt.Errorf("unresolved problem. Not going to be added to config")
}
p, err := Serialize(problem)
if err != nil {
return fmt.Errorf("failed to serialize the problem. Error: %w", err)
}
if p.Type != MultiSelectSolutionFormType {
set(p.ID, p.Answer, c.yamlMap)
if c.persistPasswords || p.Type != PasswordSolutionFormType {
if p.Type != PasswordSolutionFormType || c.persistPasswords {
set(p.ID, p.Answer, c.writeYamlMap)
err := c.Write()
if err != nil {
logrus.Errorf("Failed to write to the config file. Error: %q", err)
if err := c.Write(); err != nil {
return fmt.Errorf("failed to write to the config file. Error: %w", err)
}
return err
return nil
} else if p.Type == PasswordSolutionFormType {
logrus.Debug("passwords are not be added to the config")
logrus.Debug("passwords won't be added to the config")
}
return nil
}
Expand Down Expand Up @@ -244,11 +263,10 @@ func (c *Config) AddSolution(p Problem) error {
set(newKey, isOptionSelected, c.yamlMap)
set(newKey, isOptionSelected, c.writeYamlMap)
}
err := c.Write()
if err != nil {
logrus.Errorf("Failed to write to the config file. Error: %q", err)
if err := c.Write(); err != nil {
return fmt.Errorf("failed to write to the config file. Error: %w", err)
}
return err
return nil
}

// Get returns the value at the position given by the key in the config
Expand Down Expand Up @@ -282,7 +300,8 @@ func getPrinterAndEvaluator(buffer *bytes.Buffer) (yqlib.Printer, yqlib.StreamEv
// GenerateYAMLFromExpression generates yaml string from yq syntax expression
// Example: The expression .foo.bar="abc" gives:
// foo:
// bar: abc
//
// bar: abc
func GenerateYAMLFromExpression(expr string) (string, error) {
logrus.Debugf("GenerateYAMLFromExpression parsing the string [%s]", expr)
logging.SetBackend(new(nullLogBackend))
Expand Down
52 changes: 51 additions & 1 deletion types/qaengine/qaengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ Package qaengine contains the types used for the question answering part of the
*/
package qaengine

import "fmt"
import (
"encoding/base64"
"fmt"

"github.com/sirupsen/logrus"
)

// Store helps store answers
type Store interface {
Expand All @@ -35,6 +40,51 @@ type ValidationError struct {
Reason string
}

// Error returns the error message as a string.
func (v *ValidationError) Error() string {
return fmt.Sprintf("validation error: %s", v.Reason)
}

// Serialize transforms certain fields of the problem before it gets written to the store.
func Serialize(p Problem) (Problem, error) {
logrus.Tracef("Serialize start p: %+v", p)
defer logrus.Trace("Serialize end")
switch p.Type {
case PasswordSolutionFormType:
if p.Answer == nil {
return p, fmt.Errorf("the answer for the password type problem is nil")
}
answer, ok := p.Answer.(string)
if !ok {
return p, fmt.Errorf("expected the answer for the password type problem to be a string. Actual type %T and value %+v", p.Answer, p.Answer)
}
p.Answer = base64.StdEncoding.EncodeToString([]byte(answer))
return p, nil
default:
return p, nil
}
}

// Deserialize transforms certain fields of the problem after it gets read from the store.
func Deserialize(p Problem) (Problem, error) {
logrus.Tracef("Deserialize start p: %+v", p)
defer logrus.Trace("Deserialize end")
switch p.Type {
case PasswordSolutionFormType:
if p.Answer == nil {
return p, fmt.Errorf("the answer for the password type problem is nil")
}
answer, ok := p.Answer.(string)
if !ok {
return p, fmt.Errorf("expected the answer for the password type problem to be a string. Actual type %T and value %+v", p.Answer, p.Answer)
}
ansBytes, err := base64.StdEncoding.DecodeString(answer)
if err != nil {
return p, fmt.Errorf("failed to base64 decode the answer for the password type problem. Error: %w", err)
}
p.Answer = string(ansBytes)
return p, nil
default:
return p, nil
}
}