Skip to content

Commit 5179c52

Browse files
authored
Merge pull request #350 from stelligent/issue-344
Prompt user before overwriting password
2 parents 594141f + 07eedac commit 5179c52

File tree

8 files changed

+343
-108
lines changed

8 files changed

+343
-108
lines changed

Gopkg.lock

Lines changed: 108 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Gopkg.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,6 @@
7272
name = "gopkg.in/src-d/go-git.v4"
7373
version = "4.7.0"
7474

75-
[[constraint]]
76-
branch = "master"
77-
name = "github.com/howeyc/gopass"
78-
7975
[[override]]
8076
name = "k8s.io/api"
8177
version = "kubernetes-1.11.2"
@@ -98,4 +94,8 @@
9894

9995
[prune]
10096
go-tests = true
101-
unused-packages = true
97+
unused-packages = true
98+
99+
[[constraint]]
100+
branch = "master"
101+
name = "github.com/tcnksm/go-input"

cli/databases.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ import (
44
"errors"
55
"fmt"
66
"os"
7-
"strings"
87

9-
"github.com/howeyc/gopass"
108
"github.com/stelligent/mu/common"
119
"github.com/stelligent/mu/workflows"
1210
"github.com/urfave/cli"
@@ -117,10 +115,9 @@ func newDatabaseSetPasswordCommand(ctx *common.Context) *cli.Command {
117115
cli.ShowCommandHelp(c, "database")
118116
return errors.New("environment must be provided")
119117
}
120-
var newPassword string
121-
byteToken, err := gopass.GetPasswdPrompt(" Database password: ", true, os.Stdin, os.Stdout)
122-
if err == nil {
123-
newPassword = strings.TrimSpace(string(byteToken))
118+
cliExtension := new(common.CliAdditions)
119+
newPassword, err := cliExtension.GetPasswdPrompt(" Database password: ")
120+
if err != nil {
124121
fmt.Println("")
125122
}
126123
serviceName := c.Args().Get(1)

cli/pipelines.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"strings"
77
"time"
88

9-
"github.com/howeyc/gopass"
109
"github.com/stelligent/mu/common"
1110
"github.com/stelligent/mu/workflows"
1211
"github.com/urfave/cli"
@@ -73,9 +72,10 @@ func newPipelinesUpsertCommand(ctx *common.Context) *cli.Command {
7372
workflow := workflows.NewPipelineUpserter(ctx, func(required bool) string {
7473
if required && token == "" {
7574
fmt.Println("CodePipeline requires a personal access token from GitHub - https://github.com/settings/tokens")
76-
byteToken, err := gopass.GetPasswdPrompt(" GitHub token: ", true, os.Stdin, os.Stdout)
77-
if err == nil {
78-
token = strings.TrimSpace(string(byteToken))
75+
cliExtension := new(common.CliAdditions)
76+
var err error
77+
token, err = cliExtension.GetPasswdPrompt(" GitHub token: ")
78+
if err != nil {
7979
fmt.Println("")
8080
}
8181
}

common/cli_extension.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package common
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
input "github.com/tcnksm/go-input"
9+
)
10+
11+
// CliExtension is an interface for defining extended cli actions
12+
type CliExtension interface {
13+
Prompt(message string, def bool) (bool, error)
14+
}
15+
16+
// CliAdditions exposes methods to prompt the user for cli input
17+
type CliAdditions struct{}
18+
19+
// Prompt prompts the user to answer a yes/no question
20+
func (cli *CliAdditions) Prompt(message string, def bool) (bool, error) {
21+
22+
ui := NewUI()
23+
defPrompt := "no"
24+
if def {
25+
defPrompt = "yes"
26+
}
27+
answer, err := ui.Ask(message, &input.Options{
28+
Default: defPrompt,
29+
Required: true,
30+
Loop: true,
31+
ValidateFunc: func(s string) error {
32+
if s != "y" && s != "n" {
33+
return fmt.Errorf("input must be y or n")
34+
}
35+
return nil
36+
},
37+
})
38+
line := strings.ToLower(answer)
39+
if line == "y" {
40+
return true, err
41+
}
42+
if line == "n" {
43+
return false, err
44+
}
45+
return def, err
46+
}
47+
48+
// GetPasswdPrompt prompts the user to enter a password
49+
func (cli *CliAdditions) GetPasswdPrompt(message string) (string, error) {
50+
ui := NewUI()
51+
password, err := ui.Ask(message, &input.Options{
52+
Required: true,
53+
Loop: true,
54+
Mask: true,
55+
MaskDefault: true,
56+
})
57+
return strings.TrimSpace(password), err
58+
}
59+
60+
// NewUI returns a new input.UI bound to Std(in/out)
61+
func NewUI() *input.UI {
62+
return &input.UI{
63+
Writer: os.Stdout,
64+
Reader: os.Stdin,
65+
}
66+
}

common/cli_extension_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package common
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/stretchr/testify/mock"
9+
)
10+
11+
type mockedCliPrompt struct {
12+
mock.Mock
13+
}
14+
15+
func (m *mockedCliPrompt) Prompt(prompt string, def bool) (bool, error) {
16+
args := m.Called(prompt, def)
17+
return args.Bool(0), args.Error(1)
18+
}
19+
20+
func TestPrompt_True(t *testing.T) {
21+
assert := assert.New(t)
22+
23+
cli := new(mockedCliPrompt)
24+
cli.On("Prompt", "foo", true).Return(true, nil)
25+
26+
answer, err := cli.Prompt("foo", true)
27+
28+
assert.Nil(err)
29+
30+
cli.AssertExpectations(t)
31+
cli.AssertNumberOfCalls(t, "Prompt", 1)
32+
33+
assert.Equal(answer, true)
34+
}
35+
func TestPrompt_False(t *testing.T) {
36+
assert := assert.New(t)
37+
38+
cli := new(mockedCliPrompt)
39+
cli.On("Prompt", "foo", true).Return(false, nil)
40+
41+
answer, err := cli.Prompt("foo", true)
42+
43+
assert.Nil(err)
44+
45+
cli.AssertExpectations(t)
46+
cli.AssertNumberOfCalls(t, "Prompt", 1)
47+
48+
assert.Equal(answer, false)
49+
}

workflows/database_upsert.go

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package workflows
33
import (
44
"crypto/rand"
55
"fmt"
6+
"os"
67
"strconv"
78
"strings"
89

@@ -17,13 +18,15 @@ func NewDatabaseUpserter(ctx *common.Context, environmentName string) Executor {
1718
workflow.codeRevision = ctx.Config.Repo.Revision
1819
workflow.repoName = ctx.Config.Repo.Slug
1920

21+
cliExtension := new(common.CliAdditions)
2022
ecsImportParams := make(map[string]string)
2123

2224
return newPipelineExecutor(
2325
workflow.databaseInput(ctx, "", environmentName),
2426
workflow.databaseEnvironmentLoader(ctx.Config.Namespace, environmentName, ctx.StackManager, ecsImportParams, ctx.ElbManager),
2527
workflow.databaseRolesetUpserter(ctx.RolesetManager, ctx.RolesetManager, environmentName),
26-
workflow.databaseDeployer(ctx.Config.Namespace, &ctx.Config.Service, ecsImportParams, environmentName, ctx.StackManager, ctx.StackManager, ctx.RdsManager, ctx.ParamManager),
28+
workflow.databaseMasterPassword(ctx.Config.Namespace, &ctx.Config.Service, &ecsImportParams, environmentName, ctx.ParamManager, cliExtension),
29+
workflow.databaseDeployer(ctx.Config.Namespace, &ctx.Config.Service, ecsImportParams, environmentName, ctx.StackManager, ctx.StackManager, ctx.RdsManager),
2730
)
2831
}
2932

@@ -74,7 +77,47 @@ func (workflow *databaseWorkflow) databaseRolesetUpserter(rolesetUpserter common
7477
}
7578
}
7679

77-
func (workflow *databaseWorkflow) databaseDeployer(namespace string, service *common.Service, stackParams map[string]string, environmentName string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter, rdsSetter common.RdsIamAuthenticationSetter, paramManager common.ParamManager) Executor {
80+
// Fetch password parameter if needed
81+
func (workflow *databaseWorkflow) databaseMasterPassword(namespace string,
82+
service *common.Service, params *map[string]string, environmentName string,
83+
paramManager common.ParamManager, cliExtension common.CliExtension) Executor {
84+
return func() error {
85+
86+
dbStackName := common.CreateStackName(namespace, common.StackTypeDatabase, workflow.serviceName, environmentName)
87+
masterPasswordSSMParam := service.Database.MasterPasswordSSMParam
88+
//DatabaseMasterPassword:
89+
if masterPasswordSSMParam == "" {
90+
dbPassSSMParam := fmt.Sprintf("%s-%s", dbStackName, "DatabaseMasterPassword")
91+
dbPassVersion, err := paramManager.ParamVersion(dbPassSSMParam)
92+
if err != nil {
93+
log.Warningf("Error with ParamVersion for DatabaseMasterPassword, assuming empty: %s", err)
94+
answer, err := cliExtension.Prompt("Error retrieving DatabaseMasterPassword. Set a new DatabaseMasterPassword", false)
95+
if err != nil {
96+
log.Errorf("Error with command input: %s", err)
97+
os.Exit(1)
98+
}
99+
if !answer {
100+
os.Exit(126)
101+
}
102+
}
103+
if dbPassVersion == 0 {
104+
dbPass := randomPassword(32)
105+
err = paramManager.SetParam(dbPassSSMParam, dbPass, workflow.databaseKeyArn)
106+
if err != nil {
107+
return err
108+
}
109+
dbPassVersion = 1
110+
}
111+
masterPasswordSSMParam = fmt.Sprintf("{{resolve:ssm-secure:%s:%d}}", dbPassSSMParam, dbPassVersion)
112+
} else {
113+
masterPasswordSSMParam = fmt.Sprintf("{{resolve:ssm-secure:%s}}", masterPasswordSSMParam)
114+
}
115+
(*params)["DatabaseMasterPassword"] = masterPasswordSSMParam
116+
return nil
117+
}
118+
}
119+
120+
func (workflow *databaseWorkflow) databaseDeployer(namespace string, service *common.Service, stackParams map[string]string, environmentName string, stackUpserter common.StackUpserter, stackWaiter common.StackWaiter, rdsSetter common.RdsIamAuthenticationSetter) Executor {
78121
return func() error {
79122

80123
if service.Database.Name == "" {
@@ -102,26 +145,6 @@ func (workflow *databaseWorkflow) databaseDeployer(namespace string, service *co
102145
stackParams["DatabaseMasterUsername"] = "admin"
103146
common.NewMapElementIfNotEmpty(stackParams, "DatabaseMasterUsername", dbConfig.MasterUsername)
104147

105-
//DatabaseMasterPassword:
106-
if service.Database.MasterPasswordSSMParam == "" {
107-
dbPassSSMParam := fmt.Sprintf("%s-%s", dbStackName, "DatabaseMasterPassword")
108-
dbPassVersion, err := paramManager.ParamVersion(dbPassSSMParam)
109-
if err != nil {
110-
log.Warningf("Error with ParamVersion for DatabaseMasterPassword, assuming empty: %s", err)
111-
}
112-
if dbPassVersion == 0 {
113-
dbPass := randomPassword(32)
114-
err = paramManager.SetParam(dbPassSSMParam, dbPass, workflow.databaseKeyArn)
115-
if err != nil {
116-
return err
117-
}
118-
dbPassVersion = 1
119-
}
120-
service.Database.MasterPasswordSSMParam = fmt.Sprintf("{{resolve:ssm-secure:%s:%d}}", dbPassSSMParam, dbPassVersion)
121-
} else {
122-
service.Database.MasterPasswordSSMParam = fmt.Sprintf("{{resolve:ssm-secure:%s}}", service.Database.MasterPasswordSSMParam)
123-
}
124-
stackParams["DatabaseMasterPassword"] = service.Database.MasterPasswordSSMParam
125148
stackParams["DatabaseKeyArn"] = workflow.databaseKeyArn
126149

127150
tags := createTagMap(&DatabaseTags{

0 commit comments

Comments
 (0)