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.
This commit is contained in:
Michael Golowka 2020-05-27 12:28:00 -06:00 committed by GitHub
parent d8c52a4b44
commit b52950f884
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 5376 additions and 410 deletions

View File

@ -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

View File

@ -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=

2
go.mod
View File

@ -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

3
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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=

150
sdk/helper/random/parser.go Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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()
}
})
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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] }

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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",

View File

@ -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 {

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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",
"",

View File

@ -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<name>.+)/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<name>.+)$",
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.",
},
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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=

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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",

View File

@ -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 {

View File

@ -6,3 +6,4 @@ go:
script:
- go test
- go test -bench . -benchmem

View File

@ -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

View File

@ -1 +1,3 @@
module github.com/mitchellh/mapstructure
go 1.14

View File

@ -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 {

3
vendor/modules.txt vendored
View File

@ -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