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

encrypt plain text passwords on initial install #867

Merged
merged 1 commit into from
Jul 28, 2020
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
3 changes: 3 additions & 0 deletions pkg/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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()
Expand Down
28 changes: 19 additions & 9 deletions pkg/upstream/replicated.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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())
Expand All @@ -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{
Expand Down Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion pkg/upstream/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
56 changes: 48 additions & 8 deletions pkg/upstream/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package upstream

import (
"bytes"
"encoding/base64"
"io/ioutil"
"os"
"path"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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
}