Skip to content

Commit

Permalink
feat: support for generated secrets in schemas
Browse files Browse the repository at this point in the history
If `"default":"<generated>”` is used on a token or a password then it will default to a generated secret

Signed-off-by: Pete Muir <pmuir@bleepbleep.org.uk>
  • Loading branch information
pmuir committed Jun 20, 2019
1 parent d5e2f42 commit 28a4d7c
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
},
"password": {
"type": "string",
"format": "password-passthrough",
"format": "password",
"title": "Jenkins X Admin Password",
"description": "The Admin Password will be used by all services installed by Jenkins X"
}
Expand All @@ -31,7 +31,7 @@
"properties": {
"hmacToken": {
"type": "string",
"format": "token-passthrough",
"format": "token",
"title": "HMAC token",
"description": "The HMAC token is used to validate incoming webhooks, TODO"
}
Expand Down Expand Up @@ -82,7 +82,7 @@
},
"password": {
"type": "string",
"format": "password-passthrough",
"format": "password",
"title": "Docker Registry password",
"description": "TODO"
}
Expand All @@ -107,7 +107,7 @@
"properties": {
"passphrase": {
"type": "string",
"format": "password-passthrough",
"format": "password",
"title": "GPG Passphrase",
"description": "TODO"
}
Expand Down Expand Up @@ -144,7 +144,7 @@
},
"password": {
"type": "string",
"format": "token-passthrough",
"format": "token",
"title": "Pipeline User password",
"description": "The Pipeline User is the user used to perform git operations inside a pipeline. This is normally a bot."
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
adminUser:
password: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/adminuser-password-secret:password-passthrough
password: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/adminuser-password-secret:password
username: admin
docker:
password: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/docker-password-secret:password-passthrough
password: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/docker-password-secret:password
url: https://index.docker.io/v1/
username: james
enableDocker: true
Expand All @@ -11,7 +11,7 @@ gitProvider: github
pipelineUser:
github:
host: github.com
password: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/pipelineuser-github-password-secret:token-passthrough
password: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/pipelineuser-github-password-secret:token
username: james
prow:
hmacToken: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/prow-hmactoken-secret:token-passthrough
hmacToken: vault:gitOps/{{ .org }}/environment-{{ .org }}-{{ .repo }}-dev/prow-hmactoken-secret:token
30 changes: 25 additions & 5 deletions pkg/surveyutils/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strconv"
"strings"

"github.com/jenkins-x/jx/pkg/util/secrets"

"github.com/pkg/errors"

"github.com/jenkins-x/jx/pkg/kube"
Expand All @@ -21,6 +23,10 @@ import (
"gopkg.in/AlecAivazis/survey.v1/terminal"
)

var (
generatedPasswordValue = "<generated>"
)

// Type represents a JSON Schema object type current to https://www.ietf.org/archive/id/draft-handrews-json-schema-validation-01.txt
type Type struct {
Version string `json:"$schema,omitempty"`
Expand Down Expand Up @@ -664,10 +670,10 @@ func (o *JSONSchemaOptions) handleBasicProperty(name string, prefixes []string,
// Custom format support for passwords
storeAsSecret := false
var err error
if util.DereferenceString(t.Format) == "password" || util.DereferenceString(t.Format) == "token" || util.DereferenceString(t.Format) == "password-passthrough" || util.DereferenceString(t.
Format) == "token-passthrough" {
dereferencedFormat := strings.TrimSuffix(util.DereferenceString(t.Format), "-passthrough")
if dereferencedFormat == "password" || dereferencedFormat == "token" {
storeAsSecret = true
result, err = handlePasswordProperty(message, help, ask, validator, surveyOpts, defaultValue,
result, err = handlePasswordProperty(message, help, dereferencedFormat, ask, validator, surveyOpts, defaultValue,
autoAcceptMessage, o.Out, t.Type)
if err != nil {
return errors.WithStack(err)
Expand Down Expand Up @@ -767,7 +773,7 @@ func (o *JSONSchemaOptions) handleBasicProperty(name string, prefixes []string,
if err != nil {
return err
}
secretReference, err := o.CreateSecret(secretName, util.DereferenceString(t.Format), value)
secretReference, err := o.CreateSecret(secretName, dereferencedFormat, value)
if err != nil {
return err
}
Expand All @@ -779,7 +785,7 @@ func (o *JSONSchemaOptions) handleBasicProperty(name string, prefixes []string,
return nil
}

func handlePasswordProperty(message string, help string, ask bool, validator survey.Validator,
func handlePasswordProperty(message string, help string, kind string, ask bool, validator survey.Validator,
surveyOpts survey.AskOpt, defaultValue string, autoAcceptMessage string, out terminal.FileWriter,
t string) (interface{}, error) {
// Secret input
Expand All @@ -788,6 +794,17 @@ func handlePasswordProperty(message string, help string, ask bool, validator sur
Help: help,
}

generated := false
if defaultValue == generatedPasswordValue {
secret, err := secrets.DefaultGenerateSecret()
if err != nil {
return nil, errors.WithStack(err)
}
defaultValue = secret
fmt.Fprintf(terminal.NewAnsiStdout(out), "Generated %s %s, to use it press enter.\nThis is the only time you will be shown it so remember to save it\n", kind, util.ColorInfo(secret))
generated = true
}

var answer string
if ask {
err := survey.AskOne(prompt, &answer, validator, surveyOpts)
Expand All @@ -802,6 +819,9 @@ func handlePasswordProperty(message string, help string, ask bool, validator sur
return nil, errors.Wrapf(err, "writing %s to console", msg)
}
}
if answer == "" && generated {
answer = defaultValue
}
if answer != "" {
result, err := convertAnswer(answer, t)
if err != nil {
Expand Down
26 changes: 26 additions & 0 deletions pkg/surveyutils/jsonschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,32 @@ func TestToken(t *testing.T) {
})
}

func TestGeneratedToken(t *testing.T) {
tests.SkipForWindows(t, "go-expect does not work on windows")
tests.Retry(t, 1, time.Second*10, func(r *tests.R) {
values, secrets, err := GenerateValuesAsYaml(r, "generatedToken.test.schema.json", make(map[string]interface{}), false,
false,
false, false,
func(console *tests.ConsoleWrapper, donec chan struct{}) {
defer close(donec)
// Test boolean type
console.ExpectString("Enter a value for tokenValue")
console.SendLine("")
console.ExpectEOF()
})
assert.Equal(r, `tokenValue:
kind: Secret
name: tokenvalue-secret
`, values)
assert.Len(t, secrets, 1)
secret := secrets[0]
assert.Equal(t, "tokenvalue-secret", secret.Name)
assert.Equal(t, "token", secret.Key)
assert.Len(t, secret.Value, 20)
assert.NoError(r, err)
})
}

func TestEmail(t *testing.T) {
tests.SkipForWindows(t, "go-expect does not work on windows")
tests.Retry(t, 5, time.Second*10, func(r *tests.R) {
Expand Down
14 changes: 14 additions & 0 deletions pkg/surveyutils/test_data/generatedToken.test.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

{
"$id": "https:/jenkins-x.io/tests/basicTypes.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "test values.yaml",
"type": "object",
"properties": {
"tokenValue": {
"type": "string",
"format": "token",
"default":"<generated>"
}
}
}
34 changes: 34 additions & 0 deletions pkg/util/secrets/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package secrets

import (
"github.com/pkg/errors"
"github.com/sethvargo/go-password/password"
)

const (
allowedSymbols = "~!#%^_+-=?,."
length = 20
numDigits = 4
numSymbols = 2
upperCaseAllowed = true
allowRepeat = true
)

// DefaultGenerateSecret generates a secret using sensible defaults
func DefaultGenerateSecret() (string, error) {
input := password.GeneratorInput{
Symbols: allowedSymbols,
}

generator, err := password.NewGenerator(&input)
if err != nil {
return "", errors.Wrap(err, "unable to create password generator")
}

secret, err := generator.Generate(length, numDigits, numSymbols, !upperCaseAllowed, allowRepeat)

if err != nil {
return "", errors.Wrap(err, "unable to generate secret")
}
return secret, nil
}
2 changes: 1 addition & 1 deletion pkg/vault/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestReplaceRealExampleURI(t *testing.T) {
pegomock.RegisterMockTestingT(t)
vaultClient := vault_test.NewMockClient()
path := "secret/gitOps/jenkins-x/environment-tekton-mole-dev/connectors-github-config-clientid-secret"
key := "token-passthrough"
key := "token"
secret := uuid.New()
valuesyaml := fmt.Sprintf(`foo:
bar: vault:%s:%s
Expand Down

0 comments on commit 28a4d7c

Please sign in to comment.