diff --git a/pkg/pull/pull.go b/pkg/pull/pull.go index 856f063bb3..4715ed2b18 100644 --- a/pkg/pull/pull.go +++ b/pkg/pull/pull.go @@ -131,12 +131,14 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) { fetchOptions.License = localLicense } + encryptConfig := false if pullOptions.ConfigFile != "" { config, err := ParseConfigValuesFromFile(pullOptions.ConfigFile) if err != nil { return "", errors.Wrap(err, "failed to parse config values from file") } fetchOptions.ConfigValues = config + encryptConfig = true } else { fetchOptions.ConfigValues = localConfigValues } @@ -206,6 +208,7 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) { CreateAppDir: pullOptions.CreateAppDir, IncludeAdminConsole: includeAdminConsole, SharedPassword: pullOptions.SharedPassword, + EncryptConfig: encryptConfig, } if err := upstream.WriteUpstream(u, writeUpstreamOptions); err != nil { log.FinishSpinnerWithError() diff --git a/pkg/upstream/replicated.go b/pkg/upstream/replicated.go index 10b004ca42..a1db31e61a 100644 --- a/pkg/upstream/replicated.go +++ b/pkg/upstream/replicated.go @@ -493,8 +493,12 @@ func createConfigValues(applicationName string, config *kotsv1beta1.Config, exis var newValues kotsv1beta1.ConfigValuesSpec if existingConfigValues != nil { for k, v := range existingConfigValues.Spec.Values { + value := v.Value + if value == "" { + value = v.ValuePlaintext + } templateContextValues[k] = template.ItemValue{ - Value: v.Value, + Value: value, Default: v.Default, } } @@ -532,10 +536,11 @@ func createConfigValues(applicationName string, config *kotsv1beta1.Config, exis for _, group := range config.Spec.Groups { for _, item := range group.Items { - var foundValue string + var foundValue, foundValuePlaintext string prevValue, ok := newValues.Values[item.Name] - if ok && prevValue.Value != "" { + if ok { foundValue = prevValue.Value + foundValuePlaintext = prevValue.ValuePlaintext } renderedValue, err := builder.RenderTemplate(item.Name, item.Value.String()) @@ -548,10 +553,11 @@ func createConfigValues(applicationName string, config *kotsv1beta1.Config, exis return nil, errors.Wrap(err, "failed to render config item default") } - if foundValue != "" { + if foundValue != "" || foundValuePlaintext != "" { newValues.Values[item.Name] = kotsv1beta1.ConfigValue{ - Value: foundValue, - Default: renderedDefault, + Value: foundValue, + ValuePlaintext: foundValuePlaintext, + Default: renderedDefault, } } else { newValues.Values[item.Name] = kotsv1beta1.ConfigValue{ @@ -585,17 +591,21 @@ func findConfigValuesInFile(filename string) (*kotsv1beta1.ConfigValues, error) return nil, errors.Wrap(err, "failed to open file") } + return contentToConfigValues(content), nil +} + +func contentToConfigValues(content []byte) *kotsv1beta1.ConfigValues { decode := scheme.Codecs.UniversalDeserializer().Decode obj, gvk, err := decode(content, nil, nil) if err != nil { - return nil, nil + return nil } if gvk.Group == "kots.io" && gvk.Version == "v1beta1" && gvk.Kind == "ConfigValues" { - return obj.(*kotsv1beta1.ConfigValues), nil + return obj.(*kotsv1beta1.ConfigValues) } - return nil, nil + return nil } func findTemplateContextDataInRelease(release *Release) (*kotsv1beta1.Config, *kotsv1beta1.ConfigValues, *kotsv1beta1.License, *kotsv1beta1.Installation, error) { diff --git a/pkg/upstream/types/types.go b/pkg/upstream/types/types.go index cb5663c634..051bd93bc3 100644 --- a/pkg/upstream/types/types.go +++ b/pkg/upstream/types/types.go @@ -36,7 +36,9 @@ type WriteOptions struct { // and should be false when it's an upstream update. // When true, the channel name in Installation yaml will not be changed. PreserveInstallation bool - SharedPassword string + // Set to true on initial installation when an unencrypted config file is provided + EncryptConfig bool + SharedPassword string } func (u *Upstream) GetUpstreamDir(options WriteOptions) string { diff --git a/pkg/upstream/write.go b/pkg/upstream/write.go index 2d95b41212..2c9cc0c334 100644 --- a/pkg/upstream/write.go +++ b/pkg/upstream/write.go @@ -2,6 +2,7 @@ package upstream import ( "bytes" + "encoding/base64" "io/ioutil" "os" "path" @@ -12,6 +13,7 @@ import ( "github.com/replicatedhq/kots/pkg/upstream/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/serializer/json" + serializer "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/client-go/kubernetes/scheme" ) @@ -61,7 +63,13 @@ func WriteUpstream(u *types.Upstream, options types.WriteOptions) error { prevInstallation = prevObj.(*kotsv1beta1.Installation) } - for _, file := range u.Files { + encryptionKey, err := getEncryptionKey(prevInstallation) + if err != nil { + return errors.Wrap(err, "failed to get encryption key") + } + u.EncryptionKey = encryptionKey + + for i, file := range u.Files { fileRenderPath := path.Join(renderDir, file.Path) d, _ := path.Split(fileRenderPath) if _, err := os.Stat(d); os.IsNotExist(err) { @@ -70,18 +78,23 @@ func WriteUpstream(u *types.Upstream, options types.WriteOptions) error { } } + if options.EncryptConfig { + configValues := contentToConfigValues(file.Content) + if configValues != nil { + content, err := encryptConfigValues(configValues, encryptionKey) + if err != nil { + return errors.Wrap(err, "encrypt config values") + } + file.Content = content + u.Files[i] = file + } + } + if err := ioutil.WriteFile(fileRenderPath, file.Content, 0644); err != nil { return errors.Wrap(err, "failed to write upstream file") } } - // Write the installation status (update cursor, etc) - // but preserving the encryption key, if there already is one - encryptionKey, err := getEncryptionKey(prevInstallation) - if err != nil { - return errors.Wrap(err, "failed to get encryption key") - } - var channelName string if prevInstallation != nil && options.PreserveInstallation { channelName = prevInstallation.Spec.ChannelName @@ -105,6 +118,7 @@ func WriteUpstream(u *types.Upstream, options types.WriteOptions) error { EncryptionKey: encryptionKey, }, } + if _, err := os.Stat(path.Join(renderDir, "userdata")); os.IsNotExist(err) { if err := os.MkdirAll(path.Join(renderDir, "userdata"), 0755); err != nil { return errors.Wrap(err, "failed to create userdata dir") @@ -141,3 +155,29 @@ func mustMarshalInstallation(installation *kotsv1beta1.Installation) []byte { return b.Bytes() } + +func encryptConfigValues(configValues *kotsv1beta1.ConfigValues, encryptionKey string) ([]byte, error) { + cipher, err := crypto.AESCipherFromString(encryptionKey) + if err != nil { + return nil, errors.Wrap(err, "failed to craete cipher") + } + for k, v := range configValues.Spec.Values { + if v.ValuePlaintext == "" { + continue + } + + v.Value = base64.StdEncoding.EncodeToString(cipher.Encrypt([]byte(v.ValuePlaintext))) + v.ValuePlaintext = "" + + configValues.Spec.Values[k] = v + } + + s := serializer.NewYAMLSerializer(serializer.DefaultMetaFactory, scheme.Scheme, scheme.Scheme) + + var b bytes.Buffer + if err := s.Encode(configValues, &b); err != nil { + return nil, errors.Wrap(err, "failed to encode config values") + } + + return b.Bytes(), nil +}