diff --git a/api/go.sum b/api/go.sum index c827e5a13..d6db20c8c 100644 --- a/api/go.sum +++ b/api/go.sum @@ -158,7 +158,6 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/builtin/logical/rabbitmq/backend_test.go b/builtin/logical/rabbitmq/backend_test.go index 2b45945d2..90963aca2 100644 --- a/builtin/logical/rabbitmq/backend_test.go +++ b/builtin/logical/rabbitmq/backend_test.go @@ -10,8 +10,8 @@ import ( "github.com/hashicorp/vault/helper/testhelpers/docker" logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" + "github.com/hashicorp/vault/sdk/helper/base62" "github.com/hashicorp/vault/sdk/helper/jsonutil" - "github.com/hashicorp/vault/sdk/helper/random" "github.com/hashicorp/vault/sdk/logical" rabbithole "github.com/michaelklishin/rabbit-hole" "github.com/mitchellh/mapstructure" @@ -163,7 +163,10 @@ func TestBackend_roleWithPasswordPolicy(t *testing.T) { } backendConfig := logical.TestBackendConfig() - backendConfig.System.(*logical.StaticSystemView).SetPasswordPolicy("testpolicy", random.DefaultStringGenerator) + passGen := func() (password string, err error) { + return base62.Random(30) + } + backendConfig.System.(*logical.StaticSystemView).SetPasswordPolicy("testpolicy", passGen) b, _ := Factory(context.Background(), backendConfig) cleanup, uri, _ := prepareRabbitMQTestContainer(t) diff --git a/sdk/helper/random/parser.go b/helper/random/parser.go similarity index 100% rename from sdk/helper/random/parser.go rename to helper/random/parser.go diff --git a/sdk/helper/random/parser_test.go b/helper/random/parser_test.go similarity index 100% rename from sdk/helper/random/parser_test.go rename to helper/random/parser_test.go diff --git a/sdk/helper/random/registry.go b/helper/random/registry.go similarity index 100% rename from sdk/helper/random/registry.go rename to helper/random/registry.go diff --git a/sdk/helper/random/registry_test.go b/helper/random/registry_test.go similarity index 100% rename from sdk/helper/random/registry_test.go rename to helper/random/registry_test.go diff --git a/sdk/helper/random/rules.go b/helper/random/rules.go similarity index 100% rename from sdk/helper/random/rules.go rename to helper/random/rules.go diff --git a/sdk/helper/random/rules_test.go b/helper/random/rules_test.go similarity index 100% rename from sdk/helper/random/rules_test.go rename to helper/random/rules_test.go diff --git a/sdk/helper/random/serializing.go b/helper/random/serializing.go similarity index 100% rename from sdk/helper/random/serializing.go rename to helper/random/serializing.go diff --git a/sdk/helper/random/serializing_test.go b/helper/random/serializing_test.go similarity index 100% rename from sdk/helper/random/serializing_test.go rename to helper/random/serializing_test.go diff --git a/sdk/helper/random/string_generator.go b/helper/random/string_generator.go similarity index 100% rename from sdk/helper/random/string_generator.go rename to helper/random/string_generator.go diff --git a/sdk/helper/random/string_generator_test.go b/helper/random/string_generator_test.go similarity index 100% rename from sdk/helper/random/string_generator_test.go rename to helper/random/string_generator_test.go diff --git a/sdk/logical/system_view.go b/sdk/logical/system_view.go index b68e1bbc2..8ea6766b9 100644 --- a/sdk/logical/system_view.go +++ b/sdk/logical/system_view.go @@ -86,6 +86,8 @@ type ExtendedSystemView interface { ForwardGenericRequest(context.Context, *Request) (*Response, error) } +type PasswordGenerator func() (password string, err error) + type StaticSystemView struct { DefaultLeaseTTLVal time.Duration MaxLeaseTTLVal time.Duration @@ -101,7 +103,7 @@ type StaticSystemView struct { Features license.Features VaultVersion string PluginEnvironment *PluginEnvironment - PasswordPolicies map[string]PasswordPolicy + PasswordPolicies map[string]PasswordGenerator } type noopAuditor struct{} @@ -192,14 +194,14 @@ func (d StaticSystemView) GeneratePasswordFromPolicy(ctx context.Context, policy if !exists { return "", fmt.Errorf("password policy not found") } - return policy.Generate(ctx, nil) + return policy() } -func (d *StaticSystemView) SetPasswordPolicy(name string, policy PasswordPolicy) { +func (d *StaticSystemView) SetPasswordPolicy(name string, generator PasswordGenerator) { if d.PasswordPolicies == nil { - d.PasswordPolicies = map[string]PasswordPolicy{} + d.PasswordPolicies = map[string]PasswordGenerator{} } - d.PasswordPolicies[name] = policy + d.PasswordPolicies[name] = generator } func (d *StaticSystemView) DeletePasswordPolicy(name string) (existed bool) { diff --git a/sdk/plugin/grpc_system_test.go b/sdk/plugin/grpc_system_test.go index cf6c11506..205619463 100644 --- a/sdk/plugin/grpc_system_test.go +++ b/sdk/plugin/grpc_system_test.go @@ -9,7 +9,6 @@ import ( "github.com/golang/protobuf/proto" plugin "github.com/hashicorp/go-plugin" "github.com/hashicorp/vault/sdk/helper/consts" - "github.com/hashicorp/vault/sdk/helper/random" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/plugin/pb" "google.golang.org/grpc" @@ -242,30 +241,13 @@ func TestSystem_GRPC_pluginEnv(t *testing.T) { func TestSystem_GRPC_GeneratePasswordFromPolicy(t *testing.T) { policyName := "testpolicy" - expectedPolicy := &random.StringGenerator{ - Length: 8, - Rules: []random.Rule{ - &random.CharsetRule{ - Charset: random.LowercaseRuneset, - MinChars: 1, - }, - &random.CharsetRule{ - Charset: random.UppercaseRuneset, - MinChars: 1, - }, - &random.CharsetRule{ - Charset: random.NumericRuneset, - MinChars: 1, - }, - &random.CharsetRule{ - Charset: random.ShortSymbolRuneset, - MinChars: 1, - }, - }, + expectedPassword := "87354qtnjgrehiogd9u0t43" + passGen := func() (password string, err error) { + return expectedPassword, nil } sys := &logical.StaticSystemView{ - PasswordPolicies: map[string]logical.PasswordPolicy{ - policyName: logical.PasswordPolicy(expectedPolicy), + PasswordPolicies: map[string]logical.PasswordGenerator{ + policyName: passGen, }, } @@ -287,15 +269,7 @@ func TestSystem_GRPC_GeneratePasswordFromPolicy(t *testing.T) { t.Fatalf("no error expected, got: %s", err) } - passRunes := []rune(password) - - if len(passRunes) != expectedPolicy.Length { - t.Fatalf("Generated password should have length %d but was %d", expectedPolicy.Length, len(passRunes)) - } - - for _, rule := range expectedPolicy.Rules { - if !rule.Pass(passRunes) { - t.Fatalf("Password [%s] did not pass rule: %#v", password, rule) - } + if password != expectedPassword { + t.Fatalf("Actual password: %s\nExpected password: %s", password, expectedPassword) } } diff --git a/vault/dynamic_system_view.go b/vault/dynamic_system_view.go index 4b5c14371..bf48428e6 100644 --- a/vault/dynamic_system_view.go +++ b/vault/dynamic_system_view.go @@ -8,10 +8,10 @@ import ( "github.com/hashicorp/errwrap" "github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/helper/random" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/license" "github.com/hashicorp/vault/sdk/helper/pluginutil" - "github.com/hashicorp/vault/sdk/helper/random" "github.com/hashicorp/vault/sdk/helper/wrapping" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/version" diff --git a/vault/logical_system.go b/vault/logical_system.go index 19e12a3db..d5fd43d57 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -28,12 +28,12 @@ import ( "github.com/hashicorp/vault/helper/metricsutil" "github.com/hashicorp/vault/helper/monitor" "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/helper/random" "github.com/hashicorp/vault/physical/raft" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/jsonutil" "github.com/hashicorp/vault/sdk/helper/parseutil" - "github.com/hashicorp/vault/sdk/helper/random" "github.com/hashicorp/vault/sdk/helper/strutil" "github.com/hashicorp/vault/sdk/helper/wrapping" "github.com/hashicorp/vault/sdk/logical" diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 1ff14558e..14a725a2f 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -22,10 +22,10 @@ import ( "github.com/hashicorp/vault/helper/builtinplugins" "github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/namespace" + "github.com/hashicorp/vault/helper/random" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/helper/jsonutil" - "github.com/hashicorp/vault/sdk/helper/random" "github.com/hashicorp/vault/sdk/helper/salt" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/version" diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go b/vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go deleted file mode 100644 index 572767263..000000000 --- a/vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go +++ /dev/null @@ -1,150 +0,0 @@ -package random - -import ( - "fmt" - "reflect" - "unicode/utf8" - - "github.com/hashicorp/hcl" - "github.com/mitchellh/mapstructure" -) - -// ParsePolicy is a convenience function for parsing HCL into a StringGenerator. -// See PolicyParser.ParsePolicy for details. -func ParsePolicy(raw string) (gen StringGenerator, err error) { - parser := PolicyParser{ - RuleRegistry: Registry{ - Rules: defaultRuleNameMapping, - }, - } - return parser.ParsePolicy(raw) -} - -// ParsePolicyBytes is a convenience function for parsing HCL into a StringGenerator. -// See PolicyParser.ParsePolicy for details. -func ParsePolicyBytes(raw []byte) (gen StringGenerator, err error) { - return ParsePolicy(string(raw)) -} - -// PolicyParser parses string generator configuration from HCL. -type PolicyParser struct { - // RuleRegistry maps rule names in HCL to Rule constructors. - RuleRegistry Registry -} - -// ParsePolicy parses the provided HCL into a StringGenerator. -func (p PolicyParser) ParsePolicy(raw string) (sg StringGenerator, err error) { - rawData := map[string]interface{}{} - err = hcl.Decode(&rawData, raw) - if err != nil { - return sg, fmt.Errorf("unable to decode: %w", err) - } - - // Decode the top level items - gen := StringGenerator{} - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Result: &gen, - DecodeHook: stringToRunesFunc, - }) - if err != nil { - return sg, fmt.Errorf("unable to decode configuration: %w", err) - } - - err = decoder.Decode(rawData) - if err != nil { - return sg, fmt.Errorf("failed to decode configuration: %w", err) - } - - // Decode & parse rules - rawRules, err := getMapSlice(rawData, "rule") - if err != nil { - return sg, fmt.Errorf("unable to retrieve rules: %w", err) - } - - rules, err := parseRules(p.RuleRegistry, rawRules) - if err != nil { - return sg, fmt.Errorf("unable to parse rules: %w", err) - } - - gen = StringGenerator{ - Length: gen.Length, - Rules: rules, - } - - err = gen.validateConfig() - if err != nil { - return sg, err - } - - return gen, nil -} - -func parseRules(registry Registry, rawRules []map[string]interface{}) (rules []Rule, err error) { - for _, rawRule := range rawRules { - info, err := getRuleInfo(rawRule) - if err != nil { - return nil, fmt.Errorf("unable to get rule info: %w", err) - } - - rule, err := registry.parseRule(info.ruleType, info.data) - if err != nil { - return nil, fmt.Errorf("unable to parse rule %s: %w", info.ruleType, err) - } - rules = append(rules, rule) - } - - return rules, nil -} - -// getMapSlice from the provided map. This will retrieve and type-assert a []map[string]interface{} from the map -// This will not error if the key does not exist -// This will return an error if the value at the provided key is not of type []map[string]interface{} -func getMapSlice(m map[string]interface{}, key string) (mapSlice []map[string]interface{}, err error) { - rawSlice, exists := m[key] - if !exists { - return nil, nil - } - - mapSlice = []map[string]interface{}{} - err = mapstructure.Decode(rawSlice, &mapSlice) - if err != nil { - return nil, err - } - return mapSlice, nil -} - -type ruleInfo struct { - ruleType string - data map[string]interface{} -} - -// getRuleInfo splits the provided HCL-decoded rule into its rule type along with the data associated with it -func getRuleInfo(rule map[string]interface{}) (data ruleInfo, err error) { - // There should only be one key, but it's a dynamic key yay! - for key := range rule { - slice, err := getMapSlice(rule, key) - if err != nil { - return data, fmt.Errorf("unable to get rule data: %w", err) - } - data = ruleInfo{ - ruleType: key, - data: slice[0], - } - return data, nil - } - return data, fmt.Errorf("rule is empty") -} - -// stringToRunesFunc converts a string to a []rune for use in the mapstructure library -func stringToRunesFunc(from reflect.Kind, to reflect.Kind, data interface{}) (interface{}, error) { - if from != reflect.String || to != reflect.Slice { - return data, nil - } - - raw := data.(string) - - if !utf8.ValidString(raw) { - return nil, fmt.Errorf("invalid UTF8 string") - } - return []rune(raw), nil -} diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/random/registry.go b/vendor/github.com/hashicorp/vault/sdk/helper/random/registry.go deleted file mode 100644 index efdcf5c30..000000000 --- a/vendor/github.com/hashicorp/vault/sdk/helper/random/registry.go +++ /dev/null @@ -1,35 +0,0 @@ -package random - -import ( - "fmt" -) - -type ruleConstructor func(map[string]interface{}) (Rule, error) - -var ( - // defaultRuleNameMapping is the default mapping of HCL rule names to the appropriate rule constructor. - // Add to this map when adding a new Rule type to be recognized in HCL. - defaultRuleNameMapping = map[string]ruleConstructor{ - "charset": ParseCharset, - } - - defaultRegistry = Registry{ - Rules: defaultRuleNameMapping, - } -) - -// Registry of HCL rule names to rule constructors. -type Registry struct { - // Rules maps names of rules to a constructor for the rule - Rules map[string]ruleConstructor -} - -func (r Registry) parseRule(ruleType string, ruleData map[string]interface{}) (rule Rule, err error) { - constructor, exists := r.Rules[ruleType] - if !exists { - return nil, fmt.Errorf("unrecognized rule type %s", ruleType) - } - - rule, err = constructor(ruleData) - return rule, err -} diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/random/rules.go b/vendor/github.com/hashicorp/vault/sdk/helper/random/rules.go deleted file mode 100644 index fead5b4ff..000000000 --- a/vendor/github.com/hashicorp/vault/sdk/helper/random/rules.go +++ /dev/null @@ -1,91 +0,0 @@ -package random - -import ( - "fmt" - - "github.com/mitchellh/mapstructure" -) - -// Rule to assert on string values. -type Rule interface { - // Pass should return true if the provided value passes any assertions this Rule is making. - Pass(value []rune) bool - - // Type returns the name of the rule as associated in the registry - Type() string -} - -// CharsetRule requires a certain number of characters from the specified charset. -type CharsetRule struct { - // CharsetRule is the list of rules that candidate strings must contain a minimum number of. - Charset runes `mapstructure:"charset" json:"charset"` - - // MinChars indicates the minimum (inclusive) number of characters from the charset that should appear in the string. - MinChars int `mapstructure:"min-chars" json:"min-chars"` -} - -// ParseCharset from the provided data map. The data map is expected to be parsed from HCL. -func ParseCharset(data map[string]interface{}) (rule Rule, err error) { - cr := &CharsetRule{} - - decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ - Metadata: nil, - Result: cr, - DecodeHook: stringToRunesFunc, - }) - if err != nil { - return nil, fmt.Errorf("unable to decode charset restriction: %w", err) - } - - err = decoder.Decode(data) - if err != nil { - return nil, fmt.Errorf("failed to parse charset restriction: %w", err) - } - - return *cr, nil -} - -func (c CharsetRule) Type() string { - return "charset" -} - -// Chars returns the charset that this rule is looking for. -func (c CharsetRule) Chars() []rune { - return c.Charset -} - -func (c CharsetRule) MinLength() int { - return c.MinChars -} - -// Pass returns true if the provided candidate string has a minimum number of chars in it. -// This adheres to the Rule interface -func (c CharsetRule) Pass(value []rune) bool { - if c.MinChars <= 0 { - return true - } - - count := 0 - for _, r := range value { - // charIn is sometimes faster than a map lookup because the data is so small - // This is being kept rather than converted to a map to keep the code cleaner, - // otherwise there would need to be additional parsing logic. - if charIn(r, c.Charset) { - count++ - if count >= c.MinChars { - return true - } - } - } - - return false -} - -func charIn(search rune, charset []rune) bool { - for _, r := range charset { - if search == r { - return true - } - } - return false -} diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/random/serializing.go b/vendor/github.com/hashicorp/vault/sdk/helper/random/serializing.go deleted file mode 100644 index c99d631aa..000000000 --- a/vendor/github.com/hashicorp/vault/sdk/helper/random/serializing.go +++ /dev/null @@ -1,88 +0,0 @@ -package random - -import ( - "encoding/json" - "fmt" - - "github.com/mitchellh/mapstructure" -) - -// serializableRules is a slice of rules that can be marshalled to JSON in an HCL format -type serializableRules []Rule - -// MarshalJSON in an HCL-friendly way -func (r serializableRules) MarshalJSON() (b []byte, err error) { - // Example: - // [ - // { - // "testrule": [ - // { - // "string": "teststring", - // "int": 123 - // } - // ] - // }, - // { - // "charset": [ - // { - // "charset": "abcde", - // "min-chars": 2 - // } - // ] - // } - // ] - data := []map[string][]map[string]interface{}{} // Totally not confusing at all - for _, rule := range r { - ruleData := map[string]interface{}{} - err = mapstructure.Decode(rule, &ruleData) - if err != nil { - return nil, fmt.Errorf("unable to decode rule: %w", err) - } - - ruleMap := map[string][]map[string]interface{}{ - rule.Type(): []map[string]interface{}{ - ruleData, - }, - } - data = append(data, ruleMap) - } - - b, err = json.Marshal(data) - return b, err -} - -func (r *serializableRules) UnmarshalJSON(data []byte) (err error) { - mapData := []map[string]interface{}{} - err = json.Unmarshal(data, &mapData) - if err != nil { - return err - } - rules, err := parseRules(defaultRegistry, mapData) - if err != nil { - return err - } - *r = rules - return nil -} - -type runes []rune - -func (r runes) Len() int { return len(r) } -func (r runes) Less(i, j int) bool { return r[i] < r[j] } -func (r runes) Swap(i, j int) { r[i], r[j] = r[j], r[i] } - -// MarshalJSON converts the runes to a string for smaller JSON and easier readability -func (r runes) MarshalJSON() (b []byte, err error) { - return json.Marshal(string(r)) -} - -// UnmarshalJSON converts a string to []rune -func (r *runes) UnmarshalJSON(data []byte) (err error) { - var str string - err = json.Unmarshal(data, &str) - if err != nil { - return err - } - *r = []rune(str) - return nil -} diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/random/string_generator.go b/vendor/github.com/hashicorp/vault/sdk/helper/random/string_generator.go deleted file mode 100644 index 621930eb6..000000000 --- a/vendor/github.com/hashicorp/vault/sdk/helper/random/string_generator.go +++ /dev/null @@ -1,302 +0,0 @@ -package random - -import ( - "context" - "crypto/rand" - "fmt" - "io" - "math" - "sort" - "time" - "unicode" - - "github.com/hashicorp/go-multierror" -) - -var ( - LowercaseCharset = sortCharset("abcdefghijklmnopqrstuvwxyz") - UppercaseCharset = sortCharset("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - NumericCharset = sortCharset("0123456789") - FullSymbolCharset = sortCharset("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") - ShortSymbolCharset = sortCharset("-") - - AlphabeticCharset = sortCharset(UppercaseCharset + LowercaseCharset) - AlphaNumericCharset = sortCharset(AlphabeticCharset + NumericCharset) - AlphaNumericShortSymbolCharset = sortCharset(AlphaNumericCharset + ShortSymbolCharset) - AlphaNumericFullSymbolCharset = sortCharset(AlphaNumericCharset + FullSymbolCharset) - - LowercaseRuneset = []rune(LowercaseCharset) - UppercaseRuneset = []rune(UppercaseCharset) - NumericRuneset = []rune(NumericCharset) - FullSymbolRuneset = []rune(FullSymbolCharset) - ShortSymbolRuneset = []rune(ShortSymbolCharset) - - AlphabeticRuneset = []rune(AlphabeticCharset) - AlphaNumericRuneset = []rune(AlphaNumericCharset) - AlphaNumericShortSymbolRuneset = []rune(AlphaNumericShortSymbolCharset) - AlphaNumericFullSymbolRuneset = []rune(AlphaNumericFullSymbolCharset) - - // DefaultStringGenerator has reasonable default rules for generating strings - DefaultStringGenerator = &StringGenerator{ - Length: 20, - Rules: []Rule{ - CharsetRule{ - Charset: LowercaseRuneset, - MinChars: 1, - }, - CharsetRule{ - Charset: UppercaseRuneset, - MinChars: 1, - }, - CharsetRule{ - Charset: NumericRuneset, - MinChars: 1, - }, - CharsetRule{ - Charset: ShortSymbolRuneset, - MinChars: 1, - }, - }, - } -) - -func sortCharset(chars string) string { - r := runes(chars) - sort.Sort(r) - return string(r) -} - -// StringGenerator generats random strings from the provided charset & adhering to a set of rules. The set of rules -// are things like CharsetRule which requires a certain number of characters from a sub-charset. -type StringGenerator struct { - // Length of the string to generate. - Length int `mapstructure:"length" json:"length"` - - // Rules the generated strings must adhere to. - Rules serializableRules `mapstructure:"-" json:"rule"` // This is "rule" in JSON so it matches the HCL property type - - // CharsetRule to choose runes from. This is computed from the rules, not directly configurable - charset runes -} - -// Generate a random string from the charset and adhering to the provided rules. -// The io.Reader is optional. If not provided, it will default to the reader from crypto/rand -func (g *StringGenerator) Generate(ctx context.Context, rng io.Reader) (str string, err error) { - if _, hasTimeout := ctx.Deadline(); !hasTimeout { - var cancel func() - ctx, cancel = context.WithTimeout(ctx, 1*time.Second) // Ensure there's a timeout on the context - defer cancel() - } - - // Ensure the generator is configured well since it may be manually created rather than parsed from HCL - err = g.validateConfig() - if err != nil { - return "", err - } - -LOOP: - for { - select { - case <-ctx.Done(): - return "", fmt.Errorf("timed out generating string") - default: - str, err = g.generate(rng) - if err != nil { - return "", err - } - if str == "" { - continue LOOP - } - return str, err - } - } -} - -func (g *StringGenerator) generate(rng io.Reader) (str string, err error) { - // If performance improvements need to be made, this can be changed to read a batch of - // potential strings at once rather than one at a time. This will significantly - // improve performance, but at the cost of added complexity. - candidate, err := randomRunes(rng, g.charset, g.Length) - if err != nil { - return "", fmt.Errorf("unable to generate random characters: %w", err) - } - - for _, rule := range g.Rules { - if !rule.Pass(candidate) { - return "", nil - } - } - - // Passed all rules - return string(candidate), nil -} - -const ( - // maxCharsetLen is the maximum length a charset is allowed to be when generating a candidate string. - // This is the total number of numbers available for selecting an index out of the charset slice. - maxCharsetLen = 256 -) - -// randomRunes creates a random string based on the provided charset. The charset is limited to 255 characters, but -// could be expanded if needed. Expanding the maximum charset size will decrease performance because it will need to -// combine bytes into a larger integer using binary.BigEndian.Uint16() function. -func randomRunes(rng io.Reader, charset []rune, length int) (candidate []rune, err error) { - if len(charset) == 0 { - return nil, fmt.Errorf("no charset specified") - } - if len(charset) > maxCharsetLen { - return nil, fmt.Errorf("charset is too long: limited to %d characters", math.MaxUint8) - } - if length <= 0 { - return nil, fmt.Errorf("unable to generate a zero or negative length runeset") - } - - // This can't always select indexes from [0-maxCharsetLen) because it could introduce bias to the character selection. - // For instance, if the length of the charset is [a-zA-Z0-9-] (length of 63): - // RNG ranges: [0-62][63-125][126-188][189-251] will equally select from the entirety of the charset. However, - // the RNG values [252-255] will select the first 4 characters of the charset while ignoring the remaining 59. - // This results in a bias towards the front of the charset. - // - // To avoid this, we determine the largest integer multiplier of the charset length that is <= maxCharsetLen - // For instance, if the maxCharsetLen is 256 (the size of one byte) and the charset is length 63, the multiplier - // equals 4: - // 256/63 => 4.06 - // Trunc(4.06) => 4 - // Multiply by the charset length - // Subtract 1 to account for 0-based counting and you get the max index value: 251 - maxAllowedRNGValue := (maxCharsetLen/len(charset))*len(charset) - 1 - - // rngBufferMultiplier increases the size of the RNG buffer to account for lost - // indexes due to the maxAllowedRNGValue - rngBufferMultiplier := 1.0 - - // Don't set a multiplier if we are able to use the entire range of indexes - if maxAllowedRNGValue < maxCharsetLen { - // Anything more complicated than an arbitrary percentage appears to have little practical performance benefit - rngBufferMultiplier = 1.5 - } - - // Default to the standard crypto reader if one isn't provided - if rng == nil { - rng = rand.Reader - } - - charsetLen := byte(len(charset)) - - runes := make([]rune, 0, length) - - for len(runes) < length { - // Generate a bunch of indexes - data := make([]byte, int(float64(length)*rngBufferMultiplier)) - numBytes, err := rng.Read(data) - if err != nil { - return nil, err - } - - // Append characters until either we're out of indexes or the length is long enough - for i := 0; i < numBytes; i++ { - // Be careful to ensure that maxAllowedRNGValue isn't >= 256 as it will overflow and this - // comparison will prevent characters from being selected from the charset - if data[i] > byte(maxAllowedRNGValue) { - continue - } - - index := data[i] - if len(charset) != maxCharsetLen { - index = index % charsetLen - } - r := charset[index] - runes = append(runes, r) - - if len(runes) == length { - break - } - } - } - - return runes, nil -} - -// validateConfig of the generator to ensure that we can successfully generate a string. -func (g *StringGenerator) validateConfig() (err error) { - merr := &multierror.Error{} - - // Ensure the sum of minimum lengths in the rules doesn't exceed the length specified - minLen := getMinLength(g.Rules) - if g.Length <= 0 { - merr = multierror.Append(merr, fmt.Errorf("length must be > 0")) - } else if g.Length < minLen { - merr = multierror.Append(merr, fmt.Errorf("specified rules require at least %d characters but %d is specified", minLen, g.Length)) - } - - // Ensure we have a charset & all characters are printable - if len(g.charset) == 0 { - // Yes this is mutating the generator but this is done so we don't have to compute this on every generation - g.charset = getChars(g.Rules) - } - if len(g.charset) == 0 { - merr = multierror.Append(merr, fmt.Errorf("no charset specified")) - } else { - for _, r := range g.charset { - if !unicode.IsPrint(r) { - merr = multierror.Append(merr, fmt.Errorf("non-printable character in charset")) - break - } - } - } - return merr.ErrorOrNil() -} - -// getMinLength from the rules using the optional interface: `MinLength() int` -func getMinLength(rules []Rule) (minLen int) { - type minLengthProvider interface { - MinLength() int - } - - for _, rule := range rules { - mlp, ok := rule.(minLengthProvider) - if !ok { - continue - } - minLen += mlp.MinLength() - } - return minLen -} - -// getChars from the rules using the optional interface: `Chars() []rune` -func getChars(rules []Rule) (chars []rune) { - type charsetProvider interface { - Chars() []rune - } - - for _, rule := range rules { - cp, ok := rule.(charsetProvider) - if !ok { - continue - } - chars = append(chars, cp.Chars()...) - } - return deduplicateRunes(chars) -} - -// deduplicateRunes returns a new slice of sorted & de-duplicated runes -func deduplicateRunes(original []rune) (deduped []rune) { - if len(original) == 0 { - return nil - } - - m := map[rune]bool{} - dedupedRunes := []rune(nil) - - for _, r := range original { - if m[r] { - continue - } - m[r] = true - dedupedRunes = append(dedupedRunes, r) - } - - // They don't have to be sorted, but this is being done to make the charset easier to visualize - sort.Sort(runes(dedupedRunes)) - return dedupedRunes -} diff --git a/vendor/github.com/hashicorp/vault/sdk/logical/system_view.go b/vendor/github.com/hashicorp/vault/sdk/logical/system_view.go index b68e1bbc2..8ea6766b9 100644 --- a/vendor/github.com/hashicorp/vault/sdk/logical/system_view.go +++ b/vendor/github.com/hashicorp/vault/sdk/logical/system_view.go @@ -86,6 +86,8 @@ type ExtendedSystemView interface { ForwardGenericRequest(context.Context, *Request) (*Response, error) } +type PasswordGenerator func() (password string, err error) + type StaticSystemView struct { DefaultLeaseTTLVal time.Duration MaxLeaseTTLVal time.Duration @@ -101,7 +103,7 @@ type StaticSystemView struct { Features license.Features VaultVersion string PluginEnvironment *PluginEnvironment - PasswordPolicies map[string]PasswordPolicy + PasswordPolicies map[string]PasswordGenerator } type noopAuditor struct{} @@ -192,14 +194,14 @@ func (d StaticSystemView) GeneratePasswordFromPolicy(ctx context.Context, policy if !exists { return "", fmt.Errorf("password policy not found") } - return policy.Generate(ctx, nil) + return policy() } -func (d *StaticSystemView) SetPasswordPolicy(name string, policy PasswordPolicy) { +func (d *StaticSystemView) SetPasswordPolicy(name string, generator PasswordGenerator) { if d.PasswordPolicies == nil { - d.PasswordPolicies = map[string]PasswordPolicy{} + d.PasswordPolicies = map[string]PasswordGenerator{} } - d.PasswordPolicies[name] = policy + d.PasswordPolicies[name] = generator } func (d *StaticSystemView) DeletePasswordPolicy(name string) (existed bool) { diff --git a/vendor/modules.txt b/vendor/modules.txt index 494318c69..ccf051072 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -489,7 +489,6 @@ github.com/hashicorp/vault/sdk/helper/pathmanager github.com/hashicorp/vault/sdk/helper/pluginutil github.com/hashicorp/vault/sdk/helper/pointerutil github.com/hashicorp/vault/sdk/helper/policyutil -github.com/hashicorp/vault/sdk/helper/random github.com/hashicorp/vault/sdk/helper/salt github.com/hashicorp/vault/sdk/helper/strutil github.com/hashicorp/vault/sdk/helper/tlsutil