diff --git a/cmd/werf/bundle/export/export.go b/cmd/werf/bundle/export/export.go index da03144d3c..5dad240d8b 100644 --- a/cmd/werf/bundle/export/export.go +++ b/cmd/werf/bundle/export/export.go @@ -11,7 +11,6 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" "github.com/werf/logboek" "github.com/werf/werf/cmd/werf/common" @@ -367,22 +366,14 @@ func runExport(ctx context.Context, imagesToProcess build.ImagesToProcess) error SubchartExtenderFactoryFunc: func() chart.ChartExtender { return chart_extender.NewWerfSubchart() }, } - valueOpts := &values.Options{ + chartVersion := fmt.Sprintf("0.0.0-%d", time.Now().Unix()) + + if _, err := wc.CreateNewBundle(ctx, cmdData.Destination, chartVersion, &values.Options{ ValueFiles: common.GetValues(&commonCmdData), StringValues: common.GetSetString(&commonCmdData), Values: common.GetSet(&commonCmdData), FileValues: common.GetSetFile(&commonCmdData), - } - - chartVersion := fmt.Sprintf("0.0.0-%d", time.Now().Unix()) - - p := getter.All(helm_v3.Settings) - vals, err := valueOpts.MergeValues(p, wc) - if err != nil { - return err - } - - if _, err := wc.CreateNewBundle(ctx, cmdData.Destination, chartVersion, vals); err != nil { + }); err != nil { return fmt.Errorf("unable to create bundle: %w", err) } diff --git a/cmd/werf/bundle/publish/publish.go b/cmd/werf/bundle/publish/publish.go index 243aaba7d9..f9fc1c1368 100644 --- a/cmd/werf/bundle/publish/publish.go +++ b/cmd/werf/bundle/publish/publish.go @@ -13,7 +13,6 @@ import ( "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" "github.com/werf/logboek" "github.com/werf/werf/cmd/werf/common" @@ -23,6 +22,7 @@ import ( "github.com/werf/werf/pkg/deploy/helm/chart_extender" "github.com/werf/werf/pkg/deploy/helm/chart_extender/helpers" "github.com/werf/werf/pkg/deploy/helm/command_helpers" + "github.com/werf/werf/pkg/deploy/secrets_manager" "github.com/werf/werf/pkg/git_repo" "github.com/werf/werf/pkg/git_repo/gitdata" "github.com/werf/werf/pkg/image" @@ -111,8 +111,11 @@ func NewCmd(ctx context.Context) *cobra.Command { common.SetupSetString(&commonCmdData, cmd) common.SetupSetFile(&commonCmdData, cmd) common.SetupValues(&commonCmdData, cmd) + common.SetupSecretValues(&commonCmdData, cmd) + common.SetupIgnoreSecretKey(&commonCmdData, cmd) commonCmdData.SetupDisableDefaultValues(cmd) + commonCmdData.SetupDisableDefaultSecretValues(cmd) commonCmdData.SetupSkipDependenciesRepoRefresh(cmd) common.SetupSaveBuildReport(&commonCmdData, cmd) @@ -334,13 +337,23 @@ func runPublish(ctx context.Context, imagesToProcess build.ImagesToProcess) erro return err } - wc := chart_extender.NewWerfChart(ctx, giterminismManager, nil, chartDir, helm_v3.Settings, helmRegistryClient, chart_extender.WerfChartOptions{ + secretsManager := secrets_manager.NewSecretsManager(secrets_manager.SecretsManagerOptions{ + DisableSecretsDecryption: *commonCmdData.IgnoreSecretKey, + }) + + // FIXME(1.3): compatibility mode with older 1.2 versions, which do not require WERF_SECRET_KEY in the 'werf bundle publish' command + if err := secretsManager.AllowMissedSecretKeyMode(giterminismManager.ProjectDir()); err != nil { + return err + } + + wc := chart_extender.NewWerfChart(ctx, giterminismManager, secretsManager, chartDir, helm_v3.Settings, helmRegistryClient, chart_extender.WerfChartOptions{ BuildChartDependenciesOpts: command_helpers.BuildChartDependenciesOptions{SkipUpdate: *commonCmdData.SkipDependenciesRepoRefresh}, + SecretValueFiles: common.GetSecretValues(&commonCmdData), ExtraAnnotations: userExtraAnnotations, ExtraLabels: userExtraLabels, IgnoreInvalidAnnotationsAndLabels: true, DisableDefaultValues: *commonCmdData.DisableDefaultValues, - DisableDefaultSecretValues: true, + DisableDefaultSecretValues: *commonCmdData.DisableDefaultSecretValues, }) if err := wc.SetEnv(*commonCmdData.Environment); err != nil { @@ -377,13 +390,6 @@ func runPublish(ctx context.Context, imagesToProcess build.ImagesToProcess) erro SubchartExtenderFactoryFunc: func() chart.ChartExtender { return chart_extender.NewWerfSubchart() }, } - valueOpts := &values.Options{ - ValueFiles: *commonCmdData.Values, - StringValues: *commonCmdData.SetString, - Values: *commonCmdData.Set, - FileValues: *commonCmdData.SetFile, - } - sv, err := bundles.BundleTagToChartVersion(ctx, cmdData.Tag, time.Now()) if err != nil { return fmt.Errorf("unable to set chart version from bundle tag %q: %w", cmdData.Tag, err) @@ -393,13 +399,12 @@ func runPublish(ctx context.Context, imagesToProcess build.ImagesToProcess) erro bundleTmpDir := filepath.Join(werf.GetServiceDir(), "tmp", "bundles", uuid.NewV4().String()) defer os.RemoveAll(bundleTmpDir) - p := getter.All(helm_v3.Settings) - vals, err := valueOpts.MergeValues(p, wc) - if err != nil { - return err - } - - bundle, err := wc.CreateNewBundle(ctx, bundleTmpDir, chartVersion, vals) + bundle, err := wc.CreateNewBundle(ctx, bundleTmpDir, chartVersion, &values.Options{ + ValueFiles: *commonCmdData.Values, + StringValues: *commonCmdData.SetString, + Values: *commonCmdData.Set, + FileValues: *commonCmdData.SetFile, + }) if err != nil { return fmt.Errorf("unable to create bundle: %w", err) } diff --git a/cmd/werf/bundle/publish/publich_docs.go b/cmd/werf/bundle/publish/publish_docs.go similarity index 100% rename from cmd/werf/bundle/publish/publich_docs.go rename to cmd/werf/bundle/publish/publish_docs.go diff --git a/pkg/deploy/helm/chart_extender/bundle.go b/pkg/deploy/helm/chart_extender/bundle.go index 3351c09be4..7a1be47802 100644 --- a/pkg/deploy/helm/chart_extender/bundle.go +++ b/pkg/deploy/helm/chart_extender/bundle.go @@ -146,9 +146,9 @@ func (bundle *Bundle) MakeValues(inputVals map[string]interface{}) (map[string]i chartutil.CoalesceTables(vals, bundle.ServiceValues) if debugSecretValues() { - debugPrintValues(bundle.ChartExtenderContext, "secret", bundle.SecretsRuntimeData.DecodedSecretValues) + debugPrintValues(bundle.ChartExtenderContext, "secret", bundle.SecretsRuntimeData.DecryptedSecretValues) } - chartutil.CoalesceTables(vals, bundle.SecretsRuntimeData.DecodedSecretValues) + chartutil.CoalesceTables(vals, bundle.SecretsRuntimeData.DecryptedSecretValues) debugPrintValues(bundle.ChartExtenderContext, "input", inputVals) chartutil.CoalesceTables(vals, inputVals) diff --git a/pkg/deploy/helm/chart_extender/helpers/common_template_funcs.go b/pkg/deploy/helm/chart_extender/helpers/common_template_funcs.go index 2e1be9b9cf..64d08189df 100644 --- a/pkg/deploy/helm/chart_extender/helpers/common_template_funcs.go +++ b/pkg/deploy/helm/chart_extender/helpers/common_template_funcs.go @@ -37,11 +37,11 @@ func SetupWerfSecretFile(secretsRuntimeData *secrets.SecretsRuntimeData, funcMap return "", fmt.Errorf("expected relative secret file path, given path %v", secretRelativePath) } - decodedData, ok := secretsRuntimeData.DecodedSecretFilesData[secretRelativePath] + decodedData, ok := secretsRuntimeData.DecryptedSecretFilesData[secretRelativePath] if !ok { var secretFiles []string - for key := range secretsRuntimeData.DecodedSecretFilesData { + for key := range secretsRuntimeData.DecryptedSecretFilesData { secretFiles = append(secretFiles, key) } diff --git a/pkg/deploy/helm/chart_extender/helpers/secrets/secrets_runtime_data.go b/pkg/deploy/helm/chart_extender/helpers/secrets/secrets_runtime_data.go index 27fb133585..cf85f0a8ac 100644 --- a/pkg/deploy/helm/chart_extender/helpers/secrets/secrets_runtime_data.go +++ b/pkg/deploy/helm/chart_extender/helpers/secrets/secrets_runtime_data.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "helm.sh/helm/v3/pkg/chart" + "sigs.k8s.io/yaml" "github.com/werf/werf/pkg/deploy/secrets_manager" "github.com/werf/werf/pkg/giterminism_manager" @@ -14,14 +15,14 @@ import ( ) type SecretsRuntimeData struct { - DecodedSecretValues map[string]interface{} - DecodedSecretFilesData map[string]string - SecretValuesToMask []string + DecryptedSecretValues map[string]interface{} + DecryptedSecretFilesData map[string]string + SecretValuesToMask []string } func NewSecretsRuntimeData() *SecretsRuntimeData { return &SecretsRuntimeData{ - DecodedSecretFilesData: make(map[string]string), + DecryptedSecretFilesData: make(map[string]string), } } @@ -51,14 +52,12 @@ func (secretsRuntimeData *SecretsRuntimeData) DecodeAndLoadSecrets(ctx context.C if err != nil { return fmt.Errorf("unable to read custom secret values file %q from local filesystem: %w", customSecretValuesFileName, err) } - file.Data = data } else { data, err := opts.GiterminismManager.FileReader().ReadChartFile(ctx, customSecretValuesFileName) if err != nil { return fmt.Errorf("unable to read custom secret values file %q: %w", customSecretValuesFileName, err) } - file.Data = data } @@ -68,7 +67,7 @@ func (secretsRuntimeData *SecretsRuntimeData) DecodeAndLoadSecrets(ctx context.C var encoder *secret.YamlEncoder if len(secretDirFiles)+len(loadedSecretValuesFiles) > 0 { if enc, err := secretsManager.GetYamlEncoder(ctx, secretsWorkingDir); err != nil { - return err + return fmt.Errorf("error getting secrets yaml encoder: %w", err) } else { encoder = enc } @@ -78,8 +77,8 @@ func (secretsRuntimeData *SecretsRuntimeData) DecodeAndLoadSecrets(ctx context.C if data, err := LoadChartSecretDirFilesData(chartDir, secretDirFiles, encoder); err != nil { return fmt.Errorf("error loading secret files data: %w", err) } else { - secretsRuntimeData.DecodedSecretFilesData = data - for _, fileData := range secretsRuntimeData.DecodedSecretFilesData { + secretsRuntimeData.DecryptedSecretFilesData = data + for _, fileData := range secretsRuntimeData.DecryptedSecretFilesData { secretsRuntimeData.SecretValuesToMask = append(secretsRuntimeData.SecretValuesToMask, fileData) } } @@ -89,10 +88,42 @@ func (secretsRuntimeData *SecretsRuntimeData) DecodeAndLoadSecrets(ctx context.C if values, err := LoadChartSecretValueFiles(chartDir, loadedSecretValuesFiles, encoder); err != nil { return fmt.Errorf("error loading secret value files: %w", err) } else { - secretsRuntimeData.DecodedSecretValues = values + secretsRuntimeData.DecryptedSecretValues = values secretsRuntimeData.SecretValuesToMask = append(secretsRuntimeData.SecretValuesToMask, secretvalues.ExtractSecretValuesFromMap(values)...) } } return nil } + +func (secretsRuntimeData *SecretsRuntimeData) GetEncodedSecretValues(ctx context.Context, secretsManager *secrets_manager.SecretsManager, secretsWorkingDir string) (map[string]interface{}, error) { + if len(secretsRuntimeData.DecryptedSecretValues) == 0 { + return nil, nil + } + + // FIXME: secrets encoder should receive interface{} raw data instead of []byte yaml data + + var encoder *secret.YamlEncoder + if enc, err := secretsManager.GetYamlEncoder(ctx, secretsWorkingDir); err != nil { + return nil, fmt.Errorf("error getting secrets yaml encoder: %w", err) + } else { + encoder = enc + } + + decryptedSecretsData, err := yaml.Marshal(secretsRuntimeData.DecryptedSecretValues) + if err != nil { + return nil, fmt.Errorf("unable to marshal decrypted secrets yaml: %w", err) + } + + encryptedSecretsData, err := encoder.EncryptYamlData(decryptedSecretsData) + if err != nil { + return nil, fmt.Errorf("unable to encrypt secrets data: %w", err) + } + + var encryptedData map[string]interface{} + if err := yaml.Unmarshal(encryptedSecretsData, &encryptedData); err != nil { + return nil, fmt.Errorf("unable to unmarshal encrypted secrets data: %w", err) + } + + return encryptedData, nil +} diff --git a/pkg/deploy/helm/chart_extender/werf_chart.go b/pkg/deploy/helm/chart_extender/werf_chart.go index 3e7bf62acf..7eaa84d643 100644 --- a/pkg/deploy/helm/chart_extender/werf_chart.go +++ b/pkg/deploy/helm/chart_extender/werf_chart.go @@ -11,10 +11,13 @@ import ( "text/template" "github.com/mitchellh/copystructure" + helm_v3 "helm.sh/helm/v3/cmd/helm" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" "helm.sh/helm/v3/pkg/postrender" "helm.sh/helm/v3/pkg/registry" "sigs.k8s.io/yaml" @@ -27,6 +30,7 @@ import ( "github.com/werf/werf/pkg/deploy/helm/command_helpers" "github.com/werf/werf/pkg/deploy/secrets_manager" "github.com/werf/werf/pkg/giterminism_manager" + "github.com/werf/werf/pkg/util" ) type WerfChartOptions struct { @@ -65,9 +69,9 @@ func NewWerfChart(ctx context.Context, giterminismManager giterminism_manager.In } type WerfChartRuntimeData struct { - DecodedSecretValues map[string]interface{} - DecodedSecretFilesData map[string]string - SecretValuesToMask []string + DecryptedSecretValues map[string]interface{} + DecryptedSecretFilesData map[string]string + SecretValuesToMask []string } type WerfChart struct { @@ -161,9 +165,9 @@ func (wc *WerfChart) makeValues(inputVals map[string]interface{}, withSecrets bo if withSecrets { if debugSecretValues() { - debugPrintValues(wc.ChartExtenderContext, "secret", wc.SecretsRuntimeData.DecodedSecretValues) + debugPrintValues(wc.ChartExtenderContext, "secret", wc.SecretsRuntimeData.DecryptedSecretValues) } - chartutil.CoalesceTables(vals, wc.SecretsRuntimeData.DecodedSecretValues) + chartutil.CoalesceTables(vals, wc.SecretsRuntimeData.DecryptedSecretValues) } debugPrintValues(wc.ChartExtenderContext, "input", inputVals) @@ -208,6 +212,13 @@ func (wc *WerfChart) MakeBundleValues(chrt *chart.Chart, inputVals map[string]in return valsCopy, nil } +func (wc *WerfChart) MakeBundleSecretValues(ctx context.Context, secretsRuntimeData *secrets.SecretsRuntimeData) (map[string]interface{}, error) { + if debugSecretValues() { + debugPrintValues(wc.ChartExtenderContext, "secret", wc.SecretsRuntimeData.DecryptedSecretValues) + } + return secretsRuntimeData.GetEncodedSecretValues(ctx, wc.SecretsManager, wc.GiterminismManager.ProjectDir()) +} + // SetupTemplateFuncs method for the chart.Extender interface func (wc *WerfChart) SetupTemplateFuncs(t *template.Template, funcMap template.FuncMap) { helpers.SetupWerfSecretFile(wc.SecretsRuntimeData, funcMap) @@ -276,7 +287,7 @@ func (wc *WerfChart) SetEnv(env string) error { * CreateNewBundle creates new Bundle object with werf chart extensions taken into account. * inputVals could contain any custom values, which should be stored in the bundle. */ -func (wc *WerfChart) CreateNewBundle(ctx context.Context, destDir, chartVersion string, inputVals map[string]interface{}) (*Bundle, error) { +func (wc *WerfChart) CreateNewBundle(ctx context.Context, destDir, chartVersion string, vals *values.Options) (*Bundle, error) { chartPath := filepath.Join(wc.GiterminismManager.ProjectDir(), wc.ChartDir) chrt, err := loader.LoadDir(chartPath) if err != nil { @@ -285,14 +296,33 @@ func (wc *WerfChart) CreateNewBundle(ctx context.Context, destDir, chartVersion var valsData []byte { - vals, err := wc.MakeBundleValues(chrt, inputVals) + p := getter.All(helm_v3.Settings) + vals, err := vals.MergeValues(p, wc) + if err != nil { + return nil, fmt.Errorf("unable to merge input values: %w", err) + } + + bundleVals, err := wc.MakeBundleValues(chrt, vals) if err != nil { - return nil, fmt.Errorf("unable to construct bundle input values: %w", err) + return nil, fmt.Errorf("unable to construct bundle values: %w", err) } - valsData, err = yaml.Marshal(vals) + valsData, err = yaml.Marshal(bundleVals) if err != nil { - return nil, fmt.Errorf("unable to prepare values: %w", err) + return nil, fmt.Errorf("unable to marshal bundle values: %w", err) + } + } + + var secretValsData []byte + if wc.SecretsRuntimeData != nil && !wc.SecretsManager.IsMissedSecretKeyModeEnabled() { + vals, err := wc.MakeBundleSecretValues(ctx, wc.SecretsRuntimeData) + if err != nil { + return nil, fmt.Errorf("unable to construct bundle secret values: %w", err) + } + + secretValsData, err = yaml.Marshal(vals) + if err != nil { + return nil, fmt.Errorf("unable to marshal bundle secret values: %w", err) } } @@ -314,6 +344,13 @@ func (wc *WerfChart) CreateNewBundle(ctx context.Context, destDir, chartVersion return nil, fmt.Errorf("unable to write %q: %w", valuesFile, err) } + if secretValsData != nil { + secretValuesFile := filepath.Join(destDir, "secret-values.yaml") + if err := ioutil.WriteFile(secretValuesFile, secretValsData, os.ModePerm); err != nil { + return nil, fmt.Errorf("unable to write %q: %w", secretValuesFile, err) + } + } + if wc.HelmChart.Metadata == nil { panic("unexpected condition") } @@ -350,7 +387,28 @@ func (wc *WerfChart) CreateNewBundle(ctx context.Context, destDir, chartVersion } } + chartDirAbs := filepath.Join(wc.GiterminismManager.ProjectDir(), wc.ChartDir) + + ignoreChartValuesFiles := []string{secrets.DefaultSecretValuesFileName} + + // Do not publish into the bundle no custom values nor custom secret values. + // Final bundle values and secret values will be preconstructed, merged and + // embedded into the bundle using only 2 files: values.yaml and secret-values.yaml. + for _, customValuesPath := range append(wc.SecretValueFiles, vals.ValueFiles...) { + path := util.GetAbsoluteFilepath(customValuesPath) + if util.IsSubpathOfBasePath(chartDirAbs, path) { + ignoreChartValuesFiles = append(ignoreChartValuesFiles, util.GetRelativeToBaseFilepath(chartDirAbs, path)) + } + } + +WritingFiles: for _, f := range wc.HelmChart.Files { + for _, ignoreValuesFile := range ignoreChartValuesFiles { + if f.Name == ignoreValuesFile { + continue WritingFiles + } + } + if err := writeChartFile(ctx, destDir, f.Name, f.Data); err != nil { return nil, fmt.Errorf("error writing miscellaneous chart file: %w", err) } diff --git a/pkg/deploy/helm/chart_extender/werf_chart_stub.go b/pkg/deploy/helm/chart_extender/werf_chart_stub.go index 862c0fd050..e65d22cac7 100644 --- a/pkg/deploy/helm/chart_extender/werf_chart_stub.go +++ b/pkg/deploy/helm/chart_extender/werf_chart_stub.go @@ -108,7 +108,7 @@ func (wc *WerfChartStub) MakeValues(inputVals map[string]interface{}) (map[strin vals := make(map[string]interface{}) chartutil.CoalesceTables(vals, wc.stubServiceValuesOverrides) chartutil.CoalesceTables(vals, wc.stubServiceValues) - chartutil.CoalesceTables(vals, wc.SecretsRuntimeData.DecodedSecretValues) + chartutil.CoalesceTables(vals, wc.SecretsRuntimeData.DecryptedSecretValues) chartutil.CoalesceTables(vals, inputVals) return vals, nil diff --git a/pkg/deploy/secrets_manager/secret_key.go b/pkg/deploy/secrets_manager/secret_key.go index 404ef0615e..74e4a0628e 100644 --- a/pkg/deploy/secrets_manager/secret_key.go +++ b/pkg/deploy/secrets_manager/secret_key.go @@ -76,10 +76,20 @@ func GetRequiredSecretKey(workingDir string) ([]byte, error) { return secretKey, nil } -func NewEncryptionKeyRequiredError(notFoundIn []string) error { +type EncryptionKeyRequiredError struct { + Msg error +} + +func (err *EncryptionKeyRequiredError) Error() string { + return err.Msg.Error() +} + +func NewEncryptionKeyRequiredError(notFoundIn []string) *EncryptionKeyRequiredError { notFoundInFormatted := []string{} for _, el := range notFoundIn { notFoundInFormatted = append(notFoundInFormatted, fmt.Sprintf("%q", el)) } - return fmt.Errorf("required encryption key not found in: %s", strings.Join(notFoundInFormatted, ", ")) + return &EncryptionKeyRequiredError{ + Msg: fmt.Errorf("required encryption key not found in: %s", strings.Join(notFoundInFormatted, ", ")), + } } diff --git a/pkg/deploy/secrets_manager/secret_manager.go b/pkg/deploy/secrets_manager/secret_manager.go index caf28805e3..59b38c5d2f 100644 --- a/pkg/deploy/secrets_manager/secret_manager.go +++ b/pkg/deploy/secrets_manager/secret_manager.go @@ -10,6 +10,8 @@ import ( type SecretsManager struct { DisableSecretsDecryption bool + + missedSecretKeyModeEnabled bool } type SecretsManagerOptions struct { @@ -22,11 +24,31 @@ func NewSecretsManager(opts SecretsManagerOptions) *SecretsManager { } } +func (manager *SecretsManager) IsMissedSecretKeyModeEnabled() bool { + return manager.missedSecretKeyModeEnabled +} + +func (manager *SecretsManager) AllowMissedSecretKeyMode(workingDir string) error { + _, err := GetRequiredSecretKey(workingDir) + if err != nil { + if _, missedKey := err.(*EncryptionKeyRequiredError); missedKey { + manager.missedSecretKeyModeEnabled = true + return nil + } + return fmt.Errorf("unable to load secret key: %w", err) + } + return nil +} + func (manager *SecretsManager) GetYamlEncoder(ctx context.Context, workingDir string) (*secret.YamlEncoder, error) { if manager.DisableSecretsDecryption { logboek.Context(ctx).Default().LogLnDetails("Secrets decryption disabled") return secret.NewYamlEncoder(nil), nil } + if manager.missedSecretKeyModeEnabled { + logboek.Context(ctx).Error().LogLn("Secrets decryption disabled due to missed key (no WERF_SECRET_KEY is set)") + return secret.NewYamlEncoder(nil), nil + } if key, err := GetRequiredSecretKey(workingDir); err != nil { return nil, fmt.Errorf("unable to load secret key: %w", err)