diff --git a/pkg/cmd/create/create.go b/pkg/cmd/create/create.go index dc4e87e..eaf57fa 100644 --- a/pkg/cmd/create/create.go +++ b/pkg/cmd/create/create.go @@ -43,7 +43,7 @@ type CreateOptions struct { Args []string } -// NewCmdCreate creates a command object for the "create" command +// NewCmdCreate creates a command object for the command func NewCmdCreate() (*cobra.Command, *CreateOptions) { o := &CreateOptions{} diff --git a/pkg/cmd/run/run.go b/pkg/cmd/run/run.go index c525c08..4da551d 100644 --- a/pkg/cmd/run/run.go +++ b/pkg/cmd/run/run.go @@ -330,6 +330,10 @@ func (o *RunOptions) verifyBootSecret(requirements *config.RequirementsConfig) e data := secret.Data[key] if len(data) > 0 { found = true + err := secretmgr.VerifyBootSecrets(string(data)) + if err != nil { + return errors.Wrapf(err, "invalid secrets yaml in kubernetes secret %s in namespace %s. Please run 'jxl boot secrets edit' to populate them", name, ns) + } } } if !found { diff --git a/pkg/cmd/secrets/secrets.go b/pkg/cmd/secrets/secrets.go index a325916..3eae01d 100644 --- a/pkg/cmd/secrets/secrets.go +++ b/pkg/cmd/secrets/secrets.go @@ -20,7 +20,8 @@ func NewCmdSecrets() *cobra.Command { }, } command.AddCommand(common.SplitCommand(NewCmdEdit())) - command.AddCommand(common.SplitCommand(NewCmdImport())) command.AddCommand(common.SplitCommand(NewCmdExport())) + command.AddCommand(common.SplitCommand(NewCmdImport())) + command.AddCommand(common.SplitCommand(NewCmdVerify())) return command } diff --git a/pkg/cmd/secrets/secrets_export.go b/pkg/cmd/secrets/secrets_export.go index 4049b18..166456a 100644 --- a/pkg/cmd/secrets/secrets_export.go +++ b/pkg/cmd/secrets/secrets_export.go @@ -39,7 +39,7 @@ type ExportOptions struct { Console bool } -// NewCmdExport creates a command object for the "create" command +// NewCmdExport creates a command object for the command func NewCmdExport() (*cobra.Command, *ExportOptions) { o := &ExportOptions{} diff --git a/pkg/cmd/secrets/secrets_import.go b/pkg/cmd/secrets/secrets_import.go index 77f5190..ee64f69 100644 --- a/pkg/cmd/secrets/secrets_import.go +++ b/pkg/cmd/secrets/secrets_import.go @@ -32,13 +32,13 @@ type ImportOptions struct { File string } -// NewCmdImport creates a command object for the "create" command +// NewCmdImport creates a command object for the command func NewCmdImport() (*cobra.Command, *ImportOptions) { o := &ImportOptions{} cmd := &cobra.Command{ Use: "import", - Short: "imports the secrets from the local file system", + Short: "Imports the secrets from the local file system", Long: importLong, Example: fmt.Sprintf(importExample, common.BinaryName), Run: func(cmd *cobra.Command, args []string) { diff --git a/pkg/cmd/secrets/secrets_test.go b/pkg/cmd/secrets/secrets_test.go index 8962e25..ad70375 100644 --- a/pkg/cmd/secrets/secrets_test.go +++ b/pkg/cmd/secrets/secrets_test.go @@ -35,6 +35,7 @@ func TestImportExportCommands(t *testing.T) { _, eo := secrets.NewCmdExport() _, io := secrets.NewCmdImport() + _, vo := secrets.NewCmdVerify() ns := "jx" devEnv := kube.CreateDefaultDevEnvironment(ns) @@ -51,6 +52,11 @@ func TestImportExportCommands(t *testing.T) { f := fakejxfactory.NewFakeFactoryWithObjects(nil, jxObjects, ns) eo.Factory = f io.Factory = f + vo.Factory = f + + err = vo.Run() + require.Errorf(t, err, "should have failed to verify secrets before they are imported") + t.Logf("caught expected error when no secrets yet: %s", err.Error()) fileName := tmpFile.Name() @@ -74,4 +80,8 @@ func TestImportExportCommands(t *testing.T) { require.NoError(t, err, "failed to read the exported secrets file %s", fileName) actual := string(data) assert.Equal(t, modifiedYaml, actual, "the re-exported secrets YAML") + + err = vo.Run() + require.NoError(t, err, "should not have failed to to verify secrets after they are imported") + } diff --git a/pkg/cmd/secrets/secrets_verify.go b/pkg/cmd/secrets/secrets_verify.go new file mode 100644 index 0000000..a3e0e3b --- /dev/null +++ b/pkg/cmd/secrets/secrets_verify.go @@ -0,0 +1,57 @@ +package secrets + +import ( + "fmt" + + "github.com/jenkins-x-labs/helmboot/pkg/common" + "github.com/jenkins-x-labs/helmboot/pkg/secretmgr/factory" + "github.com/jenkins-x/jx/pkg/cmd/helper" + "github.com/jenkins-x/jx/pkg/cmd/templates" + "github.com/jenkins-x/jx/pkg/log" + "github.com/spf13/cobra" +) + +var ( + verifyLong = templates.LongDesc(` + Verifies the secrets are populated correctly +`) + + verifyExample = templates.Examples(` + # verifies the secrets are setup correctly + %s secrets verify + `) +) + +// VerifyOptions the options for viewing running PRs +type VerifyOptions struct { + factory.KindResolver + File string +} + +// NewCmdVerify creates a command object for the command +func NewCmdVerify() (*cobra.Command, *VerifyOptions) { + o := &VerifyOptions{} + + cmd := &cobra.Command{ + Use: "verify", + Short: "Verifies the secrets are populated correctly", + Long: verifyLong, + Example: fmt.Sprintf(verifyExample, common.BinaryName), + Run: func(cmd *cobra.Command, args []string) { + err := o.Run() + helper.CheckErr(err) + }, + } + AddKindResolverFlags(cmd, &o.KindResolver) + return cmd, o +} + +// Run implements the command +func (o *VerifyOptions) Run() error { + err := o.VerifySecrets() + if err != nil { + return err + } + log.Logger().Infof("secrets are valid") + return nil +} diff --git a/pkg/cmd/step/step_status.go b/pkg/cmd/step/step_status.go index 7dd55c3..7855590 100644 --- a/pkg/cmd/step/step_status.go +++ b/pkg/cmd/step/step_status.go @@ -43,7 +43,7 @@ type StatusOptions struct { JXFactory jxfactory.Factory } -// NewCmdStatus creates a command object for the "create" command +// NewCmdStatus creates a command object for the command func NewCmdStatus() (*cobra.Command, *StatusOptions) { o := &StatusOptions{} diff --git a/pkg/secretmgr/factory/resolver.go b/pkg/secretmgr/factory/resolver.go index 3f28f13..088bd89 100644 --- a/pkg/secretmgr/factory/resolver.go +++ b/pkg/secretmgr/factory/resolver.go @@ -2,6 +2,7 @@ package factory import ( "fmt" + "strings" "github.com/jenkins-x-labs/helmboot/pkg/reqhelpers" "github.com/jenkins-x-labs/helmboot/pkg/secretmgr" @@ -57,6 +58,30 @@ func (r *KindResolver) CreateSecretManager() (secretmgr.SecretManager, error) { return NewSecretManager(r.Kind, r.Factory, requirements) } +// VerifySecrets verifies that the secrets are valid +func (r *KindResolver) VerifySecrets() error { + secretsYAML := "" + sm, err := r.CreateSecretManager() + if err != nil { + return err + } + + cb := func(currentYAML string) (string, error) { + secretsYAML = currentYAML + return currentYAML, nil + } + err = sm.UpsertSecrets(cb, secretmgr.DefaultSecretsYaml) + if err != nil { + return errors.Wrapf(err, "failed to load Secrets YAML from secret manager %s", sm.String()) + } + + secretsYAML = strings.TrimSpace(secretsYAML) + if secretsYAML == "" { + return errors.Errorf("empty secrets YAML") + } + return secretmgr.VerifyBootSecrets(secretsYAML) +} + func (r *KindResolver) resolveKind(requirements *config.RequirementsConfig) (string, error) { if requirements.Cluster.Provider == cloud.GKE { // lets check if we have a Local secret otherwise default to Google diff --git a/pkg/secretmgr/helpers.go b/pkg/secretmgr/helpers.go new file mode 100644 index 0000000..b8571e6 --- /dev/null +++ b/pkg/secretmgr/helpers.go @@ -0,0 +1,35 @@ +package secretmgr + +import ( + "github.com/jenkins-x/jx/pkg/util" + "github.com/pkg/errors" + "sigs.k8s.io/yaml" +) + +var expectedSecretPaths = []string{ + "secrets.adminUser.username", + "secrets.adminUser.password", + "secrets.hmacToken", + "secrets.pipelineUser.username", + "secrets.pipelineUser.email", + "secrets.pipelineUser.token", +} + +// VerifyBootSecrets verifies the boot secrets +func VerifyBootSecrets(secretsYAML string) error { + data := map[string]interface{}{} + + err := yaml.Unmarshal([]byte(secretsYAML), &data) + if err != nil { + return errors.Wrap(err, "failed to unmarshal secrets YAML") + } + + // simple validation for now, using presence of a string value + for _, path := range expectedSecretPaths { + value := util.GetMapValueAsStringViaPath(data, path) + if value == "" { + return errors.Errorf("missing secret entry: %s", path) + } + } + return nil +}