Skip to content
This repository was archived by the owner on Feb 16, 2023. It is now read-only.

Commit 5bf5f95

Browse files
Merge pull request #236 from secrethub/feature/generate-password-with-complex-requirements
Add complex password requirements to generate command
2 parents 6dda104 + b319d52 commit 5bf5f95

1 file changed

Lines changed: 74 additions & 4 deletions

File tree

internals/secrethub/generate.go

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"strconv"
7+
"strings"
78
"time"
89

910
"github.com/secrethub/secrethub-cli/internals/cli/clip"
@@ -23,6 +24,9 @@ var (
2324
// ErrInvalidRandLength is returned when an invalid length is given.
2425
ErrInvalidRandLength = errGenerate.Code("invalid_rand_length").Error("The secret length must be larger than 0")
2526
ErrCannotUseLengthArgAndFlag = errGenerate.Code("length_arg_and_flag").Error("length cannot be provided as an argument and a flag at the same time")
27+
ErrCouldNotFindCharSet = errGenerate.Code("charset_not_found").ErrorPref("could not find charset: %s")
28+
ErrMinFlagInvalidInteger = errGenerate.Code("min_flag_invalid_int").ErrorPref("second part of --min flag is not an integer: %s")
29+
ErrInvalidMinFlag = errGenerate.Code("min_flag_invalid").ErrorPref("min flag must be of the form <charset name>:<minimum count>, invalid min flag: %s")
2630
)
2731

2832
const defaultLength = 22
@@ -36,6 +40,8 @@ type GenerateSecretCommand struct {
3640
firstArg string
3741
secondArg string
3842
lengthArg intValue
43+
charsetFlag charsetValue
44+
mins minRuleValue
3945
copyToClipboard bool
4046
clearClipboardAfter time.Duration
4147
clipper clip.Clipper
@@ -55,12 +61,12 @@ func NewGenerateSecretCommand(io ui.IO, newClient newClientFunc) *GenerateSecret
5561
// Register registers the command, arguments and flags on the provided Registerer.
5662
func (cmd *GenerateSecretCommand) Register(r command.Registerer) {
5763
clause := r.Command("generate", "Generate a random secret.")
58-
clause.HelpLong("By default, it uses numbers (0-9), lowercase letters (a-z) and uppercase letters (A-Z) and a length of 22.")
5964
clause.Arg("secret-path", "The path to write the generated secret to").Required().PlaceHolder(secretPathPlaceHolder).StringVar(&cmd.firstArg)
6065
clause.Flag("length", "The length of the generated secret. Defaults to "+strconv.Itoa(defaultLength)).PlaceHolder(strconv.Itoa(defaultLength)).Short('l').SetValue(&cmd.lengthFlag)
61-
clause.Flag("symbols", "Include symbols in secret.").Short('s').SetValue(&cmd.symbolsFlag)
66+
clause.Flag("min", "<charset>:<n> Ensure that the resulting password contains at least n characters from the given character set. Note that adding constrains reduces the strength of the secret. When possible, avoid any constraints.").SetValue(&cmd.mins)
6267
clause.Flag("clip", "Copy the generated value to the clipboard. The clipboard is automatically cleared after "+units.HumanDuration(cmd.clearClipboardAfter)+".").Short('c').BoolVar(&cmd.copyToClipboard)
63-
68+
clause.Flag("charset", "Define the set of characters to randomly generate a password from. Options are all, alphanumeric, numeric, lowercase, uppercase, letters, symbols and human-readable. Multiple character sets can be combined by supplying them in a comma separated list. Defaults to alphanumeric.").Default("alphanumeric").HintOptions("all", "alphanumeric", "numeric", "lowercase", "uppercase", "letters", "symbols", "human-readable").SetValue(&cmd.charsetFlag)
69+
clause.Flag("symbols", "Include symbols in secret.").Short('s').Hidden().SetValue(&cmd.symbolsFlag)
6470
clause.Arg("rand-command", "").Hidden().StringVar(&cmd.secondArg)
6571
clause.Arg("length", "").Hidden().SetValue(&cmd.lengthArg)
6672

@@ -74,7 +80,15 @@ func (cmd *GenerateSecretCommand) before() error {
7480
return err
7581
}
7682

77-
cmd.generator = randchar.NewGenerator(useSymbols)
83+
charset := cmd.charsetFlag.v
84+
if useSymbols {
85+
charset = charset.Add(randchar.Symbols)
86+
}
87+
88+
cmd.generator, err = randchar.NewRand(charset, cmd.mins.v...)
89+
if err != nil {
90+
return err
91+
}
7892

7993
return nil
8094
}
@@ -180,6 +194,62 @@ func (cmd *GenerateSecretCommand) useSymbols() (bool, error) {
180194
return false, nil
181195
}
182196

197+
type minRuleValue struct {
198+
v []randchar.Option
199+
}
200+
201+
func (ov *minRuleValue) String() string {
202+
return ""
203+
}
204+
205+
func (ov *minRuleValue) Set(flagValue string) error {
206+
elements := strings.Split(flagValue, ":")
207+
if len(elements) != 2 {
208+
return ErrInvalidMinFlag(flagValue)
209+
}
210+
211+
count, err := strconv.Atoi(elements[1])
212+
if err != nil {
213+
return ErrMinFlagInvalidInteger(elements[1])
214+
}
215+
216+
charset, found := randchar.CharsetByName(elements[0])
217+
if !found {
218+
return ErrCouldNotFindCharSet(elements[0])
219+
}
220+
221+
ov.v = append(ov.v, randchar.Min(count, charset))
222+
return nil
223+
}
224+
225+
func (ov *minRuleValue) IsCumulative() bool {
226+
return true
227+
}
228+
229+
type charsetValue struct {
230+
v randchar.Charset
231+
}
232+
233+
func (cv *charsetValue) String() string {
234+
return ""
235+
}
236+
237+
func (cv *charsetValue) Set(flagValue string) error {
238+
charsetNames := strings.Split(flagValue, ",")
239+
for _, charsetName := range charsetNames {
240+
charset, ok := randchar.CharsetByName(charsetName)
241+
if !ok {
242+
return ErrCouldNotFindCharSet(charsetName)
243+
}
244+
cv.v = cv.v.Add(charset)
245+
}
246+
return nil
247+
}
248+
249+
func (cv *charsetValue) IsCumulative() bool {
250+
return true
251+
}
252+
183253
type intValue struct {
184254
v *int
185255
}

0 commit comments

Comments
 (0)