From 5d8c69dc1c3e1bf553c4c19e09ace48296a7eaa8 Mon Sep 17 00:00:00 2001 From: Harikrishnan Balagopal Date: Tue, 24 Jan 2023 19:07:32 +0530 Subject: [PATCH] feat: obfuscate passwords by base64 encoding them before writing to config and cache Signed-off-by: Harikrishnan Balagopal --- cmd/transform.go | 2 +- qaengine/engine.go | 14 ++++----- types/qaengine/cache.go | 40 +++++++++++++------------- types/qaengine/config.go | 59 +++++++++++++++++++++++++------------- types/qaengine/qaengine.go | 52 ++++++++++++++++++++++++++++++++- 5 files changed, 118 insertions(+), 49 deletions(-) diff --git a/cmd/transform.go b/cmd/transform.go index 197e7a461..25e648e33 100644 --- a/cmd/transform.go +++ b/cmd/transform.go @@ -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.") diff --git a/qaengine/engine.go b/qaengine/engine.go index 03d1d96e0..71300b8e3 100644 --- a/qaengine/engine.go +++ b/qaengine/engine.go @@ -34,7 +34,7 @@ type Engine interface { var ( engines []Engine - writeStores []qatypes.Store + stores []qatypes.Store defaultEngine = NewDefaultEngine() ) @@ -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) } @@ -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 { @@ -161,8 +161,8 @@ 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 } @@ -170,8 +170,8 @@ func FetchAnswer(prob qatypes.Problem) (qatypes.Problem, error) { // 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 diff --git a/types/qaengine/cache.go b/types/qaengine/cache.go index 85031eeb3..601b65b3c 100644 --- a/types/qaengine/cache.go +++ b/types/qaengine/cache.go @@ -60,8 +60,7 @@ 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 @@ -69,24 +68,23 @@ func (cache *Cache) Load() error { // 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 { @@ -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 } @@ -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) @@ -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 } diff --git a/types/qaengine/config.go b/types/qaengine/config.go index ff951b32f..ded53e47f 100644 --- a/types/qaengine/config.go +++ b/types/qaengine/config.go @@ -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 @@ -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 } @@ -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 @@ -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)) diff --git a/types/qaengine/qaengine.go b/types/qaengine/qaengine.go index 4616881b1..04736540c 100644 --- a/types/qaengine/qaengine.go +++ b/types/qaengine/qaengine.go @@ -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 { @@ -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 + } +}