From ad90e0b39d2fa1d44aeaa670ff4ab23d391f6729 Mon Sep 17 00:00:00 2001 From: Michael Golowka <72365+pcman312@users.noreply.github.com> Date: Wed, 27 May 2020 12:28:00 -0600 Subject: [PATCH] Add user configurable password policies available to secret engines (#8637) * Add random string generator with rules engine This adds a random string generation library that validates random strings against a set of rules. The library is designed for use as generating passwords, but can be used to generate any random strings. --- api/go.mod | 4 +- api/go.sum | 5 +- go.mod | 2 +- go.sum | 3 + sdk/go.mod | 3 +- sdk/go.sum | 5 +- sdk/helper/random/parser.go | 150 ++++ sdk/helper/random/parser_test.go | 578 ++++++++++++ sdk/helper/random/registry.go | 35 + sdk/helper/random/registry_test.go | 112 +++ sdk/helper/random/rules.go | 91 ++ sdk/helper/random/rules_test.go | 90 ++ sdk/helper/random/serializing.go | 88 ++ sdk/helper/random/serializing_test.go | 58 ++ sdk/helper/random/string_generator.go | 302 +++++++ sdk/helper/random/string_generator_test.go | 824 ++++++++++++++++++ sdk/logical/storage_inmem.go | 20 + sdk/logical/system_view.go | 29 + sdk/plugin/grpc_system.go | 30 + sdk/plugin/grpc_system_test.go | 68 +- sdk/plugin/pb/backend.pb.go | 537 ++++++++---- sdk/plugin/pb/backend.proto | 13 +- vault/dynamic_system_view.go | 31 +- vault/dynamic_system_view_test.go | 150 ++++ vault/logical_system.go | 183 ++++ vault/logical_system_paths.go | 55 ++ vault/logical_system_test.go | 775 ++++++++++++++++ vendor/github.com/hashicorp/vault/api/go.mod | 4 +- vendor/github.com/hashicorp/vault/api/go.sum | 5 +- .../vault/sdk/helper/random/parser.go | 150 ++++ .../vault/sdk/helper/random/registry.go | 35 + .../vault/sdk/helper/random/rules.go | 91 ++ .../vault/sdk/helper/random/serializing.go | 88 ++ .../sdk/helper/random/string_generator.go | 302 +++++++ .../vault/sdk/logical/storage_inmem.go | 20 + .../vault/sdk/logical/system_view.go | 29 + .../hashicorp/vault/sdk/plugin/grpc_system.go | 30 + .../vault/sdk/plugin/pb/backend.pb.go | 537 ++++++++---- .../vault/sdk/plugin/pb/backend.proto | 13 +- .../mitchellh/mapstructure/.travis.yml | 1 + .../mitchellh/mapstructure/CHANGELOG.md | 19 + .../github.com/mitchellh/mapstructure/go.mod | 2 + .../mitchellh/mapstructure/mapstructure.go | 216 ++++- vendor/modules.txt | 3 +- 44 files changed, 5376 insertions(+), 410 deletions(-) create mode 100644 sdk/helper/random/parser.go create mode 100644 sdk/helper/random/parser_test.go create mode 100644 sdk/helper/random/registry.go create mode 100644 sdk/helper/random/registry_test.go create mode 100644 sdk/helper/random/rules.go create mode 100644 sdk/helper/random/rules_test.go create mode 100644 sdk/helper/random/serializing.go create mode 100644 sdk/helper/random/serializing_test.go create mode 100644 sdk/helper/random/string_generator.go create mode 100644 sdk/helper/random/string_generator_test.go create mode 100644 vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go create mode 100644 vendor/github.com/hashicorp/vault/sdk/helper/random/registry.go create mode 100644 vendor/github.com/hashicorp/vault/sdk/helper/random/rules.go create mode 100644 vendor/github.com/hashicorp/vault/sdk/helper/random/serializing.go create mode 100644 vendor/github.com/hashicorp/vault/sdk/helper/random/string_generator.go diff --git a/api/go.mod b/api/go.mod index af68a92b54..221548e432 100644 --- a/api/go.mod +++ b/api/go.mod @@ -12,8 +12,8 @@ require ( github.com/hashicorp/go-retryablehttp v0.6.2 github.com/hashicorp/go-rootcerts v1.0.1 github.com/hashicorp/hcl v1.0.0 - github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f - github.com/mitchellh/mapstructure v1.1.2 + github.com/hashicorp/vault/sdk v0.1.14-0.20200514144402-4bfac290c352 + github.com/mitchellh/mapstructure v1.2.2 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/square/go-jose.v2 v2.3.1 diff --git a/api/go.sum b/api/go.sum index 172833fa1e..d57d24817b 100644 --- a/api/go.sum +++ b/api/go.sum @@ -75,8 +75,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -95,6 +95,7 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/go.mod b/go.mod index c4b454237f..f29e458b69 100644 --- a/go.mod +++ b/go.mod @@ -109,7 +109,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-testing-interface v1.0.0 github.com/mitchellh/gox v1.0.1 - github.com/mitchellh/mapstructure v1.1.2 + github.com/mitchellh/mapstructure v1.2.2 github.com/mitchellh/reflectwalk v1.0.1 github.com/natefinch/atomic v0.0.0-20150920032501-a62ce929ffcc github.com/ncw/swift v1.0.47 diff --git a/go.sum b/go.sum index ee99816eb2..d7b061ec4a 100644 --- a/go.sum +++ b/go.sum @@ -305,6 +305,7 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18h github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= @@ -635,6 +636,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 h1:1CO5wil3HuiVLrUQ2ovSTO+6AfNOA5EMkHHVyHE9IwA= github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= diff --git a/sdk/go.mod b/sdk/go.mod index 2106fdc74a..5143b72c86 100644 --- a/sdk/go.mod +++ b/sdk/go.mod @@ -24,8 +24,9 @@ require ( github.com/hashicorp/hcl v1.0.0 github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/go-testing-interface v1.0.0 - github.com/mitchellh/mapstructure v1.1.2 + github.com/mitchellh/mapstructure v1.2.2 github.com/pierrec/lz4 v2.0.5+incompatible + github.com/pkg/errors v0.8.1 github.com/ryanuber/go-glob v1.0.0 golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480 golang.org/x/sys v0.0.0-20191008105621-543471e840be diff --git a/sdk/go.sum b/sdk/go.sum index 25730996b1..189c3823f0 100644 --- a/sdk/go.sum +++ b/sdk/go.sum @@ -82,8 +82,8 @@ github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go. github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -92,6 +92,7 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/sdk/helper/random/parser.go b/sdk/helper/random/parser.go new file mode 100644 index 0000000000..572767263e --- /dev/null +++ b/sdk/helper/random/parser.go @@ -0,0 +1,150 @@ +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/sdk/helper/random/parser_test.go b/sdk/helper/random/parser_test.go new file mode 100644 index 0000000000..2ce1fde521 --- /dev/null +++ b/sdk/helper/random/parser_test.go @@ -0,0 +1,578 @@ +package random + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestParsePolicy(t *testing.T) { + type testCase struct { + rawConfig string + expected StringGenerator + expectErr bool + } + + tests := map[string]testCase{ + "unrecognized rule": { + rawConfig: ` + length = 20 + rule "testrule" { + string = "teststring" + int = 123 + }`, + expected: StringGenerator{}, + expectErr: true, + }, + + "charset restrictions": { + rawConfig: ` + length = 20 + rule "charset" { + charset = "abcde" + min-chars = 2 + }`, + expected: StringGenerator{ + Length: 20, + charset: []rune("abcde"), + Rules: []Rule{ + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 2, + }, + }, + }, + expectErr: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual, err := ParsePolicy(test.rawConfig) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected) + } + }) + } +} + +func TestParser_ParsePolicy(t *testing.T) { + type testCase struct { + registry map[string]ruleConstructor + + rawConfig string + expected StringGenerator + expectErr bool + } + + tests := map[string]testCase{ + "empty config": { + registry: defaultRuleNameMapping, + rawConfig: "", + expected: StringGenerator{}, + expectErr: true, + }, + "bogus config": { + registry: defaultRuleNameMapping, + rawConfig: "asdf", + expected: StringGenerator{}, + expectErr: true, + }, + "config with only length": { + registry: defaultRuleNameMapping, + rawConfig: ` + length = 20`, + expected: StringGenerator{}, + expectErr: true, + }, + "config with zero length": { + registry: defaultRuleNameMapping, + rawConfig: ` + length = 0 + rule "charset" { + charset = "abcde" + }`, + expected: StringGenerator{}, + expectErr: true, + }, + "config with negative length": { + registry: defaultRuleNameMapping, + rawConfig: ` + length = -2 + rule "charset" { + charset = "abcde" + }`, + expected: StringGenerator{}, + expectErr: true, + }, + "charset restrictions": { + registry: defaultRuleNameMapping, + rawConfig: ` + length = 20 + rule "charset" { + charset = "abcde" + min-chars = 2 + }`, + expected: StringGenerator{ + Length: 20, + charset: []rune("abcde"), + Rules: []Rule{ + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 2, + }, + }, + }, + expectErr: false, + }, + "test rule": { + registry: map[string]ruleConstructor{ + "testrule": newTestRule, + }, + rawConfig: ` + length = 20 + rule "testrule" { + string = "teststring" + int = 123 + }`, + expected: StringGenerator{ + Length: 20, + charset: deduplicateRunes([]rune("teststring")), + Rules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + }, + }, + expectErr: false, + }, + "test rule and charset restrictions": { + registry: map[string]ruleConstructor{ + "testrule": newTestRule, + "charset": ParseCharset, + }, + rawConfig: ` + length = 20 + rule "testrule" { + string = "teststring" + int = 123 + } + rule "charset" { + charset = "abcde" + min-chars = 2 + }`, + expected: StringGenerator{ + Length: 20, + charset: deduplicateRunes([]rune("abcdeteststring")), + Rules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 2, + }, + }, + }, + expectErr: false, + }, + "unrecognized rule": { + registry: defaultRuleNameMapping, + rawConfig: ` + length = 20 + rule "testrule" { + string = "teststring" + int = 123 + }`, + expected: StringGenerator{}, + expectErr: true, + }, + + // ///////////////////////////////////////////////// + // JSON data + "manually JSONified HCL": { + registry: map[string]ruleConstructor{ + "testrule": newTestRule, + "charset": ParseCharset, + }, + rawConfig: ` + { + "charset": "abcde", + "length": 20, + "rule": [ + { + "testrule": [ + { + "string": "teststring", + "int": 123 + } + ] + }, + { + "charset": [ + { + "charset": "abcde", + "min-chars": 2 + } + ] + } + ] + }`, + expected: StringGenerator{ + Length: 20, + charset: deduplicateRunes([]rune("abcdeteststring")), + Rules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 2, + }, + }, + }, + expectErr: false, + }, + "JSONified HCL": { + registry: map[string]ruleConstructor{ + "testrule": newTestRule, + "charset": ParseCharset, + }, + rawConfig: toJSON(t, StringGenerator{ + Length: 20, + Rules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 2, + }, + }, + }), + expected: StringGenerator{ + Length: 20, + charset: deduplicateRunes([]rune("abcdeteststring")), + Rules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 2, + }, + }, + }, + expectErr: false, + }, + "JSON unrecognized rule": { + registry: defaultRuleNameMapping, + rawConfig: ` + { + "charset": "abcde", + "length": 20, + "rule": [ + { + "testrule": [ + { + "string": "teststring", + "int": 123 + } + ], + } + ] + }`, + expected: StringGenerator{}, + expectErr: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + parser := PolicyParser{ + RuleRegistry: Registry{ + Rules: test.registry, + }, + } + + actual, err := parser.ParsePolicy(test.rawConfig) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected) + } + }) + } +} + +func TestParseRules(t *testing.T) { + type testCase struct { + registry map[string]ruleConstructor + + rawRules []map[string]interface{} + expectedRules []Rule + expectErr bool + } + + tests := map[string]testCase{ + "nil rule data": { + registry: defaultRuleNameMapping, + rawRules: nil, + expectedRules: nil, + expectErr: false, + }, + "empty rule data": { + registry: defaultRuleNameMapping, + rawRules: []map[string]interface{}{}, + expectedRules: nil, + expectErr: false, + }, + "invalid rule data": { + registry: defaultRuleNameMapping, + rawRules: []map[string]interface{}{ + { + "testrule": map[string]interface{}{ + "string": "teststring", + }, + }, + }, + expectedRules: nil, + expectErr: true, + }, + "unrecognized rule data": { + registry: defaultRuleNameMapping, + rawRules: []map[string]interface{}{ + { + "testrule": []map[string]interface{}{ + { + "string": "teststring", + "int": 123, + }, + }, + }, + }, + expectedRules: nil, + expectErr: true, + }, + "recognized rule": { + registry: map[string]ruleConstructor{ + "testrule": newTestRule, + }, + rawRules: []map[string]interface{}{ + { + "testrule": []map[string]interface{}{ + { + "string": "teststring", + "int": 123, + }, + }, + }, + }, + expectedRules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + }, + expectErr: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + registry := Registry{ + Rules: test.registry, + } + + actualRules, err := parseRules(registry, test.rawRules) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actualRules, test.expectedRules) { + t.Fatalf("Actual: %#v\nExpected:%#v", actualRules, test.expectedRules) + } + }) + } +} + +func TestGetMapSlice(t *testing.T) { + type testCase struct { + input map[string]interface{} + key string + expectedSlice []map[string]interface{} + expectErr bool + } + + tests := map[string]testCase{ + "nil map": { + input: nil, + key: "testkey", + expectedSlice: nil, + expectErr: false, + }, + "empty map": { + input: map[string]interface{}{}, + key: "testkey", + expectedSlice: nil, + expectErr: false, + }, + "ignored keys": { + input: map[string]interface{}{ + "foo": "bar", + }, + key: "testkey", + expectedSlice: nil, + expectErr: false, + }, + "key has wrong type": { + input: map[string]interface{}{ + "foo": "bar", + }, + key: "foo", + expectedSlice: nil, + expectErr: true, + }, + "good data": { + input: map[string]interface{}{ + "foo": []map[string]interface{}{ + { + "sub-foo": "bar", + }, + }, + }, + key: "foo", + expectedSlice: []map[string]interface{}{ + { + "sub-foo": "bar", + }, + }, + expectErr: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actualSlice, err := getMapSlice(test.input, test.key) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actualSlice, test.expectedSlice) { + t.Fatalf("Actual: %#v\nExpected:%#v", actualSlice, test.expectedSlice) + } + }) + } +} + +func TestGetRuleInfo(t *testing.T) { + type testCase struct { + rule map[string]interface{} + expectedInfo ruleInfo + expectErr bool + } + + tests := map[string]testCase{ + "nil rule": { + rule: nil, + expectedInfo: ruleInfo{}, + expectErr: true, + }, + "empty rule": { + rule: map[string]interface{}{}, + expectedInfo: ruleInfo{}, + expectErr: true, + }, + "rule with invalid type": { + rule: map[string]interface{}{ + "TestRuleType": "wrong type", + }, + expectedInfo: ruleInfo{}, + expectErr: true, + }, + "rule with good data": { + rule: map[string]interface{}{ + "TestRuleType": []map[string]interface{}{ + { + "foo": "bar", + }, + }, + }, + expectedInfo: ruleInfo{ + ruleType: "TestRuleType", + data: map[string]interface{}{ + "foo": "bar", + }, + }, + expectErr: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actualInfo, err := getRuleInfo(test.rule) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actualInfo, test.expectedInfo) { + t.Fatalf("Actual: %#v\nExpected:%#v", actualInfo, test.expectedInfo) + } + }) + } +} + +func BenchmarkParser_Parse(b *testing.B) { + config := `length = 20 + rule "charset" { + charset = "abcde" + min-chars = 2 + }` + + for i := 0; i < b.N; i++ { + parser := PolicyParser{ + RuleRegistry: Registry{ + Rules: defaultRuleNameMapping, + }, + } + _, err := parser.ParsePolicy(config) + if err != nil { + b.Fatalf("Failed to parse: %s", err) + } + } +} + +func toJSON(t *testing.T, val interface{}) string { + t.Helper() + b, err := json.Marshal(val) + if err != nil { + t.Fatalf("unable to marshal to JSON: %s", err) + } + return string(b) +} diff --git a/sdk/helper/random/registry.go b/sdk/helper/random/registry.go new file mode 100644 index 0000000000..efdcf5c302 --- /dev/null +++ b/sdk/helper/random/registry.go @@ -0,0 +1,35 @@ +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/sdk/helper/random/registry_test.go b/sdk/helper/random/registry_test.go new file mode 100644 index 0000000000..3d7060650b --- /dev/null +++ b/sdk/helper/random/registry_test.go @@ -0,0 +1,112 @@ +package random + +import ( + "fmt" + "reflect" + "testing" + + "github.com/mitchellh/mapstructure" +) + +type testCharsetRule struct { + String string `mapstructure:"string" json:"string"` + Integer int `mapstructure:"int" json:"int"` + + // Default to passing + fail bool +} + +func newTestRule(data map[string]interface{}) (rule Rule, err error) { + tr := &testCharsetRule{} + err = mapstructure.Decode(data, tr) + if err != nil { + return nil, fmt.Errorf("unable to decode test rule") + } + return *tr, nil +} + +func (tr testCharsetRule) Pass([]rune) bool { return !tr.fail } +func (tr testCharsetRule) Type() string { return "testrule" } +func (tr testCharsetRule) Chars() []rune { return []rune(tr.String) } + +func TestParseRule(t *testing.T) { + type testCase struct { + rules map[string]ruleConstructor + + ruleType string + ruleData map[string]interface{} + + expectedRule Rule + expectErr bool + } + + tests := map[string]testCase{ + "missing rule": { + rules: map[string]ruleConstructor{}, + ruleType: "testrule", + ruleData: map[string]interface{}{ + "string": "teststring", + "int": 123, + }, + expectedRule: nil, + expectErr: true, + }, + "nil data": { + rules: map[string]ruleConstructor{ + "testrule": newTestRule, + }, + ruleType: "testrule", + ruleData: nil, + expectedRule: testCharsetRule{}, + expectErr: false, + }, + "good rule": { + rules: map[string]ruleConstructor{ + "testrule": newTestRule, + }, + ruleType: "testrule", + ruleData: map[string]interface{}{ + "string": "teststring", + "int": 123, + }, + expectedRule: testCharsetRule{ + String: "teststring", + Integer: 123, + }, + expectErr: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + reg := Registry{ + Rules: test.rules, + } + + actualRule, err := reg.parseRule(test.ruleType, test.ruleData) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actualRule, test.expectedRule) { + t.Fatalf("Actual: %#v\nExpected:%#v", actualRule, test.expectedRule) + } + }) + } +} + +// Ensure the mappings in the defaultRuleNameMapping are consistent between the keys +// in the map and the Type() calls on the Rule values +func TestDefaultRuleNameMapping(t *testing.T) { + for expectedType, constructor := range defaultRuleNameMapping { + // In this case, we don't care about the error since we're checking the types, not the contents + instance, _ := constructor(map[string]interface{}{}) + actualType := instance.Type() + if actualType != expectedType { + t.Fatalf("Default registry mismatched types: Actual: %s Expected: %s", actualType, expectedType) + } + } +} diff --git a/sdk/helper/random/rules.go b/sdk/helper/random/rules.go new file mode 100644 index 0000000000..fead5b4ffe --- /dev/null +++ b/sdk/helper/random/rules.go @@ -0,0 +1,91 @@ +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/sdk/helper/random/rules_test.go b/sdk/helper/random/rules_test.go new file mode 100644 index 0000000000..18aa008798 --- /dev/null +++ b/sdk/helper/random/rules_test.go @@ -0,0 +1,90 @@ +package random + +import ( + "testing" +) + +func TestCharset(t *testing.T) { + type testCase struct { + charset string + minChars int + input string + expected bool + } + + tests := map[string]testCase{ + "0 minimum, empty input": { + charset: LowercaseCharset, + minChars: 0, + input: "", + expected: true, + }, + "0 minimum, many matching": { + charset: LowercaseCharset, + minChars: 0, + input: LowercaseCharset, + expected: true, + }, + "0 minimum, no matching": { + charset: LowercaseCharset, + minChars: 0, + input: "0123456789", + expected: true, + }, + "1 minimum, empty input": { + charset: LowercaseCharset, + minChars: 1, + input: "", + expected: false, + }, + "1 minimum, no matching": { + charset: LowercaseCharset, + minChars: 1, + input: "0123456789", + expected: false, + }, + "1 minimum, exactly 1 matching": { + charset: LowercaseCharset, + minChars: 1, + input: "a", + expected: true, + }, + "1 minimum, many matching": { + charset: LowercaseCharset, + minChars: 1, + input: "abcdefhaaaa", + expected: true, + }, + "2 minimum, 1 matching": { + charset: LowercaseCharset, + minChars: 2, + input: "f", + expected: false, + }, + "2 minimum, 2 matching": { + charset: LowercaseCharset, + minChars: 2, + input: "fz", + expected: true, + }, + "2 minimum, many matching": { + charset: LowercaseCharset, + minChars: 2, + input: "joixnbonxd", + expected: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + cr := CharsetRule{ + Charset: []rune(test.charset), + MinChars: test.minChars, + } + actual := cr.Pass([]rune(test.input)) + if actual != test.expected { + t.FailNow() + } + }) + } +} diff --git a/sdk/helper/random/serializing.go b/sdk/helper/random/serializing.go new file mode 100644 index 0000000000..c99d631aad --- /dev/null +++ b/sdk/helper/random/serializing.go @@ -0,0 +1,88 @@ +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/sdk/helper/random/serializing_test.go b/sdk/helper/random/serializing_test.go new file mode 100644 index 0000000000..1710537429 --- /dev/null +++ b/sdk/helper/random/serializing_test.go @@ -0,0 +1,58 @@ +package random + +import ( + "encoding/json" + "reflect" + "testing" +) + +func TestJSONMarshalling(t *testing.T) { + expected := serializableRules{ + CharsetRule{ + Charset: LowercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: UppercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: NumericRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: ShortSymbolRuneset, + MinChars: 1, + }, + } + + marshalled, err := json.Marshal(expected) + if err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + actual := serializableRules{} + err = json.Unmarshal(marshalled, &actual) + if err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Actual: %#v\nExpected: %#v", actual, expected) + } +} + +func TestRunes_UnmarshalJSON(t *testing.T) { + data := []byte(`"noaw8hgfsdjlkfsj3"`) + + expected := runes([]rune("noaw8hgfsdjlkfsj3")) + actual := runes{} + err := (&actual).UnmarshalJSON(data) + if err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Actual: %#v\nExpected: %#v", actual, expected) + } +} diff --git a/sdk/helper/random/string_generator.go b/sdk/helper/random/string_generator.go new file mode 100644 index 0000000000..761577455a --- /dev/null +++ b/sdk/helper/random/string_generator.go @@ -0,0 +1,302 @@ +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/sdk/helper/random/string_generator_test.go b/sdk/helper/random/string_generator_test.go new file mode 100644 index 0000000000..55b252a4f9 --- /dev/null +++ b/sdk/helper/random/string_generator_test.go @@ -0,0 +1,824 @@ +package random + +import ( + "context" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "math" + MRAND "math/rand" + "reflect" + "sort" + "testing" + "time" +) + +func TestStringGenerator_Generate_successful(t *testing.T) { + type testCase struct { + timeout time.Duration + generator *StringGenerator + } + + tests := map[string]testCase{ + "common rules": { + timeout: 1 * time.Second, + generator: &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, + }, + }, + charset: AlphaNumericShortSymbolRuneset, + }, + }, + "charset not explicitly specified": { + timeout: 1 * time.Second, + generator: &StringGenerator{ + Length: 20, + Rules: []Rule{ + CharsetRule{ + Charset: LowercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: UppercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: NumericRuneset, + MinChars: 1, + }, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + // One context to rule them all, one context to find them, one context to bring them all and in the darkness bind them. + ctx, cancel := context.WithTimeout(context.Background(), test.timeout) + defer cancel() + + runeset := map[rune]bool{} + runesFound := []rune{} + + for i := 0; i < 100; i++ { + actual, err := test.generator.Generate(ctx, nil) + if err != nil { + t.Fatalf("no error expected, but got: %s", err) + } + for _, r := range actual { + if runeset[r] { + continue + } + runeset[r] = true + runesFound = append(runesFound, r) + } + } + + sort.Sort(runes(runesFound)) + + expectedCharset := getChars(test.generator.Rules) + + if !reflect.DeepEqual(runesFound, expectedCharset) { + t.Fatalf("Didn't find all characters from the charset\nActual : [%s]\nExpected: [%s]", string(runesFound), string(expectedCharset)) + } + }) + } +} + +func TestStringGenerator_Generate_errors(t *testing.T) { + type testCase struct { + timeout time.Duration + generator *StringGenerator + rng io.Reader + } + + tests := map[string]testCase{ + "already timed out": { + timeout: 0, + generator: &StringGenerator{ + Length: 20, + Rules: []Rule{ + testCharsetRule{ + fail: false, + }, + }, + charset: AlphaNumericShortSymbolRuneset, + }, + rng: rand.Reader, + }, + "impossible rules": { + timeout: 10 * time.Millisecond, // Keep this short so the test doesn't take too long + generator: &StringGenerator{ + Length: 20, + Rules: []Rule{ + testCharsetRule{ + fail: true, + }, + }, + charset: AlphaNumericShortSymbolRuneset, + }, + rng: rand.Reader, + }, + "bad RNG reader": { + timeout: 10 * time.Millisecond, // Keep this short so the test doesn't take too long + generator: &StringGenerator{ + Length: 20, + Rules: []Rule{}, + charset: AlphaNumericShortSymbolRuneset, + }, + rng: badReader{}, + }, + "0 length": { + timeout: 10 * time.Millisecond, + generator: &StringGenerator{ + Length: 0, + Rules: []Rule{ + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 0, + }, + }, + charset: []rune("abcde"), + }, + rng: rand.Reader, + }, + "-1 length": { + timeout: 10 * time.Millisecond, + generator: &StringGenerator{ + Length: -1, + Rules: []Rule{ + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 0, + }, + }, + charset: []rune("abcde"), + }, + rng: rand.Reader, + }, + "no charset": { + timeout: 10 * time.Millisecond, + generator: &StringGenerator{ + Length: 20, + Rules: []Rule{}, + }, + rng: rand.Reader, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + // One context to rule them all, one context to find them, one context to bring them all and in the darkness bind them. + ctx, cancel := context.WithTimeout(context.Background(), test.timeout) + defer cancel() + + actual, err := test.generator.Generate(ctx, test.rng) + if err == nil { + t.Fatalf("Expected error but none found") + } + if actual != "" { + t.Fatalf("Random string returned: %s", actual) + } + }) + } +} + +func TestRandomRunes_deterministic(t *testing.T) { + // These tests are to ensure that the charset selection doesn't do anything weird like selecting the same character + // over and over again. The number of test cases here should be kept to a minimum since they are sensitive to changes + type testCase struct { + rngSeed int64 + charset string + length int + expected string + } + + tests := map[string]testCase{ + "small charset": { + rngSeed: 1585593298447807000, + charset: "abcde", + length: 20, + expected: "ddddddcdebbeebdbdbcd", + }, + "common charset": { + rngSeed: 1585593298447807001, + charset: AlphaNumericShortSymbolCharset, + length: 20, + expected: "ON6lVjnBs84zJbUBVEzb", + }, + "max size charset": { + rngSeed: 1585593298447807002, + charset: " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ" + + "ġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠ" + + "šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠", + length: 20, + expected: "tųŎ℄ņ℃Œ.@řHš-ℍ}ħGIJLℏ", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + rng := MRAND.New(MRAND.NewSource(test.rngSeed)) + runes, err := randomRunes(rng, []rune(test.charset), test.length) + if err != nil { + t.Fatalf("Expected no error, but found: %s", err) + } + + str := string(runes) + + if str != test.expected { + t.Fatalf("Actual: %s Expected: %s", str, test.expected) + } + }) + } +} + +func TestRandomRunes_successful(t *testing.T) { + type testCase struct { + charset []rune // Assumes no duplicate runes + length int + } + + tests := map[string]testCase{ + "small charset": { + charset: []rune("abcde"), + length: 20, + }, + "common charset": { + charset: AlphaNumericShortSymbolRuneset, + length: 20, + }, + "max size charset": { + charset: []rune( + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ" + + "ġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠ" + + "šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠", + ), + length: 20, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + runeset := map[rune]bool{} + runesFound := []rune{} + + for i := 0; i < 10000; i++ { + actual, err := randomRunes(rand.Reader, test.charset, test.length) + if err != nil { + t.Fatalf("no error expected, but got: %s", err) + } + for _, r := range actual { + if runeset[r] { + continue + } + runeset[r] = true + runesFound = append(runesFound, r) + } + } + + sort.Sort(runes(runesFound)) + + // Sort the input too just to ensure that they can be compared + sort.Sort(runes(test.charset)) + + if !reflect.DeepEqual(runesFound, test.charset) { + t.Fatalf("Didn't find all characters from the charset\nActual : [%s]\nExpected: [%s]", string(runesFound), string(test.charset)) + } + }) + } +} + +func TestRandomRunes_errors(t *testing.T) { + type testCase struct { + charset []rune + length int + rng io.Reader + } + + tests := map[string]testCase{ + "nil charset": { + charset: nil, + length: 20, + rng: rand.Reader, + }, + "empty charset": { + charset: []rune{}, + length: 20, + rng: rand.Reader, + }, + "charset is too long": { + charset: []rune(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ" + + "ġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠ" + + "šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠" + + "Σ", + ), + length:20, + rng: rand.Reader, + }, + "length is zero": { + charset: []rune("abcde"), + length: 0, + rng: rand.Reader, + }, + "length is negative": { + charset: []rune("abcde"), + length: -3, + rng: rand.Reader, + }, + "reader failed": { + charset: []rune("abcde"), + length: 20, + rng: badReader{}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual, err := randomRunes(test.rng, test.charset, test.length) + if err == nil { + t.Fatalf("Expected error but none found") + } + if actual != nil { + t.Fatalf("Expected no value, but found [%s]", string(actual)) + } + }) + } +} + +func BenchmarkStringGenerator_Generate(b *testing.B) { + lengths := []int{ + 8, 12, 16, 20, 24, 28, + } + + type testCase struct { + generator StringGenerator + } + + benches := map[string]testCase{ + "no rules": { + generator: StringGenerator{ + charset: AlphaNumericFullSymbolRuneset, + Rules: []Rule{}, + }, + }, + "default generator": { + generator: DefaultStringGenerator, + }, + "large symbol set": { + generator: StringGenerator{ + charset: AlphaNumericFullSymbolRuneset, + Rules: []Rule{ + CharsetRule{ + Charset: LowercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: UppercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: NumericRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: FullSymbolRuneset, + MinChars: 1, + }, + }, + }, + }, + "max symbol set": { + generator: StringGenerator{ + charset: []rune(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ" + + "ġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠ" + + "šŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ℀℁ℂ℃℄℅℆ℇ℈℉ℊℋℌℍℎℏℐℑℒℓ℔ℕ№℗℘ℙℚℛℜℝ℞℟℠", + ), + Rules: []Rule{ + CharsetRule{ + Charset: LowercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: UppercaseRuneset, + MinChars: 1, + }, + CharsetRule{ + Charset: []rune("ĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒ"), + MinChars: 1, + }, + }, + }, + }, + "restrictive charset rules": { + generator: StringGenerator{ + charset: AlphaNumericShortSymbolRuneset, + Rules: []Rule{ + CharsetRule{ + Charset: []rune("A"), + MinChars: 1, + }, + CharsetRule{ + Charset: []rune("1"), + MinChars: 1, + }, + CharsetRule{ + Charset: []rune("a"), + MinChars: 1, + }, + CharsetRule{ + Charset: []rune("-"), + MinChars: 1, + }, + }, + }, + }, + } + + for name, bench := range benches { + b.Run(name, func(b *testing.B) { + for _, length := range lengths { + bench.generator.Length = length + b.Run(fmt.Sprintf("length=%d", length), func(b *testing.B) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + str, err := bench.generator.Generate(ctx, nil) + if err != nil { + b.Fatalf("Failed to generate string: %s", err) + } + if str == "" { + b.Fatalf("Didn't error but didn't generate a string") + } + } + }) + } + }) + } + + // Mimic what the SQLCredentialsProducer is doing + b.Run("SQLCredentialsProducer", func(b *testing.B) { + sg := StringGenerator{ + Length: 16, // 16 because the SQLCredentialsProducer prepends 4 characters to a 20 character password + charset: AlphaNumericRuneset, + Rules: nil, + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + str, err := sg.Generate(ctx, nil) + if err != nil { + b.Fatalf("Failed to generate string: %s", err) + } + if str == "" { + b.Fatalf("Didn't error but didn't generate a string") + } + } + }) +} + +// Ensure the StringGenerator can be properly JSON-ified +func TestStringGenerator_JSON(t *testing.T) { + expected := StringGenerator{ + Length: 20, + charset: deduplicateRunes([]rune("teststring" + ShortSymbolCharset)), + Rules: []Rule{ + testCharsetRule{ + String: "teststring", + Integer: 123, + }, + CharsetRule{ + Charset: ShortSymbolRuneset, + MinChars: 1, + }, + }, + } + + b, err := json.Marshal(expected) + if err != nil { + t.Fatalf("Failed to marshal to JSON: %s", err) + } + + parser := PolicyParser{ + RuleRegistry: Registry{ + Rules: map[string]ruleConstructor{ + "testrule": newTestRule, + "charset": ParseCharset, + }, + }, + } + actual, err := parser.ParsePolicy(string(b)) + if err != nil { + t.Fatalf("Failed to parse JSON: %s", err) + } + + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Actual: %#v\nExpected: %#v", actual, expected) + } +} + +type badReader struct{} + +func (badReader) Read([]byte) (int, error) { + return 0, fmt.Errorf("test error") +} + +func TestValidate(t *testing.T) { + type testCase struct { + generator StringGenerator + expectErr bool + } + + tests := map[string]testCase{ + "default generator": { + generator: DefaultStringGenerator, + expectErr: false, + }, + "length is 0": { + generator: StringGenerator{ + Length: 0, + }, + expectErr: true, + }, + "length is negative": { + generator: StringGenerator{ + Length: -2, + }, + expectErr: true, + }, + "nil charset, no rules": { + generator: StringGenerator{ + Length: 5, + charset: nil, + }, + expectErr: true, + }, + "zero length charset, no rules": { + generator: StringGenerator{ + Length: 5, + charset: []rune{}, + }, + expectErr: true, + }, + "rules require password longer than length": { + generator: StringGenerator{ + Length: 5, + charset: []rune("abcde"), + Rules: []Rule{ + CharsetRule{ + Charset: []rune("abcde"), + MinChars: 6, + }, + }, + }, + expectErr: true, + }, + "charset has non-printable characters": { + generator: StringGenerator{ + Length: 0, + charset: []rune{ + 'a', + 'b', + 0, // Null character + 'd', + 'e', + }, + }, + expectErr: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + err := test.generator.validateConfig() + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + }) + } +} + +type testNonCharsetRule struct { + String string `mapstructure:"string" json:"string"` +} + +func (tr testNonCharsetRule) Pass([]rune) bool { return true } +func (tr testNonCharsetRule) Type() string { return "testNonCharsetRule" } + +func TestGetChars(t *testing.T) { + type testCase struct { + rules []Rule + expected []rune + } + + tests := map[string]testCase{ + "nil rules": { + rules: nil, + expected: []rune(nil), + }, + "empty rules": { + rules: []Rule{}, + expected: []rune(nil), + }, + "rule without chars": { + rules: []Rule{ + testNonCharsetRule{ + String: "teststring", + }, + }, + expected: []rune(nil), + }, + "rule with chars": { + rules: []Rule{ + CharsetRule{ + Charset: []rune("abcdefghij"), + MinChars: 1, + }, + }, + expected: []rune("abcdefghij"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := getChars(test.rules) + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("Actual: %v\nExpected: %v", actual, test.expected) + } + }) + } +} + +func TestDeduplicateRunes(t *testing.T) { + type testCase struct { + input []rune + expected []rune + } + + tests := map[string]testCase{ + "empty string": { + input: []rune(""), + expected: []rune(nil), + }, + "no duplicates": { + input: []rune("abcde"), + expected: []rune("abcde"), + }, + "in order duplicates": { + input: []rune("aaaabbbbcccccccddddeeeee"), + expected: []rune("abcde"), + }, + "out of order duplicates": { + input: []rune("abcdeabcdeabcdeabcde"), + expected: []rune("abcde"), + }, + "unicode no duplicates": { + input: []rune("日本語"), + expected: []rune("日本語"), + }, + "unicode in order duplicates": { + input: []rune("日日日日本本本語語語語語"), + expected: []rune("日本語"), + }, + "unicode out of order duplicates": { + input: []rune("日本語日本語日本語日本語"), + expected: []rune("日本語"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + actual := deduplicateRunes(test.input) + if !reflect.DeepEqual(actual, test.expected) { + t.Fatalf("Actual: %#v\nExpected:%#v", actual, test.expected) + } + }) + } +} + +func TestRandomRunes_Bias(t *testing.T) { + type testCase struct { + charset []rune + maxStdDev float64 + } + + tests := map[string]testCase{ + "small charset": { + charset: []rune("abcde"), + maxStdDev: 2700, + }, + "lowercase characters": { + charset: LowercaseRuneset, + maxStdDev: 1000, + }, + "alphabetical characters": { + charset: AlphabeticRuneset, + maxStdDev: 800, + }, + "alphanumeric": { + charset: AlphaNumericRuneset, + maxStdDev: 800, + }, + "alphanumeric with symbol": { + charset: AlphaNumericShortSymbolRuneset, + maxStdDev: 800, + }, + "charset evenly divisible into 256": { + charset: append(AlphaNumericRuneset, '!', '@'), + maxStdDev: 800, + }, + "large charset": { + charset: FullSymbolRuneset, + maxStdDev: 800, + }, + "just under half size charset": { + charset: []rune(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğ"), + maxStdDev: 800, + }, + "half size charset": { + charset: []rune(" !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + + "`abcdefghijklmnopqrstuvwxyz{|}~ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠ"), + maxStdDev: 800, + }, + } + + for name, test := range tests { + t.Run(fmt.Sprintf("%s (%d chars)", name, len(test.charset)), func(t *testing.T) { + runeCounts := map[rune]int{} + + generations := 50000 + length := 100 + for i := 0; i < generations; i++ { + str, err := randomRunes(nil, test.charset, length) + if err != nil { + t.Fatal(err) + } + for _, r := range str { + runeCounts[r]++ + } + } + + chars := charCounts{} + + var sum float64 + for r, count := range runeCounts { + chars = append(chars, charCount{r, count}) + sum += float64(count) + } + + mean := sum / float64(len(runeCounts)) + var stdDev float64 + for _, count := range runeCounts { + stdDev += math.Pow(float64(count)-mean, 2) + } + + stdDev = math.Sqrt(stdDev / float64(len(runeCounts))) + t.Logf("Mean : %10.4f", mean) + + if stdDev > test.maxStdDev { + t.Fatalf("Standard deviation is too large: %.2f > %.2f", stdDev, test.maxStdDev) + } + }) + } +} + +type charCount struct { + r rune + count int +} + +type charCounts []charCount + +func (s charCounts) Len() int { return len(s) } +func (s charCounts) Less(i, j int) bool { return s[i].r < s[j].r } +func (s charCounts) Swap(i, j int) { s[i], s[j] = s[j], s[i] } diff --git a/sdk/logical/storage_inmem.go b/sdk/logical/storage_inmem.go index 4c7afa1b92..65368a070f 100644 --- a/sdk/logical/storage_inmem.go +++ b/sdk/logical/storage_inmem.go @@ -62,6 +62,26 @@ func (s *InmemStorage) Underlying() *inmem.InmemBackend { return s.underlying.(*inmem.InmemBackend) } +func (s *InmemStorage) FailPut(fail bool) *InmemStorage { + s.Underlying().FailPut(fail) + return s +} + +func (s *InmemStorage) FailGet(fail bool) *InmemStorage { + s.Underlying().FailGet(fail) + return s +} + +func (s *InmemStorage) FailDelete(fail bool) *InmemStorage { + s.Underlying().FailDelete(fail) + return s +} + +func (s *InmemStorage) FailList(fail bool) *InmemStorage { + s.Underlying().FailList(fail) + return s +} + func (s *InmemStorage) init() { s.underlying, _ = inmem.NewInmem(nil, nil) } diff --git a/sdk/logical/system_view.go b/sdk/logical/system_view.go index 2c5d9c3bed..41f82f36c3 100644 --- a/sdk/logical/system_view.go +++ b/sdk/logical/system_view.go @@ -3,6 +3,8 @@ package logical import ( "context" "errors" + "fmt" + "io" "time" "github.com/hashicorp/vault/sdk/helper/consts" @@ -68,6 +70,15 @@ type SystemView interface { // PluginEnv returns Vault environment information used by plugins PluginEnv(context.Context) (*PluginEnvironment, error) + + // GeneratePasswordFromPolicy generates a password from the policy referenced. + // If the policy does not exist, this will return an error. + GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) +} + +type PasswordPolicy interface { + // Generate a random password + Generate(context.Context, io.Reader) (string, error) } type ExtendedSystemView interface { @@ -90,6 +101,7 @@ type StaticSystemView struct { Features license.Features VaultVersion string PluginEnvironment *PluginEnvironment + PasswordPolicies map[string]PasswordPolicy } type noopAuditor struct{} @@ -165,3 +177,20 @@ func (d StaticSystemView) HasFeature(feature license.Features) bool { func (d StaticSystemView) PluginEnv(_ context.Context) (*PluginEnvironment, error) { return d.PluginEnvironment, nil } + +func (d StaticSystemView) GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) { + select { + case <-ctx.Done(): + return "", fmt.Errorf("context timed out") + default: + } + + if d.PasswordPolicies == nil { + return "", fmt.Errorf("password policy not found") + } + policy, exists := d.PasswordPolicies[policyName] + if !exists { + return "", fmt.Errorf("password policy not found") + } + return policy.Generate(ctx, nil) +} diff --git a/sdk/plugin/grpc_system.go b/sdk/plugin/grpc_system.go index 6e27c9b963..ead85aefeb 100644 --- a/sdk/plugin/grpc_system.go +++ b/sdk/plugin/grpc_system.go @@ -14,6 +14,8 @@ import ( "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/plugin/pb" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func newGRPCSystemView(conn *grpc.ClientConn) *gRPCSystemViewClient { @@ -161,6 +163,17 @@ func (s *gRPCSystemViewClient) PluginEnv(ctx context.Context) (*logical.PluginEn return reply.PluginEnvironment, nil } +func (s *gRPCSystemViewClient) GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) { + req := &pb.GeneratePasswordFromPolicyRequest{ + PolicyName: policyName, + } + resp, err := s.client.GeneratePasswordFromPolicy(ctx, req) + if err != nil { + return "", err + } + return resp.Password, nil +} + type gRPCSystemViewServer struct { impl logical.SystemView } @@ -274,3 +287,20 @@ func (s *gRPCSystemViewServer) PluginEnv(ctx context.Context, _ *pb.Empty) (*pb. PluginEnvironment: pluginEnv, }, nil } + +func (s *gRPCSystemViewServer) GeneratePasswordFromPolicy(ctx context.Context, req *pb.GeneratePasswordFromPolicyRequest) (*pb.GeneratePasswordFromPolicyReply, error) { + policyName := req.PolicyName + if policyName == "" { + return &pb.GeneratePasswordFromPolicyReply{}, status.Errorf(codes.InvalidArgument, "no password policy specified") + } + + password, err := s.impl.GeneratePasswordFromPolicy(ctx, policyName) + if err != nil { + return &pb.GeneratePasswordFromPolicyReply{}, status.Errorf(codes.Internal, "failed to generate password") + } + + resp := &pb.GeneratePasswordFromPolicyReply{ + Password: password, + } + return resp, nil +} diff --git a/sdk/plugin/grpc_system_test.go b/sdk/plugin/grpc_system_test.go index 748cec1d13..cf6c115062 100644 --- a/sdk/plugin/grpc_system_test.go +++ b/sdk/plugin/grpc_system_test.go @@ -2,17 +2,17 @@ package plugin import ( "context" - "testing" - - "google.golang.org/grpc" - "reflect" + "testing" + "time" "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" ) func TestSystem_GRPC_GRPC_impl(t *testing.T) { @@ -239,3 +239,63 @@ func TestSystem_GRPC_pluginEnv(t *testing.T) { t.Fatalf("expected: %v, got: %v", expected, actual) } } + +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, + }, + }, + } + sys := &logical.StaticSystemView{ + PasswordPolicies: map[string]logical.PasswordPolicy{ + policyName: logical.PasswordPolicy(expectedPolicy), + }, + } + + client, server := plugin.TestGRPCConn(t, func(s *grpc.Server) { + pb.RegisterSystemViewServer(s, &gRPCSystemViewServer{ + impl: sys, + }) + }) + defer server.Stop() + defer client.Close() + + testSystemView := newGRPCSystemView(client) + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + password, err := testSystemView.GeneratePasswordFromPolicy(ctx, policyName) + if err != nil { + 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) + } + } +} diff --git a/sdk/plugin/pb/backend.pb.go b/sdk/plugin/pb/backend.pb.go index a5e802534e..50e8156748 100644 --- a/sdk/plugin/pb/backend.pb.go +++ b/sdk/plugin/pb/backend.pb.go @@ -3017,6 +3017,100 @@ func (x *PluginEnvReply) GetErr() string { return "" } +type GeneratePasswordFromPolicyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PolicyName string `sentinel:"" protobuf:"bytes,1,opt,name=policy_name,json=policyName,proto3" json:"policy_name,omitempty"` +} + +func (x *GeneratePasswordFromPolicyRequest) Reset() { + *x = GeneratePasswordFromPolicyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeneratePasswordFromPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratePasswordFromPolicyRequest) ProtoMessage() {} + +func (x *GeneratePasswordFromPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratePasswordFromPolicyRequest.ProtoReflect.Descriptor instead. +func (*GeneratePasswordFromPolicyRequest) Descriptor() ([]byte, []int) { + return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{44} +} + +func (x *GeneratePasswordFromPolicyRequest) GetPolicyName() string { + if x != nil { + return x.PolicyName + } + return "" +} + +type GeneratePasswordFromPolicyReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Password string `sentinel:"" protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *GeneratePasswordFromPolicyReply) Reset() { + *x = GeneratePasswordFromPolicyReply{} + if protoimpl.UnsafeEnabled { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeneratePasswordFromPolicyReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratePasswordFromPolicyReply) ProtoMessage() {} + +func (x *GeneratePasswordFromPolicyReply) ProtoReflect() protoreflect.Message { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratePasswordFromPolicyReply.ProtoReflect.Descriptor instead. +func (*GeneratePasswordFromPolicyReply) Descriptor() ([]byte, []int) { + return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{45} +} + +func (x *GeneratePasswordFromPolicyReply) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + type Connection struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3029,7 +3123,7 @@ type Connection struct { func (x *Connection) Reset() { *x = Connection{} if protoimpl.UnsafeEnabled { - mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3042,7 +3136,7 @@ func (x *Connection) String() string { func (*Connection) ProtoMessage() {} func (x *Connection) ProtoReflect() protoreflect.Message { - mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3055,7 +3149,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message { // Deprecated: Use Connection.ProtoReflect.Descriptor instead. func (*Connection) Descriptor() ([]byte, []int) { - return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{44} + return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{46} } func (x *Connection) GetRemoteAddr() string { @@ -3423,89 +3517,104 @@ var file_sdk_plugin_pb_backend_proto_rawDesc = []byte{ 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x11, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x65, 0x72, 0x72, 0x22, 0x2d, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, - 0x64, 0x72, 0x32, 0xa5, 0x03, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x3e, - 0x0a, 0x0d, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, - 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x09, - 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, - 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x12, 0x53, 0x0a, 0x14, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, - 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x1d, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x70, 0x62, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x0d, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x09, - 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x53, 0x65, 0x74, - 0x75, 0x70, 0x12, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x41, 0x72, 0x67, - 0x73, 0x1a, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, - 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, - 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x70, 0x62, - 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xd5, 0x01, 0x0a, 0x07, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, - 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x72, 0x67, 0x73, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x47, 0x65, 0x74, - 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, 0x74, - 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x50, 0x75, 0x74, - 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, 0x74, - 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x50, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x32, 0xc7, 0x04, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x56, 0x69, 0x65, - 0x77, 0x12, 0x2a, 0x0a, 0x0f, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4c, 0x65, 0x61, 0x73, - 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, - 0x0b, 0x4d, 0x61, 0x78, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, - 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x70, 0x62, - 0x2e, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x36, 0x0a, - 0x0f, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x18, 0x2e, 0x70, 0x62, - 0x2e, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x38, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x19, 0x2e, - 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x4d, 0x6c, 0x6f, 0x63, - 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2c, 0x0a, 0x0a, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x2a, 0x0a, 0x09, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x12, 0x09, 0x2e, 0x70, - 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0f, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, - 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, - 0x67, 0x73, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, - 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x2a, 0x5a, 0x28, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x72, 0x22, 0x44, 0x0a, 0x21, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x1f, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, + 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x2d, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x32, 0xa5, 0x03, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, + 0x65, 0x6e, 0x64, 0x12, 0x3e, 0x0a, 0x0d, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, + 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, + 0x74, 0x68, 0x73, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, + 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x53, 0x0a, 0x14, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, + 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, + 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x1d, 0x2e, 0x70, 0x62, + 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x43, 0x6c, + 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x0d, 0x49, + 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x2e, 0x70, + 0x62, 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, + 0x72, 0x67, 0x73, 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x26, + 0x0a, 0x05, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, + 0x75, 0x70, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, + 0x70, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, + 0xd5, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, + 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x47, 0x65, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, + 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x50, 0x75, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x37, + 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, + 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xb1, 0x05, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x56, 0x69, 0x65, 0x77, 0x12, 0x2a, 0x0a, 0x0f, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0b, 0x4d, 0x61, 0x78, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, + 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, + 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x54, 0x61, + 0x69, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x36, 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x38, 0x0a, 0x10, 0x52, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, + 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, + 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x41, 0x72, + 0x67, 0x73, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, + 0x0c, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, + 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4d, 0x6c, + 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x2c, 0x0a, 0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x2e, + 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, + 0x0a, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x70, 0x62, + 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, + 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x09, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, + 0x76, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x70, + 0x62, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x3f, 0x0a, 0x0f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, + 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x68, 0x0a, 0x1a, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, + 0x25, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x2a, 0x5a, 0x28, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3520,82 +3629,84 @@ func file_sdk_plugin_pb_backend_proto_rawDescGZIP() []byte { return file_sdk_plugin_pb_backend_proto_rawDescData } -var file_sdk_plugin_pb_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 50) +var file_sdk_plugin_pb_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 52) var file_sdk_plugin_pb_backend_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: pb.Empty - (*Header)(nil), // 1: pb.Header - (*ProtoError)(nil), // 2: pb.ProtoError - (*Paths)(nil), // 3: pb.Paths - (*Request)(nil), // 4: pb.Request - (*Auth)(nil), // 5: pb.Auth - (*TokenEntry)(nil), // 6: pb.TokenEntry - (*LeaseOptions)(nil), // 7: pb.LeaseOptions - (*Secret)(nil), // 8: pb.Secret - (*Response)(nil), // 9: pb.Response - (*ResponseWrapInfo)(nil), // 10: pb.ResponseWrapInfo - (*RequestWrapInfo)(nil), // 11: pb.RequestWrapInfo - (*HandleRequestArgs)(nil), // 12: pb.HandleRequestArgs - (*HandleRequestReply)(nil), // 13: pb.HandleRequestReply - (*InitializeArgs)(nil), // 14: pb.InitializeArgs - (*InitializeReply)(nil), // 15: pb.InitializeReply - (*SpecialPathsReply)(nil), // 16: pb.SpecialPathsReply - (*HandleExistenceCheckArgs)(nil), // 17: pb.HandleExistenceCheckArgs - (*HandleExistenceCheckReply)(nil), // 18: pb.HandleExistenceCheckReply - (*SetupArgs)(nil), // 19: pb.SetupArgs - (*SetupReply)(nil), // 20: pb.SetupReply - (*TypeReply)(nil), // 21: pb.TypeReply - (*InvalidateKeyArgs)(nil), // 22: pb.InvalidateKeyArgs - (*StorageEntry)(nil), // 23: pb.StorageEntry - (*StorageListArgs)(nil), // 24: pb.StorageListArgs - (*StorageListReply)(nil), // 25: pb.StorageListReply - (*StorageGetArgs)(nil), // 26: pb.StorageGetArgs - (*StorageGetReply)(nil), // 27: pb.StorageGetReply - (*StoragePutArgs)(nil), // 28: pb.StoragePutArgs - (*StoragePutReply)(nil), // 29: pb.StoragePutReply - (*StorageDeleteArgs)(nil), // 30: pb.StorageDeleteArgs - (*StorageDeleteReply)(nil), // 31: pb.StorageDeleteReply - (*TTLReply)(nil), // 32: pb.TTLReply - (*TaintedReply)(nil), // 33: pb.TaintedReply - (*CachingDisabledReply)(nil), // 34: pb.CachingDisabledReply - (*ReplicationStateReply)(nil), // 35: pb.ReplicationStateReply - (*ResponseWrapDataArgs)(nil), // 36: pb.ResponseWrapDataArgs - (*ResponseWrapDataReply)(nil), // 37: pb.ResponseWrapDataReply - (*MlockEnabledReply)(nil), // 38: pb.MlockEnabledReply - (*LocalMountReply)(nil), // 39: pb.LocalMountReply - (*EntityInfoArgs)(nil), // 40: pb.EntityInfoArgs - (*EntityInfoReply)(nil), // 41: pb.EntityInfoReply - (*GroupsForEntityReply)(nil), // 42: pb.GroupsForEntityReply - (*PluginEnvReply)(nil), // 43: pb.PluginEnvReply - (*Connection)(nil), // 44: pb.Connection - nil, // 45: pb.Request.HeadersEntry - nil, // 46: pb.Auth.MetadataEntry - nil, // 47: pb.TokenEntry.MetaEntry - nil, // 48: pb.Response.HeadersEntry - nil, // 49: pb.SetupArgs.ConfigEntry - (*logical.Alias)(nil), // 50: logical.Alias - (*timestamp.Timestamp)(nil), // 51: google.protobuf.Timestamp - (*logical.Entity)(nil), // 52: logical.Entity - (*logical.Group)(nil), // 53: logical.Group - (*logical.PluginEnvironment)(nil), // 54: logical.PluginEnvironment + (*Empty)(nil), // 0: pb.Empty + (*Header)(nil), // 1: pb.Header + (*ProtoError)(nil), // 2: pb.ProtoError + (*Paths)(nil), // 3: pb.Paths + (*Request)(nil), // 4: pb.Request + (*Auth)(nil), // 5: pb.Auth + (*TokenEntry)(nil), // 6: pb.TokenEntry + (*LeaseOptions)(nil), // 7: pb.LeaseOptions + (*Secret)(nil), // 8: pb.Secret + (*Response)(nil), // 9: pb.Response + (*ResponseWrapInfo)(nil), // 10: pb.ResponseWrapInfo + (*RequestWrapInfo)(nil), // 11: pb.RequestWrapInfo + (*HandleRequestArgs)(nil), // 12: pb.HandleRequestArgs + (*HandleRequestReply)(nil), // 13: pb.HandleRequestReply + (*InitializeArgs)(nil), // 14: pb.InitializeArgs + (*InitializeReply)(nil), // 15: pb.InitializeReply + (*SpecialPathsReply)(nil), // 16: pb.SpecialPathsReply + (*HandleExistenceCheckArgs)(nil), // 17: pb.HandleExistenceCheckArgs + (*HandleExistenceCheckReply)(nil), // 18: pb.HandleExistenceCheckReply + (*SetupArgs)(nil), // 19: pb.SetupArgs + (*SetupReply)(nil), // 20: pb.SetupReply + (*TypeReply)(nil), // 21: pb.TypeReply + (*InvalidateKeyArgs)(nil), // 22: pb.InvalidateKeyArgs + (*StorageEntry)(nil), // 23: pb.StorageEntry + (*StorageListArgs)(nil), // 24: pb.StorageListArgs + (*StorageListReply)(nil), // 25: pb.StorageListReply + (*StorageGetArgs)(nil), // 26: pb.StorageGetArgs + (*StorageGetReply)(nil), // 27: pb.StorageGetReply + (*StoragePutArgs)(nil), // 28: pb.StoragePutArgs + (*StoragePutReply)(nil), // 29: pb.StoragePutReply + (*StorageDeleteArgs)(nil), // 30: pb.StorageDeleteArgs + (*StorageDeleteReply)(nil), // 31: pb.StorageDeleteReply + (*TTLReply)(nil), // 32: pb.TTLReply + (*TaintedReply)(nil), // 33: pb.TaintedReply + (*CachingDisabledReply)(nil), // 34: pb.CachingDisabledReply + (*ReplicationStateReply)(nil), // 35: pb.ReplicationStateReply + (*ResponseWrapDataArgs)(nil), // 36: pb.ResponseWrapDataArgs + (*ResponseWrapDataReply)(nil), // 37: pb.ResponseWrapDataReply + (*MlockEnabledReply)(nil), // 38: pb.MlockEnabledReply + (*LocalMountReply)(nil), // 39: pb.LocalMountReply + (*EntityInfoArgs)(nil), // 40: pb.EntityInfoArgs + (*EntityInfoReply)(nil), // 41: pb.EntityInfoReply + (*GroupsForEntityReply)(nil), // 42: pb.GroupsForEntityReply + (*PluginEnvReply)(nil), // 43: pb.PluginEnvReply + (*GeneratePasswordFromPolicyRequest)(nil), // 44: pb.GeneratePasswordFromPolicyRequest + (*GeneratePasswordFromPolicyReply)(nil), // 45: pb.GeneratePasswordFromPolicyReply + (*Connection)(nil), // 46: pb.Connection + nil, // 47: pb.Request.HeadersEntry + nil, // 48: pb.Auth.MetadataEntry + nil, // 49: pb.TokenEntry.MetaEntry + nil, // 50: pb.Response.HeadersEntry + nil, // 51: pb.SetupArgs.ConfigEntry + (*logical.Alias)(nil), // 52: logical.Alias + (*timestamp.Timestamp)(nil), // 53: google.protobuf.Timestamp + (*logical.Entity)(nil), // 54: logical.Entity + (*logical.Group)(nil), // 55: logical.Group + (*logical.PluginEnvironment)(nil), // 56: logical.PluginEnvironment } var file_sdk_plugin_pb_backend_proto_depIDxs = []int32{ 8, // 0: pb.Request.secret:type_name -> pb.Secret 5, // 1: pb.Request.auth:type_name -> pb.Auth - 45, // 2: pb.Request.headers:type_name -> pb.Request.HeadersEntry + 47, // 2: pb.Request.headers:type_name -> pb.Request.HeadersEntry 11, // 3: pb.Request.wrap_info:type_name -> pb.RequestWrapInfo - 44, // 4: pb.Request.connection:type_name -> pb.Connection + 46, // 4: pb.Request.connection:type_name -> pb.Connection 7, // 5: pb.Auth.lease_options:type_name -> pb.LeaseOptions - 46, // 6: pb.Auth.metadata:type_name -> pb.Auth.MetadataEntry - 50, // 7: pb.Auth.alias:type_name -> logical.Alias - 50, // 8: pb.Auth.group_aliases:type_name -> logical.Alias - 47, // 9: pb.TokenEntry.meta:type_name -> pb.TokenEntry.MetaEntry - 51, // 10: pb.LeaseOptions.issue_time:type_name -> google.protobuf.Timestamp + 48, // 6: pb.Auth.metadata:type_name -> pb.Auth.MetadataEntry + 52, // 7: pb.Auth.alias:type_name -> logical.Alias + 52, // 8: pb.Auth.group_aliases:type_name -> logical.Alias + 49, // 9: pb.TokenEntry.meta:type_name -> pb.TokenEntry.MetaEntry + 53, // 10: pb.LeaseOptions.issue_time:type_name -> google.protobuf.Timestamp 7, // 11: pb.Secret.lease_options:type_name -> pb.LeaseOptions 8, // 12: pb.Response.secret:type_name -> pb.Secret 5, // 13: pb.Response.auth:type_name -> pb.Auth 10, // 14: pb.Response.wrap_info:type_name -> pb.ResponseWrapInfo - 48, // 15: pb.Response.headers:type_name -> pb.Response.HeadersEntry - 51, // 16: pb.ResponseWrapInfo.creation_time:type_name -> google.protobuf.Timestamp + 50, // 15: pb.Response.headers:type_name -> pb.Response.HeadersEntry + 53, // 16: pb.ResponseWrapInfo.creation_time:type_name -> google.protobuf.Timestamp 4, // 17: pb.HandleRequestArgs.request:type_name -> pb.Request 9, // 18: pb.HandleRequestReply.response:type_name -> pb.Response 2, // 19: pb.HandleRequestReply.err:type_name -> pb.ProtoError @@ -3603,13 +3714,13 @@ var file_sdk_plugin_pb_backend_proto_depIDxs = []int32{ 3, // 21: pb.SpecialPathsReply.paths:type_name -> pb.Paths 4, // 22: pb.HandleExistenceCheckArgs.request:type_name -> pb.Request 2, // 23: pb.HandleExistenceCheckReply.err:type_name -> pb.ProtoError - 49, // 24: pb.SetupArgs.Config:type_name -> pb.SetupArgs.ConfigEntry + 51, // 24: pb.SetupArgs.Config:type_name -> pb.SetupArgs.ConfigEntry 23, // 25: pb.StorageGetReply.entry:type_name -> pb.StorageEntry 23, // 26: pb.StoragePutArgs.entry:type_name -> pb.StorageEntry 10, // 27: pb.ResponseWrapDataReply.wrap_info:type_name -> pb.ResponseWrapInfo - 52, // 28: pb.EntityInfoReply.entity:type_name -> logical.Entity - 53, // 29: pb.GroupsForEntityReply.groups:type_name -> logical.Group - 54, // 30: pb.PluginEnvReply.plugin_environment:type_name -> logical.PluginEnvironment + 54, // 28: pb.EntityInfoReply.entity:type_name -> logical.Entity + 55, // 29: pb.GroupsForEntityReply.groups:type_name -> logical.Group + 56, // 30: pb.PluginEnvReply.plugin_environment:type_name -> logical.PluginEnvironment 1, // 31: pb.Request.HeadersEntry.value:type_name -> pb.Header 1, // 32: pb.Response.HeadersEntry.value:type_name -> pb.Header 12, // 33: pb.Backend.HandleRequest:input_type -> pb.HandleRequestArgs @@ -3635,31 +3746,33 @@ var file_sdk_plugin_pb_backend_proto_depIDxs = []int32{ 40, // 53: pb.SystemView.EntityInfo:input_type -> pb.EntityInfoArgs 0, // 54: pb.SystemView.PluginEnv:input_type -> pb.Empty 40, // 55: pb.SystemView.GroupsForEntity:input_type -> pb.EntityInfoArgs - 13, // 56: pb.Backend.HandleRequest:output_type -> pb.HandleRequestReply - 16, // 57: pb.Backend.SpecialPaths:output_type -> pb.SpecialPathsReply - 18, // 58: pb.Backend.HandleExistenceCheck:output_type -> pb.HandleExistenceCheckReply - 0, // 59: pb.Backend.Cleanup:output_type -> pb.Empty - 0, // 60: pb.Backend.InvalidateKey:output_type -> pb.Empty - 20, // 61: pb.Backend.Setup:output_type -> pb.SetupReply - 15, // 62: pb.Backend.Initialize:output_type -> pb.InitializeReply - 21, // 63: pb.Backend.Type:output_type -> pb.TypeReply - 25, // 64: pb.Storage.List:output_type -> pb.StorageListReply - 27, // 65: pb.Storage.Get:output_type -> pb.StorageGetReply - 29, // 66: pb.Storage.Put:output_type -> pb.StoragePutReply - 31, // 67: pb.Storage.Delete:output_type -> pb.StorageDeleteReply - 32, // 68: pb.SystemView.DefaultLeaseTTL:output_type -> pb.TTLReply - 32, // 69: pb.SystemView.MaxLeaseTTL:output_type -> pb.TTLReply - 33, // 70: pb.SystemView.Tainted:output_type -> pb.TaintedReply - 34, // 71: pb.SystemView.CachingDisabled:output_type -> pb.CachingDisabledReply - 35, // 72: pb.SystemView.ReplicationState:output_type -> pb.ReplicationStateReply - 37, // 73: pb.SystemView.ResponseWrapData:output_type -> pb.ResponseWrapDataReply - 38, // 74: pb.SystemView.MlockEnabled:output_type -> pb.MlockEnabledReply - 39, // 75: pb.SystemView.LocalMount:output_type -> pb.LocalMountReply - 41, // 76: pb.SystemView.EntityInfo:output_type -> pb.EntityInfoReply - 43, // 77: pb.SystemView.PluginEnv:output_type -> pb.PluginEnvReply - 42, // 78: pb.SystemView.GroupsForEntity:output_type -> pb.GroupsForEntityReply - 56, // [56:79] is the sub-list for method output_type - 33, // [33:56] is the sub-list for method input_type + 44, // 56: pb.SystemView.GeneratePasswordFromPolicy:input_type -> pb.GeneratePasswordFromPolicyRequest + 13, // 57: pb.Backend.HandleRequest:output_type -> pb.HandleRequestReply + 16, // 58: pb.Backend.SpecialPaths:output_type -> pb.SpecialPathsReply + 18, // 59: pb.Backend.HandleExistenceCheck:output_type -> pb.HandleExistenceCheckReply + 0, // 60: pb.Backend.Cleanup:output_type -> pb.Empty + 0, // 61: pb.Backend.InvalidateKey:output_type -> pb.Empty + 20, // 62: pb.Backend.Setup:output_type -> pb.SetupReply + 15, // 63: pb.Backend.Initialize:output_type -> pb.InitializeReply + 21, // 64: pb.Backend.Type:output_type -> pb.TypeReply + 25, // 65: pb.Storage.List:output_type -> pb.StorageListReply + 27, // 66: pb.Storage.Get:output_type -> pb.StorageGetReply + 29, // 67: pb.Storage.Put:output_type -> pb.StoragePutReply + 31, // 68: pb.Storage.Delete:output_type -> pb.StorageDeleteReply + 32, // 69: pb.SystemView.DefaultLeaseTTL:output_type -> pb.TTLReply + 32, // 70: pb.SystemView.MaxLeaseTTL:output_type -> pb.TTLReply + 33, // 71: pb.SystemView.Tainted:output_type -> pb.TaintedReply + 34, // 72: pb.SystemView.CachingDisabled:output_type -> pb.CachingDisabledReply + 35, // 73: pb.SystemView.ReplicationState:output_type -> pb.ReplicationStateReply + 37, // 74: pb.SystemView.ResponseWrapData:output_type -> pb.ResponseWrapDataReply + 38, // 75: pb.SystemView.MlockEnabled:output_type -> pb.MlockEnabledReply + 39, // 76: pb.SystemView.LocalMount:output_type -> pb.LocalMountReply + 41, // 77: pb.SystemView.EntityInfo:output_type -> pb.EntityInfoReply + 43, // 78: pb.SystemView.PluginEnv:output_type -> pb.PluginEnvReply + 42, // 79: pb.SystemView.GroupsForEntity:output_type -> pb.GroupsForEntityReply + 45, // 80: pb.SystemView.GeneratePasswordFromPolicy:output_type -> pb.GeneratePasswordFromPolicyReply + 57, // [57:81] is the sub-list for method output_type + 33, // [33:57] is the sub-list for method input_type 33, // [33:33] is the sub-list for extension type_name 33, // [33:33] is the sub-list for extension extendee 0, // [0:33] is the sub-list for field type_name @@ -4200,6 +4313,30 @@ func file_sdk_plugin_pb_backend_proto_init() { } } file_sdk_plugin_pb_backend_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeneratePasswordFromPolicyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sdk_plugin_pb_backend_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeneratePasswordFromPolicyReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sdk_plugin_pb_backend_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Connection); i { case 0: return &v.state @@ -4218,7 +4355,7 @@ func file_sdk_plugin_pb_backend_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_sdk_plugin_pb_backend_proto_rawDesc, NumEnums: 0, - NumMessages: 50, + NumMessages: 52, NumExtensions: 0, NumServices: 3, }, @@ -4838,6 +4975,8 @@ type SystemViewClient interface { // GroupsForEntity returns the group membership information for the given // entity id GroupsForEntity(ctx context.Context, in *EntityInfoArgs, opts ...grpc.CallOption) (*GroupsForEntityReply, error) + // GeneratePasswordFromPolicy generates a password from an existing password policy + GeneratePasswordFromPolicy(ctx context.Context, in *GeneratePasswordFromPolicyRequest, opts ...grpc.CallOption) (*GeneratePasswordFromPolicyReply, error) } type systemViewClient struct { @@ -4947,6 +5086,15 @@ func (c *systemViewClient) GroupsForEntity(ctx context.Context, in *EntityInfoAr return out, nil } +func (c *systemViewClient) GeneratePasswordFromPolicy(ctx context.Context, in *GeneratePasswordFromPolicyRequest, opts ...grpc.CallOption) (*GeneratePasswordFromPolicyReply, error) { + out := new(GeneratePasswordFromPolicyReply) + err := c.cc.Invoke(ctx, "/pb.SystemView/GeneratePasswordFromPolicy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SystemViewServer is the server API for SystemView service. type SystemViewServer interface { // DefaultLeaseTTL returns the default lease TTL set in Vault configuration @@ -4985,6 +5133,8 @@ type SystemViewServer interface { // GroupsForEntity returns the group membership information for the given // entity id GroupsForEntity(context.Context, *EntityInfoArgs) (*GroupsForEntityReply, error) + // GeneratePasswordFromPolicy generates a password from an existing password policy + GeneratePasswordFromPolicy(context.Context, *GeneratePasswordFromPolicyRequest) (*GeneratePasswordFromPolicyReply, error) } // UnimplementedSystemViewServer can be embedded to have forward compatible implementations. @@ -5024,6 +5174,9 @@ func (*UnimplementedSystemViewServer) PluginEnv(context.Context, *Empty) (*Plugi func (*UnimplementedSystemViewServer) GroupsForEntity(context.Context, *EntityInfoArgs) (*GroupsForEntityReply, error) { return nil, status.Errorf(codes.Unimplemented, "method GroupsForEntity not implemented") } +func (*UnimplementedSystemViewServer) GeneratePasswordFromPolicy(context.Context, *GeneratePasswordFromPolicyRequest) (*GeneratePasswordFromPolicyReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GeneratePasswordFromPolicy not implemented") +} func RegisterSystemViewServer(s *grpc.Server, srv SystemViewServer) { s.RegisterService(&_SystemView_serviceDesc, srv) @@ -5227,6 +5380,24 @@ func _SystemView_GroupsForEntity_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _SystemView_GeneratePasswordFromPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GeneratePasswordFromPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemViewServer).GeneratePasswordFromPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.SystemView/GeneratePasswordFromPolicy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemViewServer).GeneratePasswordFromPolicy(ctx, req.(*GeneratePasswordFromPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _SystemView_serviceDesc = grpc.ServiceDesc{ ServiceName: "pb.SystemView", HandlerType: (*SystemViewServer)(nil), @@ -5275,6 +5446,10 @@ var _SystemView_serviceDesc = grpc.ServiceDesc{ MethodName: "GroupsForEntity", Handler: _SystemView_GroupsForEntity_Handler, }, + { + MethodName: "GeneratePasswordFromPolicy", + Handler: _SystemView_GeneratePasswordFromPolicy_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "sdk/plugin/pb/backend.proto", diff --git a/sdk/plugin/pb/backend.proto b/sdk/plugin/pb/backend.proto index 7b910ea0f0..e7244df50b 100644 --- a/sdk/plugin/pb/backend.proto +++ b/sdk/plugin/pb/backend.proto @@ -554,12 +554,20 @@ message PluginEnvReply { string err = 2; } +message GeneratePasswordFromPolicyRequest { + string policy_name = 1; +} + +message GeneratePasswordFromPolicyReply { + string password = 1; +} + // SystemView exposes system configuration information in a safe way for plugins // to consume. Plugins should implement the client for this service. service SystemView { // DefaultLeaseTTL returns the default lease TTL set in Vault configuration rpc DefaultLeaseTTL(Empty) returns (TTLReply); - + // MaxLeaseTTL returns the max lease TTL set in Vault configuration; backend // authors should take care not to issue credentials that last longer than // this value, as Vault will revoke them @@ -603,6 +611,9 @@ service SystemView { // GroupsForEntity returns the group membership information for the given // entity id rpc GroupsForEntity(EntityInfoArgs) returns (GroupsForEntityReply); + + // GeneratePasswordFromPolicy generates a password from an existing password policy + rpc GeneratePasswordFromPolicy(GeneratePasswordFromPolicyRequest) returns (GeneratePasswordFromPolicyReply); } message Connection { diff --git a/vault/dynamic_system_view.go b/vault/dynamic_system_view.go index 652ed6e1c0..4b5c14371f 100644 --- a/vault/dynamic_system_view.go +++ b/vault/dynamic_system_view.go @@ -6,12 +6,12 @@ import ( "time" "github.com/hashicorp/errwrap" - "github.com/hashicorp/vault/helper/identity" "github.com/hashicorp/vault/helper/namespace" "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" @@ -327,3 +327,32 @@ func (d dynamicSystemView) PluginEnv(_ context.Context) (*logical.PluginEnvironm VaultVersion: version.GetVersion().Version, }, nil } + +func (d dynamicSystemView) GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) { + if policyName == "" { + return "", fmt.Errorf("missing password policy name") + } + + // Ensure there's a timeout on the context of some sort + if _, hasTimeout := ctx.Deadline(); !hasTimeout { + var cancel func() + ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + } + + policyCfg, err := retrievePasswordPolicy(ctx, d.core.systemBarrierView, policyName) + if err != nil { + return "", fmt.Errorf("failed to retrieve password policy: %w", err) + } + + if policyCfg == nil { + return "", fmt.Errorf("no password policy found") + } + + passPolicy, err := random.ParsePolicy(policyCfg.HCLPolicy) + if err != nil { + return "", fmt.Errorf("stored password policy is invalid: %w", err) + } + + return passPolicy.Generate(ctx, nil) +} diff --git a/vault/dynamic_system_view_test.go b/vault/dynamic_system_view_test.go index 6820282ee1..b5a90f1d76 100644 --- a/vault/dynamic_system_view_test.go +++ b/vault/dynamic_system_view_test.go @@ -1,7 +1,13 @@ package vault import ( + "context" + "encoding/json" + "fmt" + "reflect" + "sort" "testing" + "time" log "github.com/hashicorp/go-hclog" ldapcred "github.com/hashicorp/vault/builtin/credential/ldap" @@ -149,3 +155,147 @@ func TestIdentity_BackendTemplating(t *testing.T) { } } } + +func TestDynamicSystemView_GeneratePasswordFromPolicy_successful(t *testing.T) { + policyName := "testpolicy" + rawPolicy := map[string]interface{}{ + "policy": `length = 20 +rule "charset" { + charset = "abcdefghijklmnopqrstuvwxyz" + min_chars = 1 +} +rule "charset" { + charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + min_chars = 1 +} +rule "charset" { + charset = "0123456789" + min_chars = 1 +}`, + } + marshalledPolicy, err := json.Marshal(rawPolicy) + if err != nil { + t.Fatalf("Unable to set up test: unable to marshal raw policy to JSON: %s", err) + } + + testStorage := fakeBarrier{ + getEntry: &logical.StorageEntry{ + Key: getPasswordPolicyKey(policyName), + Value: marshalledPolicy, + }, + } + + dsv := dynamicSystemView{ + core: &Core{ + systemBarrierView: NewBarrierView(testStorage, "sys/"), + }, + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + runeset := map[rune]bool{} + runesFound := []rune{} + + for i := 0; i < 100; i++ { + actual, err := dsv.GeneratePasswordFromPolicy(ctx, policyName) + if err != nil { + t.Fatalf("no error expected, but got: %s", err) + } + for _, r := range actual { + if runeset[r] { + continue + } + runeset[r] = true + runesFound = append(runesFound, r) + } + } + + sort.Sort(runes(runesFound)) + + expectedRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + sort.Sort(runes(expectedRunes)) // Sort it so they can be compared + + if !reflect.DeepEqual(runesFound, expectedRunes) { + t.Fatalf("Didn't find all characters from the charset\nActual : [%s]\nExpected: [%s]", string(runesFound), string(expectedRunes)) + } +} + +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] } + +func TestDynamicSystemView_GeneratePasswordFromPolicy_failed(t *testing.T) { + type testCase struct { + policyName string + getEntry *logical.StorageEntry + getErr error + } + + tests := map[string]testCase{ + "no policy name": { + policyName: "", + }, + "no policy found": { + policyName: "testpolicy", + getEntry: nil, + getErr: nil, + }, + "error retrieving policy": { + policyName: "testpolicy", + getEntry: nil, + getErr: fmt.Errorf("a test error"), + }, + "saved policy is malformed": { + policyName: "testpolicy", + getEntry: &logical.StorageEntry{ + Key: getPasswordPolicyKey("testpolicy"), + Value: []byte(`{"policy":"asdfahsdfasdf"}`), + }, + getErr: nil, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + testStorage := fakeBarrier{ + getEntry: test.getEntry, + getErr: test.getErr, + } + + dsv := dynamicSystemView{ + core: &Core{ + systemBarrierView: NewBarrierView(testStorage, "sys/"), + }, + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + actualPassword, err := dsv.GeneratePasswordFromPolicy(ctx, test.policyName) + if err == nil { + t.Fatalf("err expected, got nil") + } + if actualPassword != "" { + t.Fatalf("no password expected, got %s", actualPassword) + } + }) + } +} + +type fakeBarrier struct { + getEntry *logical.StorageEntry + getErr error +} + +func (b fakeBarrier) Get(context.Context, string) (*logical.StorageEntry, error) { + return b.getEntry, b.getErr +} +func (b fakeBarrier) List(context.Context, string) ([]string, error) { + return nil, fmt.Errorf("not implemented") +} +func (b fakeBarrier) Put(context.Context, *logical.StorageEntry) error { + return fmt.Errorf("not implemented") +} +func (b fakeBarrier) Delete(context.Context, string) error { + return fmt.Errorf("not implemented") +} diff --git a/vault/logical_system.go b/vault/logical_system.go index a38991c214..19e12a3db3 100644 --- a/vault/logical_system.go +++ b/vault/logical_system.go @@ -33,6 +33,7 @@ import ( "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" @@ -2065,6 +2066,183 @@ func (b *SystemBackend) handlePoliciesDelete(policyType PolicyType) framework.Op } } +type passwordPolicyConfig struct { + HCLPolicy string `json:"policy"` +} + +func getPasswordPolicyKey(policyName string) string { + return fmt.Sprintf("password_policy/%s", policyName) +} + +const ( + minPasswordLength = 4 + maxPasswordLength = 100 +) + +// handlePoliciesPasswordSet saves/updates password policies +func (*SystemBackend) handlePoliciesPasswordSet(ctx context.Context, req *logical.Request, data *framework.FieldData) (resp *logical.Response, err error) { + policyName := data.Get("name").(string) + if policyName == "" { + return nil, logical.CodedError(http.StatusBadRequest, "missing policy name") + } + + rawPolicy := data.Get("policy").(string) + if rawPolicy == "" { + return nil, logical.CodedError(http.StatusBadRequest, "missing policy") + } + + // Optionally decode base64 string + decodedPolicy, err := base64.StdEncoding.DecodeString(rawPolicy) + if err == nil { + rawPolicy = string(decodedPolicy) + } + + // Parse the policy to ensure that it's valid + policy, err := random.ParsePolicy(rawPolicy) + if err != nil { + return nil, logical.CodedError(http.StatusBadRequest, fmt.Sprintf("invalid password policy: %s", err)) + } + + if policy.Length > maxPasswordLength || policy.Length < minPasswordLength { + return nil, logical.CodedError(http.StatusBadRequest, + fmt.Sprintf("passwords must be between %d and %d characters", minPasswordLength, maxPasswordLength)) + } + + // Generate some passwords to ensure that we're confident that the policy isn't impossible + timeout := 1 * time.Second + genCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + attempts := 10 + failed := 0 + for i := 0; i < attempts; i++ { + _, err = policy.Generate(genCtx, nil) + if err != nil { + failed++ + } + } + + if failed == attempts { + return nil, logical.CodedError(http.StatusBadRequest, + fmt.Sprintf("unable to generate password from provided policy in %s: are the rules impossible?", timeout)) + } + + if failed > 0 { + return nil, logical.CodedError(http.StatusBadRequest, + fmt.Sprintf("failed to generate passwords %d times out of %d attempts in %s - is the policy too restrictive?", failed, attempts, timeout)) + } + + cfg := passwordPolicyConfig{ + HCLPolicy: rawPolicy, + } + entry, err := logical.StorageEntryJSON(getPasswordPolicyKey(policyName), cfg) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, fmt.Sprintf("unable to save password policy: %s", err)) + } + + err = req.Storage.Put(ctx, entry) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, + fmt.Sprintf("failed to save policy to storage backend: %s", err)) + } + + return logical.RespondWithStatusCode(nil, req, http.StatusNoContent) +} + +// handlePoliciesPasswordGet retrieves a password policy if it exists +func (*SystemBackend) handlePoliciesPasswordGet(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + policyName := data.Get("name").(string) + if policyName == "" { + return nil, logical.CodedError(http.StatusBadRequest, "missing policy name") + } + + cfg, err := retrievePasswordPolicy(ctx, req.Storage, policyName) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, "failed to retrieve password policy") + } + if cfg == nil { + return nil, logical.CodedError(http.StatusNotFound, "policy does not exist") + } + + resp := &logical.Response{ + Data: map[string]interface{}{ + "policy": cfg.HCLPolicy, + }, + } + + return resp, nil +} + +// retrievePasswordPolicy retrieves a password policy from the logical storage +func retrievePasswordPolicy(ctx context.Context, storage logical.Storage, policyName string) (policyCfg *passwordPolicyConfig, err error) { + entry, err := storage.Get(ctx, getPasswordPolicyKey(policyName)) + if err != nil { + return nil, err + } + if entry == nil { + return nil, nil + } + + policyCfg = &passwordPolicyConfig{} + err = json.Unmarshal(entry.Value, &policyCfg) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal stored data: %w", err) + } + + return policyCfg, nil +} + +// handlePoliciesPasswordDelete deletes a password policy if it exists +func (*SystemBackend) handlePoliciesPasswordDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + policyName := data.Get("name").(string) + if policyName == "" { + return nil, logical.CodedError(http.StatusBadRequest, "missing policy name") + } + + err := req.Storage.Delete(ctx, getPasswordPolicyKey(policyName)) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, + fmt.Sprintf("failed to delete password policy: %s", err)) + } + + return nil, nil +} + +// handlePoliciesPasswordGenerate generates a password from the specified password policy +func (*SystemBackend) handlePoliciesPasswordGenerate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + policyName := data.Get("name").(string) + if policyName == "" { + return nil, logical.CodedError(http.StatusBadRequest, "missing policy name") + } + + cfg, err := retrievePasswordPolicy(ctx, req.Storage, policyName) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, "failed to retrieve password policy") + } + if cfg == nil { + return nil, logical.CodedError(http.StatusNotFound, "policy does not exist") + } + + policy, err := random.ParsePolicy(cfg.HCLPolicy) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, + "stored password policy configuration failed to parse") + } + + password, err := policy.Generate(ctx, nil) + if err != nil { + return nil, logical.CodedError(http.StatusInternalServerError, + fmt.Sprintf("failed to generate password from policy: %s", err)) + } + + resp := &logical.Response{ + Data: map[string]interface{}{ + "password": password, + }, + } + return resp, nil +} + // handleAuditTable handles the "audit" endpoint to provide the audit table func (b *SystemBackend) handleAuditTable(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { b.Core.auditLock.RLock() @@ -3836,6 +4014,11 @@ or delete a policy. "", }, + "password-policy-name": { + `The name of the password policy.`, + "", + }, + "audit-hash": { "The hash of the given string via the given audit backend", "", diff --git a/vault/logical_system_paths.go b/vault/logical_system_paths.go index 2bf4495377..eb36e4f37f 100644 --- a/vault/logical_system_paths.go +++ b/vault/logical_system_paths.go @@ -1463,6 +1463,61 @@ func (b *SystemBackend) policyPaths() []*framework.Path { HelpSynopsis: strings.TrimSpace(sysHelp["policy"][0]), HelpDescription: strings.TrimSpace(sysHelp["policy"][1]), }, + + { + Pattern: "policies/password/(?P.+)/generate$", + + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The name of the password policy.", + }, + }, + + Operations: map[logical.Operation]framework.OperationHandler{ + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handlePoliciesPasswordGenerate, + Summary: "Generate a password from an existing password policy.", + }, + }, + + HelpSynopsis: "Generate a password from an existing password policy.", + HelpDescription: "Generate a password from an existing password policy.", + }, + + { + Pattern: "policies/password/(?P.+)$", + + Fields: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The name of the password policy.", + }, + "policy": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The password policy", + }, + }, + + Operations: map[logical.Operation]framework.OperationHandler{ + logical.UpdateOperation: &framework.PathOperation{ + Callback: b.handlePoliciesPasswordSet, + Summary: "Add a new or update an existing password policy.", + }, + logical.ReadOperation: &framework.PathOperation{ + Callback: b.handlePoliciesPasswordGet, + Summary: "Retrieve an existing password policy.", + }, + logical.DeleteOperation: &framework.PathOperation{ + Callback: b.handlePoliciesPasswordDelete, + Summary: "Delete a password policy.", + }, + }, + + HelpSynopsis: "Read, Modify, or Delete a password policy.", + HelpDescription: "Read the rules of an existing password policy, create or update " + + "the rules of a password policy, or delete a password policy.", + }, } } diff --git a/vault/logical_system_test.go b/vault/logical_system_test.go index 30b1dffebe..7f13ba1f68 100644 --- a/vault/logical_system_test.go +++ b/vault/logical_system_test.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "fmt" "io/ioutil" + "net/http" "os" "path/filepath" "reflect" @@ -24,6 +25,7 @@ import ( "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" @@ -2730,3 +2732,776 @@ func TestSystemBackend_PathWildcardPreflight(t *testing.T) { t.Fatalf("err: %v", err) } } + +func TestHandlePoliciesPasswordSet(t *testing.T) { + type testCase struct { + inputData *framework.FieldData + + storage *logical.InmemStorage + + expectedResp *logical.Response + expectErr bool + expectedStore map[string]*logical.StorageEntry + } + + tests := map[string]testCase{ + "missing policy name": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "policy": `length = 20 + rule "charset" { + charset="abcdefghij" + }`, + }), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "missing policy": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "garbage policy": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + "policy": "hasdukfhiuashdfoiasjdf", + }), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "storage failure": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + "policy": "length = 20\n" + + "rule \"charset\" {\n" + + " charset=\"abcdefghij\"\n" + + "}", + }), + + storage: new(logical.InmemStorage).FailPut(true), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "impossible policy": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + "policy": "length = 20\n" + + "rule \"charset\" {\n" + + " charset=\"a\"\n" + + " min-chars = 30\n" + + "}", + }), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "not base64 encoded": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + "policy": "length = 20\n" + + "rule \"charset\" {\n" + + " charset=\"abcdefghij\"\n" + + "}", + }), + + storage: new(logical.InmemStorage), + + expectedResp: &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPContentType: "application/json", + logical.HTTPStatusCode: http.StatusNoContent, + }, + }, + expectErr: false, + expectedStore: makeStorageMap(storageEntry(t, "testpolicy", "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}")), + }, + "base64 encoded": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + "policy": base64Encode( + "length = 20\n" + + "rule \"charset\" {\n" + + " charset=\"abcdefghij\"\n" + + "}"), + }), + + storage: new(logical.InmemStorage), + + expectedResp: &logical.Response{ + Data: map[string]interface{}{ + logical.HTTPContentType: "application/json", + logical.HTTPStatusCode: http.StatusNoContent, + }, + }, + expectErr: false, + expectedStore: makeStorageMap(storageEntry(t, "testpolicy", + "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}")), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + req := &logical.Request{ + Storage: test.storage, + } + + b := &SystemBackend{} + + actualResp, err := b.handlePoliciesPasswordSet(ctx, req, test.inputData) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + if !reflect.DeepEqual(actualResp, test.expectedResp) { + t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp) + } + + actualStore := LogicalToMap(t, ctx, test.storage) + if !reflect.DeepEqual(actualStore, test.expectedStore) { + t.Fatalf("Actual: %#v\nActual: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore)) + } + }) + } +} + +func TestHandlePoliciesPasswordGet(t *testing.T) { + type testCase struct { + inputData *framework.FieldData + + storage *logical.InmemStorage + + expectedResp *logical.Response + expectErr bool + expectedStore map[string]*logical.StorageEntry + } + + tests := map[string]testCase{ + "missing policy name": { + inputData: passwordPoliciesFieldData(map[string]interface{}{}), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "storage error": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: new(logical.InmemStorage).FailGet(true), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "missing value": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "good value": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: makeStorage(t, storageEntry(t, "testpolicy", + "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}")), + + expectedResp: &logical.Response{ + Data: map[string]interface{}{ + "policy": "length = 20\n" + + "rule \"charset\" {\n" + + " charset=\"abcdefghij\"\n" + + "}", + }, + }, + expectErr: false, + expectedStore: makeStorageMap(storageEntry(t, "testpolicy", + "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}")), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + + req := &logical.Request{ + Storage: test.storage, + } + + b := &SystemBackend{} + + actualResp, err := b.handlePoliciesPasswordGet(ctx, req, test.inputData) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + if !reflect.DeepEqual(actualResp, test.expectedResp) { + t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp) + } + + actualStore := LogicalToMap(t, ctx, test.storage) + if !reflect.DeepEqual(actualStore, test.expectedStore) { + t.Fatalf("Actual: %#v\nActual: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore)) + } + }) + } +} + +func TestHandlePoliciesPasswordDelete(t *testing.T) { + type testCase struct { + inputData *framework.FieldData + + storage logical.Storage + + expectedResp *logical.Response + expectErr bool + expectedStore map[string]*logical.StorageEntry + } + + tests := map[string]testCase{ + "missing policy name": { + inputData: passwordPoliciesFieldData(map[string]interface{}{}), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "storage failure": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: new(logical.InmemStorage).FailDelete(true), + + expectedResp: nil, + expectErr: true, + expectedStore: map[string]*logical.StorageEntry{}, + }, + "successful delete": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: makeStorage(t, + &logical.StorageEntry{ + Key: getPasswordPolicyKey("testpolicy"), + Value: toJson(t, + passwordPolicyConfig{ + HCLPolicy: "length = 18\n" + + "rule \"charset\" {\n" + + " charset=\"ABCDEFGHIJ\"\n" + + "}", + }), + }, + &logical.StorageEntry{ + Key: getPasswordPolicyKey("unrelated_policy"), + Value: toJson(t, + passwordPolicyConfig{ + HCLPolicy: "length = 20\n" + + "rule \"charset\" {\n" + + " charset=\"abcdefghij\"\n" + + "}", + }), + }, + ), + + expectedResp: nil, + expectErr: false, + expectedStore: makeStorageMap(storageEntry(t, "unrelated_policy", + "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}")), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + + req := &logical.Request{ + Storage: test.storage, + } + + b := &SystemBackend{} + + actualResp, err := b.handlePoliciesPasswordDelete(ctx, req, test.inputData) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + if !reflect.DeepEqual(actualResp, test.expectedResp) { + t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp) + } + + actualStore := LogicalToMap(t, ctx, test.storage) + if !reflect.DeepEqual(actualStore, test.expectedStore) { + t.Fatalf("Actual: %#v\nExpected: %#v", dereferenceMap(actualStore), dereferenceMap(test.expectedStore)) + } + }) + } +} + +func TestHandlePoliciesPasswordGenerate(t *testing.T) { + t.Run("errors", func(t *testing.T) { + type testCase struct { + timeout time.Duration + inputData *framework.FieldData + + storage *logical.InmemStorage + + expectedResp *logical.Response + expectErr bool + } + + tests := map[string]testCase{ + "missing policy name": { + inputData: passwordPoliciesFieldData(map[string]interface{}{}), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + }, + "storage failure": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: new(logical.InmemStorage).FailGet(true), + + expectedResp: nil, + expectErr: true, + }, + "policy does not exist": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: new(logical.InmemStorage), + + expectedResp: nil, + expectErr: true, + }, + "policy improperly saved": { + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: makeStorage(t, storageEntry(t, "testpolicy", "badpolicy")), + + expectedResp: nil, + expectErr: true, + }, + "failed to generate": { + timeout: 0 * time.Second, // Timeout immediately + inputData: passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }), + + storage: makeStorage(t, storageEntry(t, "testpolicy", + "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}")), + + expectedResp: nil, + expectErr: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), test.timeout) + defer cancel() + + req := &logical.Request{ + Storage: test.storage, + } + + b := &SystemBackend{} + + actualResp, err := b.handlePoliciesPasswordGenerate(ctx, req, test.inputData) + if test.expectErr && err == nil { + t.Fatalf("err expected, got nil") + } + if !test.expectErr && err != nil { + t.Fatalf("no error expected, got: %s", err) + } + if !reflect.DeepEqual(actualResp, test.expectedResp) { + t.Fatalf("Actual response: %#v\nExpected response: %#v", actualResp, test.expectedResp) + } + }) + } + }) + + t.Run("success", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + policyEntry := storageEntry(t, "testpolicy", + "length = 20\n"+ + "rule \"charset\" {\n"+ + " charset=\"abcdefghij\"\n"+ + "}") + storage := makeStorage(t, policyEntry) + + inputData := passwordPoliciesFieldData(map[string]interface{}{ + "name": "testpolicy", + }) + + expectedResp := &logical.Response{ + Data: map[string]interface{}{ + // Doesn't include the password as that's pulled out and compared separately + }, + } + + // Password assertions + expectedPassLen := 20 + rules := []random.Rule{ + random.CharsetRule{ + Charset: []rune("abcdefghij"), + MinChars: expectedPassLen, + }, + } + + // Run the test a bunch of times to help ensure we don't have flaky behavior + for i := 0; i < 1000; i++ { + req := &logical.Request{ + Storage: storage, + } + + b := &SystemBackend{} + + actualResp, err := b.handlePoliciesPasswordGenerate(ctx, req, inputData) + if err != nil { + t.Fatalf("no error expected, got: %s", err) + } + + assert(t, actualResp != nil, "response is nil") + assert(t, actualResp.Data != nil, "expected data, got nil") + assertHasKey(t, actualResp.Data, "password", "password key not found in data") + assertIsString(t, actualResp.Data["password"], "password key should have a string value") + password := actualResp.Data["password"].(string) + + // Delete the password so the rest of the response can be compared + delete(actualResp.Data, "password") + assert(t, reflect.DeepEqual(actualResp, expectedResp), "Actual response: %#v\nExpected response: %#v", actualResp, expectedResp) + + // Check to make sure the password is correctly formatted + passwordLength := len([]rune(password)) + if passwordLength != expectedPassLen { + t.Fatalf("password is %d characters but should be %d", passwordLength, expectedPassLen) + } + + for _, rule := range rules { + if !rule.Pass([]rune(password)) { + t.Fatalf("password %s does not have the correct characters", password) + } + } + } + }) +} + +func assert(t *testing.T, pass bool, f string, vals ...interface{}) { + t.Helper() + if !pass { + t.Fatalf(f, vals...) + } +} + +func assertHasKey(t *testing.T, m map[string]interface{}, key string, f string, vals ...interface{}) { + t.Helper() + _, exists := m[key] + if !exists { + t.Fatalf(f, vals...) + } +} + +func assertIsString(t *testing.T, val interface{}, f string, vals ...interface{}) { + t.Helper() + _, ok := val.(string) + if !ok { + t.Fatalf(f, vals...) + } +} + +func passwordPoliciesFieldData(raw map[string]interface{}) *framework.FieldData { + return &framework.FieldData{ + Raw: raw, + Schema: map[string]*framework.FieldSchema{ + "name": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The name of the password policy.", + }, + "policy": &framework.FieldSchema{ + Type: framework.TypeString, + Description: "The password policy", + }, + }, + } +} + +func base64Encode(data string) string { + return base64.StdEncoding.EncodeToString([]byte(data)) +} + +func toJson(t *testing.T, val interface{}) []byte { + t.Helper() + + b, err := jsonutil.EncodeJSON(val) + if err != nil { + t.Fatalf("Unable to marshal to JSON: %s", err) + } + return b +} + +func storageEntry(t *testing.T, key string, policy string) *logical.StorageEntry { + return &logical.StorageEntry{ + Key: getPasswordPolicyKey(key), + Value: toJson(t, passwordPolicyConfig{ + HCLPolicy: policy, + }), + } +} + +func makeStorageMap(entries ...*logical.StorageEntry) map[string]*logical.StorageEntry { + m := map[string]*logical.StorageEntry{} + for _, entry := range entries { + m[entry.Key] = entry + } + return m +} + +func dereferenceMap(store map[string]*logical.StorageEntry) map[string]interface{} { + m := map[string]interface{}{} + + for k, v := range store { + m[k] = map[string]string{ + "Key": v.Key, + "Value": string(v.Value), + } + } + return m +} + +type walkFunc func(*logical.StorageEntry) error + +// WalkLogicalStorage applies the provided walkFunc against each entry in the logical storage. +// This operates as a breadth first search. +// TODO: Figure out a place for this to live permanently. This is generic and should be in a helper package somewhere. +// At the time of writing, none of these locations work due to import cycles: +// - vault/helper/testhelpers +// - vault/helper/testhelpers/logical +// - vault/helper/testhelpers/teststorage +func WalkLogicalStorage(ctx context.Context, store logical.Storage, walker walkFunc) (err error) { + if store == nil { + return fmt.Errorf("no storage provided") + } + if walker == nil { + return fmt.Errorf("no walk function provided") + } + + keys, err := store.List(ctx, "") + if err != nil { + return fmt.Errorf("unable to list root keys: %w", err) + } + + // Non-recursive breadth-first search through all keys + for i := 0; i < len(keys); i++ { + key := keys[i] + + entry, err := store.Get(ctx, key) + if err != nil { + return fmt.Errorf("unable to retrieve key at [%s]: %w", key, err) + } + if entry != nil { + err = walker(entry) + if err != nil { + return err + } + } + + if strings.HasSuffix(key, "/") { + // Directory + subkeys, err := store.List(ctx, key) + if err != nil { + return fmt.Errorf("unable to list keys at [%s]: %w", key, err) + } + + // Append the sub-keys to the keys slice so it searches into the sub-directory + for _, subkey := range subkeys { + // Avoids infinite loop if the subkey is empty which then repeats indefinitely + if subkey == "" { + continue + } + subkey = fmt.Sprintf("%s%s", key, subkey) + keys = append(keys, subkey) + } + } + } + return nil +} + +// LogicalToMap retrieves all entries in the store and returns them as a map of key -> StorageEntry +func LogicalToMap(t *testing.T, ctx context.Context, store logical.Storage) (data map[string]*logical.StorageEntry) { + data = map[string]*logical.StorageEntry{} + f := func(entry *logical.StorageEntry) error { + data[entry.Key] = entry + return nil + } + + err := WalkLogicalStorage(ctx, store, f) + if err != nil { + t.Fatalf("Unable to walk the storage: %s", err) + } + return data +} + +// Ensure the WalkLogicalStorage function works +func TestWalkLogicalStorage(t *testing.T) { + type testCase struct { + entries []*logical.StorageEntry + } + + tests := map[string]testCase{ + "no entries": { + entries: []*logical.StorageEntry{}, + }, + "one entry": { + entries: []*logical.StorageEntry{ + { + Key: "root", + }, + }, + }, + "many entries": { + entries: []*logical.StorageEntry{ + // Alphabetical, breadth-first + {Key: "bar"}, + {Key: "foo"}, + {Key: "bar/sub-bar1"}, + {Key: "bar/sub-bar2"}, + {Key: "foo/sub-foo1"}, + {Key: "foo/sub-foo2"}, + {Key: "foo/sub-foo3"}, + {Key: "bar/sub-bar1/sub-sub-bar1"}, + {Key: "bar/sub-bar1/sub-sub-bar2"}, + {Key: "bar/sub-bar2/sub-sub-bar1"}, + {Key: "foo/sub-foo1/sub-sub-foo1"}, + {Key: "foo/sub-foo2/sub-sub-foo1"}, + {Key: "foo/sub-foo3/sub-sub-foo1"}, + {Key: "foo/sub-foo3/sub-sub-foo2"}, + }, + }, + "sub key without root key": { + entries: []*logical.StorageEntry{ + {Key: "foo/bar/baz"}, + }, + }, + "key with trailing slash": { + entries: []*logical.StorageEntry{ + {Key: "foo/"}, + }, + }, + "double slash": { + entries: []*logical.StorageEntry{ + {Key: "foo//"}, + {Key: "foo//bar"}, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + store := makeStorage(t, test.entries...) + + actualEntries := []*logical.StorageEntry{} + f := func(entry *logical.StorageEntry) error { + actualEntries = append(actualEntries, entry) + return nil + } + + err := WalkLogicalStorage(ctx, store, f) + if err != nil { + t.Fatalf("Failed to walk storage: %s", err) + } + + if !reflect.DeepEqual(actualEntries, test.entries) { + t.Fatalf("Actual: %#v\nExpected: %#v", actualEntries, test.entries) + } + }) + } +} + +func makeStorage(t *testing.T, entries ...*logical.StorageEntry) *logical.InmemStorage { + t.Helper() + + ctx := context.Background() + + store := new(logical.InmemStorage) + + for _, entry := range entries { + err := store.Put(ctx, entry) + if err != nil { + t.Fatalf("Unable to load test storage: %s", err) + } + } + + return store +} diff --git a/vendor/github.com/hashicorp/vault/api/go.mod b/vendor/github.com/hashicorp/vault/api/go.mod index af68a92b54..221548e432 100644 --- a/vendor/github.com/hashicorp/vault/api/go.mod +++ b/vendor/github.com/hashicorp/vault/api/go.mod @@ -12,8 +12,8 @@ require ( github.com/hashicorp/go-retryablehttp v0.6.2 github.com/hashicorp/go-rootcerts v1.0.1 github.com/hashicorp/hcl v1.0.0 - github.com/hashicorp/vault/sdk v0.1.14-0.20200519221530-14615acda45f - github.com/mitchellh/mapstructure v1.1.2 + github.com/hashicorp/vault/sdk v0.1.14-0.20200514144402-4bfac290c352 + github.com/mitchellh/mapstructure v1.2.2 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/square/go-jose.v2 v2.3.1 diff --git a/vendor/github.com/hashicorp/vault/api/go.sum b/vendor/github.com/hashicorp/vault/api/go.sum index 172833fa1e..d57d24817b 100644 --- a/vendor/github.com/hashicorp/vault/api/go.sum +++ b/vendor/github.com/hashicorp/vault/api/go.sum @@ -75,8 +75,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -95,6 +95,7 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= diff --git a/vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go b/vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go new file mode 100644 index 0000000000..572767263e --- /dev/null +++ b/vendor/github.com/hashicorp/vault/sdk/helper/random/parser.go @@ -0,0 +1,150 @@ +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 new file mode 100644 index 0000000000..efdcf5c302 --- /dev/null +++ b/vendor/github.com/hashicorp/vault/sdk/helper/random/registry.go @@ -0,0 +1,35 @@ +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 new file mode 100644 index 0000000000..fead5b4ffe --- /dev/null +++ b/vendor/github.com/hashicorp/vault/sdk/helper/random/rules.go @@ -0,0 +1,91 @@ +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 new file mode 100644 index 0000000000..c99d631aad --- /dev/null +++ b/vendor/github.com/hashicorp/vault/sdk/helper/random/serializing.go @@ -0,0 +1,88 @@ +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 new file mode 100644 index 0000000000..761577455a --- /dev/null +++ b/vendor/github.com/hashicorp/vault/sdk/helper/random/string_generator.go @@ -0,0 +1,302 @@ +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/storage_inmem.go b/vendor/github.com/hashicorp/vault/sdk/logical/storage_inmem.go index 4c7afa1b92..65368a070f 100644 --- a/vendor/github.com/hashicorp/vault/sdk/logical/storage_inmem.go +++ b/vendor/github.com/hashicorp/vault/sdk/logical/storage_inmem.go @@ -62,6 +62,26 @@ func (s *InmemStorage) Underlying() *inmem.InmemBackend { return s.underlying.(*inmem.InmemBackend) } +func (s *InmemStorage) FailPut(fail bool) *InmemStorage { + s.Underlying().FailPut(fail) + return s +} + +func (s *InmemStorage) FailGet(fail bool) *InmemStorage { + s.Underlying().FailGet(fail) + return s +} + +func (s *InmemStorage) FailDelete(fail bool) *InmemStorage { + s.Underlying().FailDelete(fail) + return s +} + +func (s *InmemStorage) FailList(fail bool) *InmemStorage { + s.Underlying().FailList(fail) + return s +} + func (s *InmemStorage) init() { s.underlying, _ = inmem.NewInmem(nil, nil) } 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 2c5d9c3bed..41f82f36c3 100644 --- a/vendor/github.com/hashicorp/vault/sdk/logical/system_view.go +++ b/vendor/github.com/hashicorp/vault/sdk/logical/system_view.go @@ -3,6 +3,8 @@ package logical import ( "context" "errors" + "fmt" + "io" "time" "github.com/hashicorp/vault/sdk/helper/consts" @@ -68,6 +70,15 @@ type SystemView interface { // PluginEnv returns Vault environment information used by plugins PluginEnv(context.Context) (*PluginEnvironment, error) + + // GeneratePasswordFromPolicy generates a password from the policy referenced. + // If the policy does not exist, this will return an error. + GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) +} + +type PasswordPolicy interface { + // Generate a random password + Generate(context.Context, io.Reader) (string, error) } type ExtendedSystemView interface { @@ -90,6 +101,7 @@ type StaticSystemView struct { Features license.Features VaultVersion string PluginEnvironment *PluginEnvironment + PasswordPolicies map[string]PasswordPolicy } type noopAuditor struct{} @@ -165,3 +177,20 @@ func (d StaticSystemView) HasFeature(feature license.Features) bool { func (d StaticSystemView) PluginEnv(_ context.Context) (*PluginEnvironment, error) { return d.PluginEnvironment, nil } + +func (d StaticSystemView) GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) { + select { + case <-ctx.Done(): + return "", fmt.Errorf("context timed out") + default: + } + + if d.PasswordPolicies == nil { + return "", fmt.Errorf("password policy not found") + } + policy, exists := d.PasswordPolicies[policyName] + if !exists { + return "", fmt.Errorf("password policy not found") + } + return policy.Generate(ctx, nil) +} diff --git a/vendor/github.com/hashicorp/vault/sdk/plugin/grpc_system.go b/vendor/github.com/hashicorp/vault/sdk/plugin/grpc_system.go index 6e27c9b963..ead85aefeb 100644 --- a/vendor/github.com/hashicorp/vault/sdk/plugin/grpc_system.go +++ b/vendor/github.com/hashicorp/vault/sdk/plugin/grpc_system.go @@ -14,6 +14,8 @@ import ( "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/sdk/plugin/pb" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func newGRPCSystemView(conn *grpc.ClientConn) *gRPCSystemViewClient { @@ -161,6 +163,17 @@ func (s *gRPCSystemViewClient) PluginEnv(ctx context.Context) (*logical.PluginEn return reply.PluginEnvironment, nil } +func (s *gRPCSystemViewClient) GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error) { + req := &pb.GeneratePasswordFromPolicyRequest{ + PolicyName: policyName, + } + resp, err := s.client.GeneratePasswordFromPolicy(ctx, req) + if err != nil { + return "", err + } + return resp.Password, nil +} + type gRPCSystemViewServer struct { impl logical.SystemView } @@ -274,3 +287,20 @@ func (s *gRPCSystemViewServer) PluginEnv(ctx context.Context, _ *pb.Empty) (*pb. PluginEnvironment: pluginEnv, }, nil } + +func (s *gRPCSystemViewServer) GeneratePasswordFromPolicy(ctx context.Context, req *pb.GeneratePasswordFromPolicyRequest) (*pb.GeneratePasswordFromPolicyReply, error) { + policyName := req.PolicyName + if policyName == "" { + return &pb.GeneratePasswordFromPolicyReply{}, status.Errorf(codes.InvalidArgument, "no password policy specified") + } + + password, err := s.impl.GeneratePasswordFromPolicy(ctx, policyName) + if err != nil { + return &pb.GeneratePasswordFromPolicyReply{}, status.Errorf(codes.Internal, "failed to generate password") + } + + resp := &pb.GeneratePasswordFromPolicyReply{ + Password: password, + } + return resp, nil +} diff --git a/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.pb.go b/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.pb.go index a5e802534e..50e8156748 100644 --- a/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.pb.go +++ b/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.pb.go @@ -3017,6 +3017,100 @@ func (x *PluginEnvReply) GetErr() string { return "" } +type GeneratePasswordFromPolicyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PolicyName string `sentinel:"" protobuf:"bytes,1,opt,name=policy_name,json=policyName,proto3" json:"policy_name,omitempty"` +} + +func (x *GeneratePasswordFromPolicyRequest) Reset() { + *x = GeneratePasswordFromPolicyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeneratePasswordFromPolicyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratePasswordFromPolicyRequest) ProtoMessage() {} + +func (x *GeneratePasswordFromPolicyRequest) ProtoReflect() protoreflect.Message { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratePasswordFromPolicyRequest.ProtoReflect.Descriptor instead. +func (*GeneratePasswordFromPolicyRequest) Descriptor() ([]byte, []int) { + return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{44} +} + +func (x *GeneratePasswordFromPolicyRequest) GetPolicyName() string { + if x != nil { + return x.PolicyName + } + return "" +} + +type GeneratePasswordFromPolicyReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Password string `sentinel:"" protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"` +} + +func (x *GeneratePasswordFromPolicyReply) Reset() { + *x = GeneratePasswordFromPolicyReply{} + if protoimpl.UnsafeEnabled { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[45] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GeneratePasswordFromPolicyReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GeneratePasswordFromPolicyReply) ProtoMessage() {} + +func (x *GeneratePasswordFromPolicyReply) ProtoReflect() protoreflect.Message { + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[45] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GeneratePasswordFromPolicyReply.ProtoReflect.Descriptor instead. +func (*GeneratePasswordFromPolicyReply) Descriptor() ([]byte, []int) { + return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{45} +} + +func (x *GeneratePasswordFromPolicyReply) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + type Connection struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3029,7 +3123,7 @@ type Connection struct { func (x *Connection) Reset() { *x = Connection{} if protoimpl.UnsafeEnabled { - mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3042,7 +3136,7 @@ func (x *Connection) String() string { func (*Connection) ProtoMessage() {} func (x *Connection) ProtoReflect() protoreflect.Message { - mi := &file_sdk_plugin_pb_backend_proto_msgTypes[44] + mi := &file_sdk_plugin_pb_backend_proto_msgTypes[46] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3055,7 +3149,7 @@ func (x *Connection) ProtoReflect() protoreflect.Message { // Deprecated: Use Connection.ProtoReflect.Descriptor instead. func (*Connection) Descriptor() ([]byte, []int) { - return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{44} + return file_sdk_plugin_pb_backend_proto_rawDescGZIP(), []int{46} } func (x *Connection) GetRemoteAddr() string { @@ -3423,89 +3517,104 @@ var file_sdk_plugin_pb_backend_proto_rawDesc = []byte{ 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x11, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x72, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x65, 0x72, 0x72, 0x22, 0x2d, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x41, 0x64, - 0x64, 0x72, 0x32, 0xa5, 0x03, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x3e, - 0x0a, 0x0d, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, - 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x09, - 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, - 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, - 0x12, 0x53, 0x0a, 0x14, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, - 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, - 0x63, 0x6b, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x1d, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, - 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x43, 0x6c, 0x65, 0x61, 0x6e, 0x75, 0x70, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x09, 0x2e, 0x70, 0x62, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x0d, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x76, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x09, - 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x26, 0x0a, 0x05, 0x53, 0x65, 0x74, - 0x75, 0x70, 0x12, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x41, 0x72, 0x67, - 0x73, 0x1a, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, 0x70, 0x52, 0x65, 0x70, 0x6c, - 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x12, - 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x41, - 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, - 0x69, 0x7a, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, 0x2e, 0x70, 0x62, - 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xd5, 0x01, 0x0a, 0x07, 0x53, - 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x13, - 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x41, - 0x72, 0x67, 0x73, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x47, 0x65, 0x74, - 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, 0x74, - 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, 0x0a, 0x03, 0x50, 0x75, 0x74, - 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, 0x74, - 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x50, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, 0x2e, - 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x32, 0xc7, 0x04, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x56, 0x69, 0x65, - 0x77, 0x12, 0x2a, 0x0a, 0x0f, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x4c, 0x65, 0x61, 0x73, - 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, - 0x0b, 0x4d, 0x61, 0x78, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, - 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x10, 0x2e, 0x70, 0x62, - 0x2e, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x36, 0x0a, - 0x0f, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x18, 0x2e, 0x70, 0x62, - 0x2e, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x38, 0x0a, 0x10, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x19, 0x2e, - 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, - 0x61, 0x74, 0x61, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x4d, 0x6c, 0x6f, 0x63, - 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2c, 0x0a, 0x0a, 0x4c, 0x6f, - 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x6f, - 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, - 0x2a, 0x0a, 0x09, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x12, 0x09, 0x2e, 0x70, - 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3f, 0x0a, 0x0f, 0x47, - 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, - 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, - 0x67, 0x73, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, - 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x2a, 0x5a, 0x28, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x72, 0x22, 0x44, 0x0a, 0x21, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x6f, 0x6c, 0x69, + 0x63, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, + 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3d, 0x0a, 0x1f, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, + 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x2d, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6d, + 0x6f, 0x74, 0x65, 0x41, 0x64, 0x64, 0x72, 0x32, 0xa5, 0x03, 0x0a, 0x07, 0x42, 0x61, 0x63, 0x6b, + 0x65, 0x6e, 0x64, 0x12, 0x3e, 0x0a, 0x0d, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x70, 0x62, + 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, 0x0c, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, + 0x74, 0x68, 0x73, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, + 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x70, 0x65, 0x63, 0x69, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x73, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x53, 0x0a, 0x14, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, + 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1c, 0x2e, + 0x70, 0x62, 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, + 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x1d, 0x2e, 0x70, 0x62, + 0x2e, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x65, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x1f, 0x0a, 0x07, 0x43, 0x6c, + 0x65, 0x61, 0x6e, 0x75, 0x70, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x31, 0x0a, 0x0d, 0x49, + 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x15, 0x2e, 0x70, + 0x62, 0x2e, 0x49, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x41, + 0x72, 0x67, 0x73, 0x1a, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x26, + 0x0a, 0x05, 0x53, 0x65, 0x74, 0x75, 0x70, 0x12, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, + 0x75, 0x70, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x0e, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x65, 0x74, 0x75, + 0x70, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x0a, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x20, 0x0a, + 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x0d, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, + 0xd5, 0x01, 0x0a, 0x07, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x4c, 0x69, 0x73, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, + 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x47, 0x65, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x47, 0x65, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2e, + 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x50, 0x75, 0x74, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x53, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x75, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x37, + 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, + 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x72, 0x67, 0x73, 0x1a, + 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x32, 0xb1, 0x05, 0x0a, 0x0a, 0x53, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x56, 0x69, 0x65, 0x77, 0x12, 0x2a, 0x0a, 0x0f, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, + 0x74, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x0b, 0x4d, 0x61, 0x78, 0x4c, 0x65, 0x61, 0x73, 0x65, 0x54, 0x54, + 0x4c, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0c, 0x2e, 0x70, + 0x62, 0x2e, 0x54, 0x54, 0x4c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x26, 0x0a, 0x07, 0x54, 0x61, + 0x69, 0x6e, 0x74, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x10, 0x2e, 0x70, 0x62, 0x2e, 0x54, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x52, 0x65, 0x70, + 0x6c, 0x79, 0x12, 0x36, 0x0a, 0x0f, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x44, 0x69, 0x73, + 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x38, 0x0a, 0x10, 0x52, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x09, + 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, + 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x47, 0x0a, 0x10, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x12, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x41, 0x72, + 0x67, 0x73, 0x1a, 0x19, 0x2e, 0x70, 0x62, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x57, 0x72, 0x61, 0x70, 0x44, 0x61, 0x74, 0x61, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x30, 0x0a, + 0x0c, 0x4d, 0x6c, 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x09, 0x2e, + 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4d, 0x6c, + 0x6f, 0x63, 0x6b, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, + 0x2c, 0x0a, 0x0a, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x09, 0x2e, + 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x4c, 0x6f, + 0x63, 0x61, 0x6c, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, + 0x0a, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x2e, 0x70, 0x62, + 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, + 0x13, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x12, 0x2a, 0x0a, 0x09, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, + 0x76, 0x12, 0x09, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x12, 0x2e, 0x70, + 0x62, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x45, 0x6e, 0x76, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x3f, 0x0a, 0x0f, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x12, 0x12, 0x2e, 0x70, 0x62, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x49, + 0x6e, 0x66, 0x6f, 0x41, 0x72, 0x67, 0x73, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x72, 0x6f, + 0x75, 0x70, 0x73, 0x46, 0x6f, 0x72, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x12, 0x68, 0x0a, 0x1a, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, + 0x25, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x46, 0x72, 0x6f, 0x6d, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x42, 0x2a, 0x5a, 0x28, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -3520,82 +3629,84 @@ func file_sdk_plugin_pb_backend_proto_rawDescGZIP() []byte { return file_sdk_plugin_pb_backend_proto_rawDescData } -var file_sdk_plugin_pb_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 50) +var file_sdk_plugin_pb_backend_proto_msgTypes = make([]protoimpl.MessageInfo, 52) var file_sdk_plugin_pb_backend_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: pb.Empty - (*Header)(nil), // 1: pb.Header - (*ProtoError)(nil), // 2: pb.ProtoError - (*Paths)(nil), // 3: pb.Paths - (*Request)(nil), // 4: pb.Request - (*Auth)(nil), // 5: pb.Auth - (*TokenEntry)(nil), // 6: pb.TokenEntry - (*LeaseOptions)(nil), // 7: pb.LeaseOptions - (*Secret)(nil), // 8: pb.Secret - (*Response)(nil), // 9: pb.Response - (*ResponseWrapInfo)(nil), // 10: pb.ResponseWrapInfo - (*RequestWrapInfo)(nil), // 11: pb.RequestWrapInfo - (*HandleRequestArgs)(nil), // 12: pb.HandleRequestArgs - (*HandleRequestReply)(nil), // 13: pb.HandleRequestReply - (*InitializeArgs)(nil), // 14: pb.InitializeArgs - (*InitializeReply)(nil), // 15: pb.InitializeReply - (*SpecialPathsReply)(nil), // 16: pb.SpecialPathsReply - (*HandleExistenceCheckArgs)(nil), // 17: pb.HandleExistenceCheckArgs - (*HandleExistenceCheckReply)(nil), // 18: pb.HandleExistenceCheckReply - (*SetupArgs)(nil), // 19: pb.SetupArgs - (*SetupReply)(nil), // 20: pb.SetupReply - (*TypeReply)(nil), // 21: pb.TypeReply - (*InvalidateKeyArgs)(nil), // 22: pb.InvalidateKeyArgs - (*StorageEntry)(nil), // 23: pb.StorageEntry - (*StorageListArgs)(nil), // 24: pb.StorageListArgs - (*StorageListReply)(nil), // 25: pb.StorageListReply - (*StorageGetArgs)(nil), // 26: pb.StorageGetArgs - (*StorageGetReply)(nil), // 27: pb.StorageGetReply - (*StoragePutArgs)(nil), // 28: pb.StoragePutArgs - (*StoragePutReply)(nil), // 29: pb.StoragePutReply - (*StorageDeleteArgs)(nil), // 30: pb.StorageDeleteArgs - (*StorageDeleteReply)(nil), // 31: pb.StorageDeleteReply - (*TTLReply)(nil), // 32: pb.TTLReply - (*TaintedReply)(nil), // 33: pb.TaintedReply - (*CachingDisabledReply)(nil), // 34: pb.CachingDisabledReply - (*ReplicationStateReply)(nil), // 35: pb.ReplicationStateReply - (*ResponseWrapDataArgs)(nil), // 36: pb.ResponseWrapDataArgs - (*ResponseWrapDataReply)(nil), // 37: pb.ResponseWrapDataReply - (*MlockEnabledReply)(nil), // 38: pb.MlockEnabledReply - (*LocalMountReply)(nil), // 39: pb.LocalMountReply - (*EntityInfoArgs)(nil), // 40: pb.EntityInfoArgs - (*EntityInfoReply)(nil), // 41: pb.EntityInfoReply - (*GroupsForEntityReply)(nil), // 42: pb.GroupsForEntityReply - (*PluginEnvReply)(nil), // 43: pb.PluginEnvReply - (*Connection)(nil), // 44: pb.Connection - nil, // 45: pb.Request.HeadersEntry - nil, // 46: pb.Auth.MetadataEntry - nil, // 47: pb.TokenEntry.MetaEntry - nil, // 48: pb.Response.HeadersEntry - nil, // 49: pb.SetupArgs.ConfigEntry - (*logical.Alias)(nil), // 50: logical.Alias - (*timestamp.Timestamp)(nil), // 51: google.protobuf.Timestamp - (*logical.Entity)(nil), // 52: logical.Entity - (*logical.Group)(nil), // 53: logical.Group - (*logical.PluginEnvironment)(nil), // 54: logical.PluginEnvironment + (*Empty)(nil), // 0: pb.Empty + (*Header)(nil), // 1: pb.Header + (*ProtoError)(nil), // 2: pb.ProtoError + (*Paths)(nil), // 3: pb.Paths + (*Request)(nil), // 4: pb.Request + (*Auth)(nil), // 5: pb.Auth + (*TokenEntry)(nil), // 6: pb.TokenEntry + (*LeaseOptions)(nil), // 7: pb.LeaseOptions + (*Secret)(nil), // 8: pb.Secret + (*Response)(nil), // 9: pb.Response + (*ResponseWrapInfo)(nil), // 10: pb.ResponseWrapInfo + (*RequestWrapInfo)(nil), // 11: pb.RequestWrapInfo + (*HandleRequestArgs)(nil), // 12: pb.HandleRequestArgs + (*HandleRequestReply)(nil), // 13: pb.HandleRequestReply + (*InitializeArgs)(nil), // 14: pb.InitializeArgs + (*InitializeReply)(nil), // 15: pb.InitializeReply + (*SpecialPathsReply)(nil), // 16: pb.SpecialPathsReply + (*HandleExistenceCheckArgs)(nil), // 17: pb.HandleExistenceCheckArgs + (*HandleExistenceCheckReply)(nil), // 18: pb.HandleExistenceCheckReply + (*SetupArgs)(nil), // 19: pb.SetupArgs + (*SetupReply)(nil), // 20: pb.SetupReply + (*TypeReply)(nil), // 21: pb.TypeReply + (*InvalidateKeyArgs)(nil), // 22: pb.InvalidateKeyArgs + (*StorageEntry)(nil), // 23: pb.StorageEntry + (*StorageListArgs)(nil), // 24: pb.StorageListArgs + (*StorageListReply)(nil), // 25: pb.StorageListReply + (*StorageGetArgs)(nil), // 26: pb.StorageGetArgs + (*StorageGetReply)(nil), // 27: pb.StorageGetReply + (*StoragePutArgs)(nil), // 28: pb.StoragePutArgs + (*StoragePutReply)(nil), // 29: pb.StoragePutReply + (*StorageDeleteArgs)(nil), // 30: pb.StorageDeleteArgs + (*StorageDeleteReply)(nil), // 31: pb.StorageDeleteReply + (*TTLReply)(nil), // 32: pb.TTLReply + (*TaintedReply)(nil), // 33: pb.TaintedReply + (*CachingDisabledReply)(nil), // 34: pb.CachingDisabledReply + (*ReplicationStateReply)(nil), // 35: pb.ReplicationStateReply + (*ResponseWrapDataArgs)(nil), // 36: pb.ResponseWrapDataArgs + (*ResponseWrapDataReply)(nil), // 37: pb.ResponseWrapDataReply + (*MlockEnabledReply)(nil), // 38: pb.MlockEnabledReply + (*LocalMountReply)(nil), // 39: pb.LocalMountReply + (*EntityInfoArgs)(nil), // 40: pb.EntityInfoArgs + (*EntityInfoReply)(nil), // 41: pb.EntityInfoReply + (*GroupsForEntityReply)(nil), // 42: pb.GroupsForEntityReply + (*PluginEnvReply)(nil), // 43: pb.PluginEnvReply + (*GeneratePasswordFromPolicyRequest)(nil), // 44: pb.GeneratePasswordFromPolicyRequest + (*GeneratePasswordFromPolicyReply)(nil), // 45: pb.GeneratePasswordFromPolicyReply + (*Connection)(nil), // 46: pb.Connection + nil, // 47: pb.Request.HeadersEntry + nil, // 48: pb.Auth.MetadataEntry + nil, // 49: pb.TokenEntry.MetaEntry + nil, // 50: pb.Response.HeadersEntry + nil, // 51: pb.SetupArgs.ConfigEntry + (*logical.Alias)(nil), // 52: logical.Alias + (*timestamp.Timestamp)(nil), // 53: google.protobuf.Timestamp + (*logical.Entity)(nil), // 54: logical.Entity + (*logical.Group)(nil), // 55: logical.Group + (*logical.PluginEnvironment)(nil), // 56: logical.PluginEnvironment } var file_sdk_plugin_pb_backend_proto_depIDxs = []int32{ 8, // 0: pb.Request.secret:type_name -> pb.Secret 5, // 1: pb.Request.auth:type_name -> pb.Auth - 45, // 2: pb.Request.headers:type_name -> pb.Request.HeadersEntry + 47, // 2: pb.Request.headers:type_name -> pb.Request.HeadersEntry 11, // 3: pb.Request.wrap_info:type_name -> pb.RequestWrapInfo - 44, // 4: pb.Request.connection:type_name -> pb.Connection + 46, // 4: pb.Request.connection:type_name -> pb.Connection 7, // 5: pb.Auth.lease_options:type_name -> pb.LeaseOptions - 46, // 6: pb.Auth.metadata:type_name -> pb.Auth.MetadataEntry - 50, // 7: pb.Auth.alias:type_name -> logical.Alias - 50, // 8: pb.Auth.group_aliases:type_name -> logical.Alias - 47, // 9: pb.TokenEntry.meta:type_name -> pb.TokenEntry.MetaEntry - 51, // 10: pb.LeaseOptions.issue_time:type_name -> google.protobuf.Timestamp + 48, // 6: pb.Auth.metadata:type_name -> pb.Auth.MetadataEntry + 52, // 7: pb.Auth.alias:type_name -> logical.Alias + 52, // 8: pb.Auth.group_aliases:type_name -> logical.Alias + 49, // 9: pb.TokenEntry.meta:type_name -> pb.TokenEntry.MetaEntry + 53, // 10: pb.LeaseOptions.issue_time:type_name -> google.protobuf.Timestamp 7, // 11: pb.Secret.lease_options:type_name -> pb.LeaseOptions 8, // 12: pb.Response.secret:type_name -> pb.Secret 5, // 13: pb.Response.auth:type_name -> pb.Auth 10, // 14: pb.Response.wrap_info:type_name -> pb.ResponseWrapInfo - 48, // 15: pb.Response.headers:type_name -> pb.Response.HeadersEntry - 51, // 16: pb.ResponseWrapInfo.creation_time:type_name -> google.protobuf.Timestamp + 50, // 15: pb.Response.headers:type_name -> pb.Response.HeadersEntry + 53, // 16: pb.ResponseWrapInfo.creation_time:type_name -> google.protobuf.Timestamp 4, // 17: pb.HandleRequestArgs.request:type_name -> pb.Request 9, // 18: pb.HandleRequestReply.response:type_name -> pb.Response 2, // 19: pb.HandleRequestReply.err:type_name -> pb.ProtoError @@ -3603,13 +3714,13 @@ var file_sdk_plugin_pb_backend_proto_depIDxs = []int32{ 3, // 21: pb.SpecialPathsReply.paths:type_name -> pb.Paths 4, // 22: pb.HandleExistenceCheckArgs.request:type_name -> pb.Request 2, // 23: pb.HandleExistenceCheckReply.err:type_name -> pb.ProtoError - 49, // 24: pb.SetupArgs.Config:type_name -> pb.SetupArgs.ConfigEntry + 51, // 24: pb.SetupArgs.Config:type_name -> pb.SetupArgs.ConfigEntry 23, // 25: pb.StorageGetReply.entry:type_name -> pb.StorageEntry 23, // 26: pb.StoragePutArgs.entry:type_name -> pb.StorageEntry 10, // 27: pb.ResponseWrapDataReply.wrap_info:type_name -> pb.ResponseWrapInfo - 52, // 28: pb.EntityInfoReply.entity:type_name -> logical.Entity - 53, // 29: pb.GroupsForEntityReply.groups:type_name -> logical.Group - 54, // 30: pb.PluginEnvReply.plugin_environment:type_name -> logical.PluginEnvironment + 54, // 28: pb.EntityInfoReply.entity:type_name -> logical.Entity + 55, // 29: pb.GroupsForEntityReply.groups:type_name -> logical.Group + 56, // 30: pb.PluginEnvReply.plugin_environment:type_name -> logical.PluginEnvironment 1, // 31: pb.Request.HeadersEntry.value:type_name -> pb.Header 1, // 32: pb.Response.HeadersEntry.value:type_name -> pb.Header 12, // 33: pb.Backend.HandleRequest:input_type -> pb.HandleRequestArgs @@ -3635,31 +3746,33 @@ var file_sdk_plugin_pb_backend_proto_depIDxs = []int32{ 40, // 53: pb.SystemView.EntityInfo:input_type -> pb.EntityInfoArgs 0, // 54: pb.SystemView.PluginEnv:input_type -> pb.Empty 40, // 55: pb.SystemView.GroupsForEntity:input_type -> pb.EntityInfoArgs - 13, // 56: pb.Backend.HandleRequest:output_type -> pb.HandleRequestReply - 16, // 57: pb.Backend.SpecialPaths:output_type -> pb.SpecialPathsReply - 18, // 58: pb.Backend.HandleExistenceCheck:output_type -> pb.HandleExistenceCheckReply - 0, // 59: pb.Backend.Cleanup:output_type -> pb.Empty - 0, // 60: pb.Backend.InvalidateKey:output_type -> pb.Empty - 20, // 61: pb.Backend.Setup:output_type -> pb.SetupReply - 15, // 62: pb.Backend.Initialize:output_type -> pb.InitializeReply - 21, // 63: pb.Backend.Type:output_type -> pb.TypeReply - 25, // 64: pb.Storage.List:output_type -> pb.StorageListReply - 27, // 65: pb.Storage.Get:output_type -> pb.StorageGetReply - 29, // 66: pb.Storage.Put:output_type -> pb.StoragePutReply - 31, // 67: pb.Storage.Delete:output_type -> pb.StorageDeleteReply - 32, // 68: pb.SystemView.DefaultLeaseTTL:output_type -> pb.TTLReply - 32, // 69: pb.SystemView.MaxLeaseTTL:output_type -> pb.TTLReply - 33, // 70: pb.SystemView.Tainted:output_type -> pb.TaintedReply - 34, // 71: pb.SystemView.CachingDisabled:output_type -> pb.CachingDisabledReply - 35, // 72: pb.SystemView.ReplicationState:output_type -> pb.ReplicationStateReply - 37, // 73: pb.SystemView.ResponseWrapData:output_type -> pb.ResponseWrapDataReply - 38, // 74: pb.SystemView.MlockEnabled:output_type -> pb.MlockEnabledReply - 39, // 75: pb.SystemView.LocalMount:output_type -> pb.LocalMountReply - 41, // 76: pb.SystemView.EntityInfo:output_type -> pb.EntityInfoReply - 43, // 77: pb.SystemView.PluginEnv:output_type -> pb.PluginEnvReply - 42, // 78: pb.SystemView.GroupsForEntity:output_type -> pb.GroupsForEntityReply - 56, // [56:79] is the sub-list for method output_type - 33, // [33:56] is the sub-list for method input_type + 44, // 56: pb.SystemView.GeneratePasswordFromPolicy:input_type -> pb.GeneratePasswordFromPolicyRequest + 13, // 57: pb.Backend.HandleRequest:output_type -> pb.HandleRequestReply + 16, // 58: pb.Backend.SpecialPaths:output_type -> pb.SpecialPathsReply + 18, // 59: pb.Backend.HandleExistenceCheck:output_type -> pb.HandleExistenceCheckReply + 0, // 60: pb.Backend.Cleanup:output_type -> pb.Empty + 0, // 61: pb.Backend.InvalidateKey:output_type -> pb.Empty + 20, // 62: pb.Backend.Setup:output_type -> pb.SetupReply + 15, // 63: pb.Backend.Initialize:output_type -> pb.InitializeReply + 21, // 64: pb.Backend.Type:output_type -> pb.TypeReply + 25, // 65: pb.Storage.List:output_type -> pb.StorageListReply + 27, // 66: pb.Storage.Get:output_type -> pb.StorageGetReply + 29, // 67: pb.Storage.Put:output_type -> pb.StoragePutReply + 31, // 68: pb.Storage.Delete:output_type -> pb.StorageDeleteReply + 32, // 69: pb.SystemView.DefaultLeaseTTL:output_type -> pb.TTLReply + 32, // 70: pb.SystemView.MaxLeaseTTL:output_type -> pb.TTLReply + 33, // 71: pb.SystemView.Tainted:output_type -> pb.TaintedReply + 34, // 72: pb.SystemView.CachingDisabled:output_type -> pb.CachingDisabledReply + 35, // 73: pb.SystemView.ReplicationState:output_type -> pb.ReplicationStateReply + 37, // 74: pb.SystemView.ResponseWrapData:output_type -> pb.ResponseWrapDataReply + 38, // 75: pb.SystemView.MlockEnabled:output_type -> pb.MlockEnabledReply + 39, // 76: pb.SystemView.LocalMount:output_type -> pb.LocalMountReply + 41, // 77: pb.SystemView.EntityInfo:output_type -> pb.EntityInfoReply + 43, // 78: pb.SystemView.PluginEnv:output_type -> pb.PluginEnvReply + 42, // 79: pb.SystemView.GroupsForEntity:output_type -> pb.GroupsForEntityReply + 45, // 80: pb.SystemView.GeneratePasswordFromPolicy:output_type -> pb.GeneratePasswordFromPolicyReply + 57, // [57:81] is the sub-list for method output_type + 33, // [33:57] is the sub-list for method input_type 33, // [33:33] is the sub-list for extension type_name 33, // [33:33] is the sub-list for extension extendee 0, // [0:33] is the sub-list for field type_name @@ -4200,6 +4313,30 @@ func file_sdk_plugin_pb_backend_proto_init() { } } file_sdk_plugin_pb_backend_proto_msgTypes[44].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeneratePasswordFromPolicyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sdk_plugin_pb_backend_proto_msgTypes[45].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GeneratePasswordFromPolicyReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sdk_plugin_pb_backend_proto_msgTypes[46].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Connection); i { case 0: return &v.state @@ -4218,7 +4355,7 @@ func file_sdk_plugin_pb_backend_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_sdk_plugin_pb_backend_proto_rawDesc, NumEnums: 0, - NumMessages: 50, + NumMessages: 52, NumExtensions: 0, NumServices: 3, }, @@ -4838,6 +4975,8 @@ type SystemViewClient interface { // GroupsForEntity returns the group membership information for the given // entity id GroupsForEntity(ctx context.Context, in *EntityInfoArgs, opts ...grpc.CallOption) (*GroupsForEntityReply, error) + // GeneratePasswordFromPolicy generates a password from an existing password policy + GeneratePasswordFromPolicy(ctx context.Context, in *GeneratePasswordFromPolicyRequest, opts ...grpc.CallOption) (*GeneratePasswordFromPolicyReply, error) } type systemViewClient struct { @@ -4947,6 +5086,15 @@ func (c *systemViewClient) GroupsForEntity(ctx context.Context, in *EntityInfoAr return out, nil } +func (c *systemViewClient) GeneratePasswordFromPolicy(ctx context.Context, in *GeneratePasswordFromPolicyRequest, opts ...grpc.CallOption) (*GeneratePasswordFromPolicyReply, error) { + out := new(GeneratePasswordFromPolicyReply) + err := c.cc.Invoke(ctx, "/pb.SystemView/GeneratePasswordFromPolicy", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // SystemViewServer is the server API for SystemView service. type SystemViewServer interface { // DefaultLeaseTTL returns the default lease TTL set in Vault configuration @@ -4985,6 +5133,8 @@ type SystemViewServer interface { // GroupsForEntity returns the group membership information for the given // entity id GroupsForEntity(context.Context, *EntityInfoArgs) (*GroupsForEntityReply, error) + // GeneratePasswordFromPolicy generates a password from an existing password policy + GeneratePasswordFromPolicy(context.Context, *GeneratePasswordFromPolicyRequest) (*GeneratePasswordFromPolicyReply, error) } // UnimplementedSystemViewServer can be embedded to have forward compatible implementations. @@ -5024,6 +5174,9 @@ func (*UnimplementedSystemViewServer) PluginEnv(context.Context, *Empty) (*Plugi func (*UnimplementedSystemViewServer) GroupsForEntity(context.Context, *EntityInfoArgs) (*GroupsForEntityReply, error) { return nil, status.Errorf(codes.Unimplemented, "method GroupsForEntity not implemented") } +func (*UnimplementedSystemViewServer) GeneratePasswordFromPolicy(context.Context, *GeneratePasswordFromPolicyRequest) (*GeneratePasswordFromPolicyReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method GeneratePasswordFromPolicy not implemented") +} func RegisterSystemViewServer(s *grpc.Server, srv SystemViewServer) { s.RegisterService(&_SystemView_serviceDesc, srv) @@ -5227,6 +5380,24 @@ func _SystemView_GroupsForEntity_Handler(srv interface{}, ctx context.Context, d return interceptor(ctx, in, info, handler) } +func _SystemView_GeneratePasswordFromPolicy_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GeneratePasswordFromPolicyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemViewServer).GeneratePasswordFromPolicy(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.SystemView/GeneratePasswordFromPolicy", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemViewServer).GeneratePasswordFromPolicy(ctx, req.(*GeneratePasswordFromPolicyRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _SystemView_serviceDesc = grpc.ServiceDesc{ ServiceName: "pb.SystemView", HandlerType: (*SystemViewServer)(nil), @@ -5275,6 +5446,10 @@ var _SystemView_serviceDesc = grpc.ServiceDesc{ MethodName: "GroupsForEntity", Handler: _SystemView_GroupsForEntity_Handler, }, + { + MethodName: "GeneratePasswordFromPolicy", + Handler: _SystemView_GeneratePasswordFromPolicy_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "sdk/plugin/pb/backend.proto", diff --git a/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.proto b/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.proto index 7b910ea0f0..e7244df50b 100644 --- a/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.proto +++ b/vendor/github.com/hashicorp/vault/sdk/plugin/pb/backend.proto @@ -554,12 +554,20 @@ message PluginEnvReply { string err = 2; } +message GeneratePasswordFromPolicyRequest { + string policy_name = 1; +} + +message GeneratePasswordFromPolicyReply { + string password = 1; +} + // SystemView exposes system configuration information in a safe way for plugins // to consume. Plugins should implement the client for this service. service SystemView { // DefaultLeaseTTL returns the default lease TTL set in Vault configuration rpc DefaultLeaseTTL(Empty) returns (TTLReply); - + // MaxLeaseTTL returns the max lease TTL set in Vault configuration; backend // authors should take care not to issue credentials that last longer than // this value, as Vault will revoke them @@ -603,6 +611,9 @@ service SystemView { // GroupsForEntity returns the group membership information for the given // entity id rpc GroupsForEntity(EntityInfoArgs) returns (GroupsForEntityReply); + + // GeneratePasswordFromPolicy generates a password from an existing password policy + rpc GeneratePasswordFromPolicy(GeneratePasswordFromPolicyRequest) returns (GeneratePasswordFromPolicyReply); } message Connection { diff --git a/vendor/github.com/mitchellh/mapstructure/.travis.yml b/vendor/github.com/mitchellh/mapstructure/.travis.yml index 1689c7d735..b122a8e3d9 100644 --- a/vendor/github.com/mitchellh/mapstructure/.travis.yml +++ b/vendor/github.com/mitchellh/mapstructure/.travis.yml @@ -6,3 +6,4 @@ go: script: - go test + - go test -bench . -benchmem diff --git a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md index 3b3cb723f8..0a21e2cd1b 100644 --- a/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md +++ b/vendor/github.com/mitchellh/mapstructure/CHANGELOG.md @@ -1,3 +1,22 @@ +## 1.2.2 + +* Do not add unsettable (unexported) values to the unused metadata key + or "remain" value. [GH-150] + +## 1.2.1 + +* Go modules checksum mismatch fix + +## 1.2.0 + +* Added support to capture unused values in a field using the `",remain"` value + in the mapstructure tag. There is an example to showcase usage. +* Added `DecoderConfig` option to always squash embedded structs +* `json.Number` can decode into `uint` types +* Empty slices are preserved and not replaced with nil slices +* Fix panic that can occur in when decoding a map into a nil slice of structs +* Improved package documentation for godoc + ## 1.1.2 * Fix error when decode hook decodes interface implementation into interface diff --git a/vendor/github.com/mitchellh/mapstructure/go.mod b/vendor/github.com/mitchellh/mapstructure/go.mod index d2a7125620..a03ae97308 100644 --- a/vendor/github.com/mitchellh/mapstructure/go.mod +++ b/vendor/github.com/mitchellh/mapstructure/go.mod @@ -1 +1,3 @@ module github.com/mitchellh/mapstructure + +go 1.14 diff --git a/vendor/github.com/mitchellh/mapstructure/mapstructure.go b/vendor/github.com/mitchellh/mapstructure/mapstructure.go index 256ee63fbf..daea3318e0 100644 --- a/vendor/github.com/mitchellh/mapstructure/mapstructure.go +++ b/vendor/github.com/mitchellh/mapstructure/mapstructure.go @@ -1,10 +1,109 @@ -// Package mapstructure exposes functionality to convert an arbitrary -// map[string]interface{} into a native Go structure. +// Package mapstructure exposes functionality to convert one arbitrary +// Go type into another, typically to convert a map[string]interface{} +// into a native Go structure. // // The Go structure can be arbitrarily complex, containing slices, // other structs, etc. and the decoder will properly decode nested // maps and so on into the proper structures in the native Go struct. // See the examples to see what the decoder is capable of. +// +// The simplest function to start with is Decode. +// +// Field Tags +// +// When decoding to a struct, mapstructure will use the field name by +// default to perform the mapping. For example, if a struct has a field +// "Username" then mapstructure will look for a key in the source value +// of "username" (case insensitive). +// +// type User struct { +// Username string +// } +// +// You can change the behavior of mapstructure by using struct tags. +// The default struct tag that mapstructure looks for is "mapstructure" +// but you can customize it using DecoderConfig. +// +// Renaming Fields +// +// To rename the key that mapstructure looks for, use the "mapstructure" +// tag and set a value directly. For example, to change the "username" example +// above to "user": +// +// type User struct { +// Username string `mapstructure:"user"` +// } +// +// Embedded Structs and Squashing +// +// Embedded structs are treated as if they're another field with that name. +// By default, the two structs below are equivalent when decoding with +// mapstructure: +// +// type Person struct { +// Name string +// } +// +// type Friend struct { +// Person +// } +// +// type Friend struct { +// Person Person +// } +// +// This would require an input that looks like below: +// +// map[string]interface{}{ +// "person": map[string]interface{}{"name": "alice"}, +// } +// +// If your "person" value is NOT nested, then you can append ",squash" to +// your tag value and mapstructure will treat it as if the embedded struct +// were part of the struct directly. Example: +// +// type Friend struct { +// Person `mapstructure:",squash"` +// } +// +// Now the following input would be accepted: +// +// map[string]interface{}{ +// "name": "alice", +// } +// +// DecoderConfig has a field that changes the behavior of mapstructure +// to always squash embedded structs. +// +// Remainder Values +// +// If there are any unmapped keys in the source value, mapstructure by +// default will silently ignore them. You can error by setting ErrorUnused +// in DecoderConfig. If you're using Metadata you can also maintain a slice +// of the unused keys. +// +// You can also use the ",remain" suffix on your tag to collect all unused +// values in a map. The field with this tag MUST be a map type and should +// probably be a "map[string]interface{}" or "map[interface{}]interface{}". +// See example below: +// +// type Friend struct { +// Name string +// Other map[string]interface{} `mapstructure:",remain"` +// } +// +// Given the input below, Other would be populated with the other +// values that weren't used (everything but "name"): +// +// map[string]interface{}{ +// "name": "bob", +// "address": "123 Maple St.", +// } +// +// Other Configuration +// +// mapstructure is highly configurable. See the DecoderConfig struct +// for other features and options that are supported. package mapstructure import ( @@ -80,6 +179,14 @@ type DecoderConfig struct { // WeaklyTypedInput bool + // Squash will squash embedded structs. A squash tag may also be + // added to an individual struct field using a tag. For example: + // + // type Parent struct { + // Child `mapstructure:",squash"` + // } + Squash bool + // Metadata is the struct that will contain extra metadata about // the decoding. If this is nil, then no metadata will be tracked. Metadata *Metadata @@ -438,6 +545,7 @@ func (d *Decoder) decodeInt(name string, data interface{}, val reflect.Value) er func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) error { dataVal := reflect.Indirect(reflect.ValueOf(data)) dataKind := getKind(dataVal) + dataType := dataVal.Type() switch { case dataKind == reflect.Int: @@ -469,6 +577,18 @@ func (d *Decoder) decodeUint(name string, data interface{}, val reflect.Value) e } else { return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + if i < 0 && !d.config.WeaklyTypedInput { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) default: return fmt.Errorf( "'%s' expected type '%s', got unconvertible type '%s'", @@ -689,16 +809,19 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re keyName = tagParts[0] } + // If Squash is set in the config, we squash the field down. + squash := d.config.Squash && v.Kind() == reflect.Struct // If "squash" is specified in the tag, we squash the field down. - squash := false - for _, tag := range tagParts[1:] { - if tag == "squash" { - squash = true - break + if !squash { + for _, tag := range tagParts[1:] { + if tag == "squash" { + squash = true + break + } + } + if squash && v.Kind() != reflect.Struct { + return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } - } - if squash && v.Kind() != reflect.Struct { - return fmt.Errorf("cannot squash non-struct type '%s'", v.Type()) } switch v.Kind() { @@ -805,8 +928,8 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) valElemType := valType.Elem() sliceType := reflect.SliceOf(valElemType) - valSlice := val - if valSlice.IsNil() || d.config.ZeroFields { + // If we have a non array/slice type then we first attempt to convert. + if dataValKind != reflect.Array && dataValKind != reflect.Slice { if d.config.WeaklyTypedInput { switch { // Slice and array we use the normal logic @@ -833,18 +956,17 @@ func (d *Decoder) decodeSlice(name string, data interface{}, val reflect.Value) } } - // Check input type - if dataValKind != reflect.Array && dataValKind != reflect.Slice { - return fmt.Errorf( - "'%s': source data must be an array or slice, got %s", name, dataValKind) + return fmt.Errorf( + "'%s': source data must be an array or slice, got %s", name, dataValKind) + } - } - - // If the input value is empty, then don't allocate since non-nil != nil - if dataVal.Len() == 0 { - return nil - } + // If the input value is nil, then don't allocate since empty != nil + if dataVal.IsNil() { + return nil + } + valSlice := val + if valSlice.IsNil() || d.config.ZeroFields { // Make a new slice to hold our result, same size as the original data. valSlice = reflect.MakeSlice(sliceType, dataVal.Len(), dataVal.Len()) } @@ -1005,6 +1127,11 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e field reflect.StructField val reflect.Value } + + // remainField is set to a valid field set with the "remain" tag if + // we are keeping track of remaining values. + var remainField *field + fields := []field{} for len(structs) > 0 { structVal := structs[0] @@ -1017,13 +1144,21 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e fieldKind := fieldType.Type.Kind() // If "squash" is specified in the tag, we squash the field down. - squash := false + squash := d.config.Squash && fieldKind == reflect.Struct + remain := false + + // We always parse the tags cause we're looking for other tags too tagParts := strings.Split(fieldType.Tag.Get(d.config.TagName), ",") for _, tag := range tagParts[1:] { if tag == "squash" { squash = true break } + + if tag == "remain" { + remain = true + break + } } if squash { @@ -1036,8 +1171,14 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e continue } - // Normal struct field, store it away - fields = append(fields, field{fieldType, structVal.Field(i)}) + // Build our field + fieldCurrent := field{fieldType, structVal.Field(i)} + if remain { + remainField = &fieldCurrent + } else { + // Normal struct field, store it away + fields = append(fields, field{fieldType, structVal.Field(i)}) + } } } @@ -1078,9 +1219,6 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } } - // Delete the key we're using from the unused map so we stop tracking - delete(dataValKeysUnused, rawMapKey.Interface()) - if !fieldValue.IsValid() { // This should never happen panic("field is not valid") @@ -1092,6 +1230,9 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e continue } + // Delete the key we're using from the unused map so we stop tracking + delete(dataValKeysUnused, rawMapKey.Interface()) + // If the name is empty string, then we're at the root, and we // don't dot-join the fields. if name != "" { @@ -1103,6 +1244,25 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e } } + // If we have a "remain"-tagged field and we have unused keys then + // we put the unused keys directly into the remain field. + if remainField != nil && len(dataValKeysUnused) > 0 { + // Build a map of only the unused values + remain := map[interface{}]interface{}{} + for key := range dataValKeysUnused { + remain[key] = dataVal.MapIndex(reflect.ValueOf(key)).Interface() + } + + // Decode it as-if we were just decoding this map onto our map. + if err := d.decodeMap(name, remain, remainField.val); err != nil { + errors = appendErrors(errors, err) + } + + // Set the map to nil so we have none so that the next check will + // not error (ErrorUnused) + dataValKeysUnused = nil + } + if d.config.ErrorUnused && len(dataValKeysUnused) > 0 { keys := make([]string, 0, len(dataValKeysUnused)) for rawKey := range dataValKeysUnused { diff --git a/vendor/modules.txt b/vendor/modules.txt index f56bb8e5bf..5c69ed58ac 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -489,6 +489,7 @@ 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 @@ -634,7 +635,7 @@ github.com/mitchellh/gox github.com/mitchellh/hashstructure # github.com/mitchellh/iochan v1.0.0 github.com/mitchellh/iochan -# github.com/mitchellh/mapstructure v1.1.2 +# github.com/mitchellh/mapstructure v1.2.2 github.com/mitchellh/mapstructure # github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 github.com/mitchellh/pointerstructure