secrets/database: adds ability to manage alternative credential types and configuration (#15376)
This commit is contained in:
parent
60acf9ad6e
commit
d3629ab49d
|
@ -0,0 +1,169 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/vault/helper/random"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// passwordGenerator generates password credentials.
|
||||
// A zero value passwordGenerator is usable.
|
||||
type passwordGenerator struct {
|
||||
// PasswordPolicy is the named password policy used to generate passwords.
|
||||
// If empty (default), a random string of 20 characters will be generated.
|
||||
PasswordPolicy string `mapstructure:"password_policy,omitempty"`
|
||||
}
|
||||
|
||||
// newPasswordGenerator returns a new passwordGenerator using the given config.
|
||||
// Default values will be set on the returned passwordGenerator if not provided
|
||||
// in the config.
|
||||
func newPasswordGenerator(config map[string]interface{}) (passwordGenerator, error) {
|
||||
var pg passwordGenerator
|
||||
if err := mapstructure.WeakDecode(config, &pg); err != nil {
|
||||
return pg, err
|
||||
}
|
||||
|
||||
return pg, nil
|
||||
}
|
||||
|
||||
// Generate generates a password credential using the configured password policy.
|
||||
// Returns the generated password or an error.
|
||||
func (pg passwordGenerator) generate(ctx context.Context, b *databaseBackend, wrapper databaseVersionWrapper) (string, error) {
|
||||
if !wrapper.isV5() && !wrapper.isV4() {
|
||||
return "", fmt.Errorf("no underlying database specified")
|
||||
}
|
||||
|
||||
// The database plugin generates the password if its interface is v4
|
||||
if wrapper.isV4() {
|
||||
password, err := wrapper.v4.GenerateCredentials(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
if pg.PasswordPolicy == "" {
|
||||
return random.DefaultStringGenerator.Generate(ctx, b.GetRandomReader())
|
||||
}
|
||||
return b.System().GeneratePasswordFromPolicy(ctx, pg.PasswordPolicy)
|
||||
}
|
||||
|
||||
// configMap returns the configuration of the passwordGenerator
|
||||
// as a map from string to string.
|
||||
func (pg passwordGenerator) configMap() (map[string]interface{}, error) {
|
||||
config := make(map[string]interface{})
|
||||
if err := mapstructure.WeakDecode(pg, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// rsaKeyGenerator generates RSA key pair credentials.
|
||||
// A zero value rsaKeyGenerator is usable.
|
||||
type rsaKeyGenerator struct {
|
||||
// Format is the output format of the generated private key.
|
||||
// Options include: 'pkcs8' (default)
|
||||
Format string `mapstructure:"format,omitempty"`
|
||||
|
||||
// KeyBits is the bit size of the RSA key to generate.
|
||||
// Options include: 2048 (default), 3072, and 4096
|
||||
KeyBits int `mapstructure:"key_bits,omitempty"`
|
||||
}
|
||||
|
||||
// newRSAKeyGenerator returns a new rsaKeyGenerator using the given config.
|
||||
// Default values will be set on the returned rsaKeyGenerator if not provided
|
||||
// in the given config.
|
||||
func newRSAKeyGenerator(config map[string]interface{}) (rsaKeyGenerator, error) {
|
||||
var kg rsaKeyGenerator
|
||||
if err := mapstructure.WeakDecode(config, &kg); err != nil {
|
||||
return kg, err
|
||||
}
|
||||
|
||||
switch strings.ToLower(kg.Format) {
|
||||
case "":
|
||||
kg.Format = "pkcs8"
|
||||
case "pkcs8":
|
||||
default:
|
||||
return kg, fmt.Errorf("invalid format: %v", kg.Format)
|
||||
}
|
||||
|
||||
switch kg.KeyBits {
|
||||
case 0:
|
||||
kg.KeyBits = 2048
|
||||
case 2048, 3072, 4096:
|
||||
default:
|
||||
return kg, fmt.Errorf("invalid key_bits: %v", kg.KeyBits)
|
||||
}
|
||||
|
||||
return kg, nil
|
||||
}
|
||||
|
||||
// Generate generates an RSA key pair. Returns a PEM-encoded, PKIX marshaled
|
||||
// public key and a PEM-encoded private key marshaled into the configuration
|
||||
// format (in that order) or an error.
|
||||
func (kg *rsaKeyGenerator) generate(r io.Reader) ([]byte, []byte, error) {
|
||||
reader := rand.Reader
|
||||
if r != nil {
|
||||
reader = r
|
||||
}
|
||||
|
||||
var keyBits int
|
||||
switch kg.KeyBits {
|
||||
case 0:
|
||||
keyBits = 2048
|
||||
case 2048, 3072, 4096:
|
||||
keyBits = kg.KeyBits
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid key_bits: %v", kg.KeyBits)
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(reader, keyBits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
public, err := x509.MarshalPKIXPublicKey(key.Public())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var private []byte
|
||||
switch strings.ToLower(kg.Format) {
|
||||
case "", "pkcs8":
|
||||
private, err = x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("invalid format: %v", kg.Format)
|
||||
}
|
||||
|
||||
publicBlock := &pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: public,
|
||||
}
|
||||
privateBlock := &pem.Block{
|
||||
Type: "PRIVATE KEY",
|
||||
Bytes: private,
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(publicBlock), pem.EncodeToMemory(privateBlock), nil
|
||||
}
|
||||
|
||||
// configMap returns the configuration of the rsaKeyGenerator
|
||||
// as a map from string to string.
|
||||
func (kg rsaKeyGenerator) configMap() (map[string]interface{}, error) {
|
||||
config := make(map[string]interface{})
|
||||
if err := mapstructure.WeakDecode(kg, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
|
@ -0,0 +1,543 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/helper/base62"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func Test_newPasswordGenerator(t *testing.T) {
|
||||
type args struct {
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want passwordGenerator
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "newPasswordGenerator with nil config",
|
||||
args: args{
|
||||
config: nil,
|
||||
},
|
||||
want: passwordGenerator{
|
||||
PasswordPolicy: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newPasswordGenerator without password_policy",
|
||||
args: args{
|
||||
config: map[string]interface{}{},
|
||||
},
|
||||
want: passwordGenerator{
|
||||
PasswordPolicy: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newPasswordGenerator with password_policy",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
},
|
||||
want: passwordGenerator{
|
||||
PasswordPolicy: "test-policy",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := newPasswordGenerator(tt.args.config)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_newRSAKeyGenerator(t *testing.T) {
|
||||
type args struct {
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want rsaKeyGenerator
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "newRSAKeyGenerator with nil config",
|
||||
args: args{
|
||||
config: nil,
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with empty config",
|
||||
args: args{
|
||||
config: map[string]interface{}{},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with zero value format",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"format": "",
|
||||
},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with zero value key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "0",
|
||||
},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with format",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with format case insensitive",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"format": "PKCS8",
|
||||
},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "PKCS8",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with 3072 key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "3072",
|
||||
},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 3072,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with 4096 key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "4096",
|
||||
},
|
||||
},
|
||||
want: rsaKeyGenerator{
|
||||
Format: "pkcs8",
|
||||
KeyBits: 4096,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with invalid key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "4097",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "newRSAKeyGenerator with invalid format",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"format": "pkcs1",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := newRSAKeyGenerator(tt.args.config)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_passwordGenerator_generate(t *testing.T) {
|
||||
config := logical.TestBackendConfig()
|
||||
b := Backend(config)
|
||||
b.Setup(context.Background(), config)
|
||||
|
||||
type args struct {
|
||||
config map[string]interface{}
|
||||
mock func() interface{}
|
||||
passGen logical.PasswordGenerator
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantRegexp string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "wrapper missing v4 and v5 interface",
|
||||
args: args{
|
||||
mock: func() interface{} {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "v4: generate password using GenerateCredentials",
|
||||
args: args{
|
||||
mock: func() interface{} {
|
||||
v4Mock := new(mockLegacyDatabase)
|
||||
v4Mock.On("GenerateCredentials", mock.Anything).
|
||||
Return("v4-generated-password", nil).
|
||||
Times(1)
|
||||
return v4Mock
|
||||
},
|
||||
},
|
||||
wantRegexp: "^v4-generated-password$",
|
||||
},
|
||||
{
|
||||
name: "v5: generate password without policy",
|
||||
args: args{
|
||||
mock: func() interface{} {
|
||||
return new(mockNewDatabase)
|
||||
},
|
||||
},
|
||||
wantRegexp: "^[a-zA-Z0-9-]{20}$",
|
||||
},
|
||||
{
|
||||
name: "v5: generate password with non-existing policy",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"password_policy": "not-created",
|
||||
},
|
||||
mock: func() interface{} {
|
||||
return new(mockNewDatabase)
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "v5: generate password with existing policy",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
mock: func() interface{} {
|
||||
return new(mockNewDatabase)
|
||||
},
|
||||
passGen: func() (string, error) {
|
||||
return base62.Random(30)
|
||||
},
|
||||
},
|
||||
wantRegexp: "^[a-zA-Z0-9]{30}$",
|
||||
},
|
||||
{
|
||||
name: "v5: generate password with existing policy static",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
mock: func() interface{} {
|
||||
return new(mockNewDatabase)
|
||||
},
|
||||
passGen: func() (string, error) {
|
||||
return "policy-generated-password", nil
|
||||
},
|
||||
},
|
||||
wantRegexp: "^policy-generated-password$",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Set up the version wrapper with a mock database implementation
|
||||
wrapper := databaseVersionWrapper{}
|
||||
switch m := tt.args.mock().(type) {
|
||||
case *mockNewDatabase:
|
||||
wrapper.v5 = m
|
||||
case *mockLegacyDatabase:
|
||||
wrapper.v4 = m
|
||||
}
|
||||
|
||||
// Set the password policy for the test case
|
||||
config.System.(*logical.StaticSystemView).SetPasswordPolicy(
|
||||
"test-policy", tt.args.passGen)
|
||||
|
||||
// Generate the password
|
||||
pg, err := newPasswordGenerator(tt.args.config)
|
||||
got, err := pg.generate(context.Background(), b, wrapper)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.Regexp(t, tt.wantRegexp, got)
|
||||
|
||||
// Assert all expected calls took place on the mock
|
||||
if m, ok := wrapper.v5.(*mockNewDatabase); ok {
|
||||
m.AssertExpectations(t)
|
||||
}
|
||||
if m, ok := wrapper.v4.(*mockLegacyDatabase); ok {
|
||||
m.AssertExpectations(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_passwordGenerator_configMap(t *testing.T) {
|
||||
type args struct {
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "nil config results in empty map",
|
||||
args: args{
|
||||
config: nil,
|
||||
},
|
||||
want: map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
name: "empty config results in empty map",
|
||||
args: args{
|
||||
config: map[string]interface{}{},
|
||||
},
|
||||
want: map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
name: "input config is equal to output config",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
pg, err := newPasswordGenerator(tt.args.config)
|
||||
assert.NoError(t, err)
|
||||
cm, err := pg.configMap()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, cm)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_rsaKeyGenerator_generate(t *testing.T) {
|
||||
type args struct {
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "generate RSA key with nil default config",
|
||||
args: args{
|
||||
config: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate RSA key with empty default config",
|
||||
args: args{
|
||||
config: map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate RSA key with 2048 key_bits and format",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "2048",
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate RSA key with 2048 key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "2048",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate RSA key with 3072 key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "3072",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate RSA key with 4096 key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": "4096",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Generate the RSA key pair
|
||||
kg, err := newRSAKeyGenerator(tt.args.config)
|
||||
public, private, err := kg.generate(rand.Reader)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, public)
|
||||
assert.NotEmpty(t, private)
|
||||
|
||||
// Decode the public and private key PEMs
|
||||
pubBlock, pubRest := pem.Decode(public)
|
||||
privBlock, privRest := pem.Decode(private)
|
||||
assert.NotNil(t, pubBlock)
|
||||
assert.Empty(t, pubRest)
|
||||
assert.Equal(t, "PUBLIC KEY", pubBlock.Type)
|
||||
assert.NotNil(t, privBlock)
|
||||
assert.Empty(t, privRest)
|
||||
assert.Equal(t, "PRIVATE KEY", privBlock.Type)
|
||||
|
||||
// Assert that we can parse the public key PEM block
|
||||
pub, err := x509.ParsePKIXPublicKey(pubBlock.Bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pub)
|
||||
assert.IsType(t, &rsa.PublicKey{}, pub)
|
||||
|
||||
// Assert that we can parse the private key PEM block in
|
||||
// the configured format
|
||||
switch kg.Format {
|
||||
case "pkcs8":
|
||||
priv, err := x509.ParsePKCS8PrivateKey(privBlock.Bytes)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, priv)
|
||||
assert.IsType(t, &rsa.PrivateKey{}, priv)
|
||||
default:
|
||||
t.Fatal("unknown format")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_rsaKeyGenerator_configMap(t *testing.T) {
|
||||
type args struct {
|
||||
config map[string]interface{}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "nil config results in defaults",
|
||||
args: args{
|
||||
config: nil,
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty config results in defaults",
|
||||
args: args{
|
||||
config: map[string]interface{}{},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config with format",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 2048,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config with key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"key_bits": 4096,
|
||||
},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 4096,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "config with format and key_bits",
|
||||
args: args{
|
||||
config: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 3072,
|
||||
},
|
||||
},
|
||||
want: map[string]interface{}{
|
||||
"format": "pkcs8",
|
||||
"key_bits": 3072,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
kg, err := newRSAKeyGenerator(tt.args.config)
|
||||
assert.NoError(t, err)
|
||||
cm, err := kg.configMap()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, cm)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -33,6 +33,22 @@ type DatabaseConfig struct {
|
|||
PasswordPolicy string `json:"password_policy" structs:"password_policy" mapstructure:"password_policy"`
|
||||
}
|
||||
|
||||
func (c *DatabaseConfig) SupportsCredentialType(credentialType v5.CredentialType) bool {
|
||||
credTypes, ok := c.ConnectionDetails[v5.SupportedCredentialTypesKey].([]interface{})
|
||||
if !ok {
|
||||
// Default to supporting CredentialTypePassword for database plugins that
|
||||
// don't specify supported credential types in the initialization response
|
||||
return credentialType == v5.CredentialTypePassword
|
||||
}
|
||||
|
||||
for _, ct := range credTypes {
|
||||
if ct == credentialType.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pathResetConnection configures a path to reset a plugin.
|
||||
func pathResetConnection(b *databaseBackend) *framework.Path {
|
||||
return &framework.Path{
|
||||
|
@ -347,7 +363,7 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc {
|
|||
|
||||
resp := &logical.Response{}
|
||||
|
||||
// This is a simple test to check for passwords in the connection_url paramater. If one exists,
|
||||
// This is a simple test to check for passwords in the connection_url parameter. If one exists,
|
||||
// warn the user to use templated url string
|
||||
if connURLRaw, ok := config.ConnectionDetails["connection_url"]; ok {
|
||||
if connURL, err := url.Parse(connURLRaw.(string)); err == nil {
|
||||
|
|
|
@ -72,6 +72,12 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
|
|||
return nil, fmt.Errorf("%q is not an allowed role", name)
|
||||
}
|
||||
|
||||
// If the plugin doesn't support the credential type, return an error
|
||||
if !dbConfig.SupportsCredentialType(role.CredentialType) {
|
||||
return logical.ErrorResponse("unsupported credential_type: %q",
|
||||
role.CredentialType.String()), nil
|
||||
}
|
||||
|
||||
// Get the Database object
|
||||
dbi, err := b.GetConnection(ctx, req.Storage, role.DBName)
|
||||
if err != nil {
|
||||
|
@ -90,12 +96,6 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
|
|||
// to ensure the database credential does not expire before the lease
|
||||
expiration = expiration.Add(5 * time.Second)
|
||||
|
||||
password, err := dbi.database.GeneratePassword(ctx, b.System(), dbConfig.PasswordPolicy)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return nil, fmt.Errorf("unable to generate password: %w", err)
|
||||
}
|
||||
|
||||
newUserReq := v5.NewUserRequest{
|
||||
UsernameConfig: v5.UsernameMetadata{
|
||||
DisplayName: req.DisplayName,
|
||||
|
@ -107,21 +107,70 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
|
|||
RollbackStatements: v5.Statements{
|
||||
Commands: role.Statements.Rollback,
|
||||
},
|
||||
Password: password,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
// Overwriting the password in the event this is a legacy database plugin and the provided password is ignored
|
||||
respData := make(map[string]interface{})
|
||||
|
||||
// Generate the credential based on the role's credential type
|
||||
switch role.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
generator, err := newPasswordGenerator(role.CredentialConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Fall back to database config-level password policy if not set on role
|
||||
if generator.PasswordPolicy == "" {
|
||||
generator.PasswordPolicy = dbConfig.PasswordPolicy
|
||||
}
|
||||
|
||||
// Generate the password
|
||||
password, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return nil, fmt.Errorf("failed to generate password: %s", err)
|
||||
}
|
||||
|
||||
// Set input credential
|
||||
newUserReq.CredentialType = v5.CredentialTypePassword
|
||||
newUserReq.Password = password
|
||||
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
generator, err := newRSAKeyGenerator(role.CredentialConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Generate the RSA key pair
|
||||
public, private, err := generator.generate(b.GetRandomReader())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
}
|
||||
|
||||
// Set input credential
|
||||
newUserReq.CredentialType = v5.CredentialTypeRSAPrivateKey
|
||||
newUserReq.PublicKey = public
|
||||
|
||||
// Set output credential
|
||||
respData["rsa_private_key"] = string(private)
|
||||
}
|
||||
|
||||
// Overwriting the password in the event this is a legacy database
|
||||
// plugin and the provided password is ignored
|
||||
newUserResp, password, err := dbi.database.NewUser(ctx, newUserReq)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return nil, err
|
||||
}
|
||||
respData["username"] = newUserResp.Username
|
||||
|
||||
respData := map[string]interface{}{
|
||||
"username": newUserResp.Username,
|
||||
"password": password,
|
||||
// Database plugins using the v4 interface generate and return the password.
|
||||
// Set the password response to what is returned by the NewUser request.
|
||||
if role.CredentialType == v5.CredentialTypePassword {
|
||||
respData["password"] = password
|
||||
}
|
||||
|
||||
internal := map[string]interface{}{
|
||||
"username": newUserResp.Username,
|
||||
"role": name,
|
||||
|
@ -158,14 +207,22 @@ func (b *databaseBackend) pathStaticCredsRead() framework.OperationFunc {
|
|||
return nil, fmt.Errorf("%q is not an allowed role", name)
|
||||
}
|
||||
|
||||
respData := map[string]interface{}{
|
||||
"username": role.StaticAccount.Username,
|
||||
"ttl": role.StaticAccount.CredentialTTL().Seconds(),
|
||||
"rotation_period": role.StaticAccount.RotationPeriod.Seconds(),
|
||||
"last_vault_rotation": role.StaticAccount.LastVaultRotation,
|
||||
}
|
||||
|
||||
switch role.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
respData["password"] = role.StaticAccount.Password
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
respData["rsa_private_key"] = string(role.StaticAccount.PrivateKey)
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"username": role.StaticAccount.Username,
|
||||
"password": role.StaticAccount.Password,
|
||||
"ttl": role.StaticAccount.PasswordTTL().Seconds(),
|
||||
"rotation_period": role.StaticAccount.RotationPeriod.Seconds(),
|
||||
"last_vault_rotation": role.StaticAccount.LastVaultRotation,
|
||||
},
|
||||
Data: respData,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
|
||||
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
"github.com/hashicorp/vault/sdk/helper/locksutil"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
|
@ -88,6 +89,16 @@ func fieldsForType(roleType string) map[string]*framework.FieldSchema {
|
|||
Type: framework.TypeString,
|
||||
Description: "Name of the database this role acts on.",
|
||||
},
|
||||
"credential_type": {
|
||||
Type: framework.TypeString,
|
||||
Description: "The type of credential to manage. Options include: " +
|
||||
"'password', 'rsa_private_key'. Defaults to 'password'.",
|
||||
Default: "password",
|
||||
},
|
||||
"credential_config": {
|
||||
Type: framework.TypeKVPairs,
|
||||
Description: "The configuration for the given credential_type.",
|
||||
},
|
||||
}
|
||||
|
||||
// Get the fields that are specific to the type of role, and add them to the
|
||||
|
@ -252,6 +263,7 @@ func (b *databaseBackend) pathStaticRoleRead(ctx context.Context, req *logical.R
|
|||
data := map[string]interface{}{
|
||||
"db_name": role.DBName,
|
||||
"rotation_statements": role.Statements.Rotation,
|
||||
"credential_type": role.CredentialType.String(),
|
||||
}
|
||||
|
||||
// guard against nil StaticAccount; shouldn't happen but we'll be safe
|
||||
|
@ -264,6 +276,9 @@ func (b *databaseBackend) pathStaticRoleRead(ctx context.Context, req *logical.R
|
|||
}
|
||||
}
|
||||
|
||||
if len(role.CredentialConfig) > 0 {
|
||||
data["credential_config"] = role.CredentialConfig
|
||||
}
|
||||
if len(role.Statements.Rotation) == 0 {
|
||||
data["rotation_statements"] = []string{}
|
||||
}
|
||||
|
@ -290,6 +305,10 @@ func (b *databaseBackend) pathRoleRead(ctx context.Context, req *logical.Request
|
|||
"renew_statements": role.Statements.Renewal,
|
||||
"default_ttl": role.DefaultTTL.Seconds(),
|
||||
"max_ttl": role.MaxTTL.Seconds(),
|
||||
"credential_type": role.CredentialType.String(),
|
||||
}
|
||||
if len(role.CredentialConfig) > 0 {
|
||||
data["credential_config"] = role.CredentialConfig
|
||||
}
|
||||
if len(role.Statements.Creation) == 0 {
|
||||
data["creation_statements"] = []string{}
|
||||
|
@ -356,6 +375,23 @@ func (b *databaseBackend) pathRoleCreateUpdate(ctx context.Context, req *logical
|
|||
if role.DBName == "" {
|
||||
return logical.ErrorResponse("database name is required"), nil
|
||||
}
|
||||
|
||||
if credentialTypeRaw, ok := data.GetOk("credential_type"); ok {
|
||||
credentialType := credentialTypeRaw.(string)
|
||||
if err := role.setCredentialType(credentialType); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
}
|
||||
|
||||
var credentialConfig map[string]string
|
||||
if raw, ok := data.GetOk("credential_config"); ok {
|
||||
credentialConfig = raw.(map[string]string)
|
||||
} else if req.Operation == logical.CreateOperation {
|
||||
credentialConfig = data.Get("credential_config").(map[string]string)
|
||||
}
|
||||
if err := role.setCredentialConfig(credentialConfig); err != nil {
|
||||
return logical.ErrorResponse("credential_config validation failed: %s", err), nil
|
||||
}
|
||||
}
|
||||
|
||||
// Statements
|
||||
|
@ -447,7 +483,7 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||
|
||||
// createRole is a boolean to indicate if this is a new role creation. This is
|
||||
// can be used later by database plugins that distinguish between creating and
|
||||
// updating roles, and may use seperate statements depending on the context.
|
||||
// updating roles, and may use separate statements depending on the context.
|
||||
createRole := (req.Operation == logical.CreateOperation)
|
||||
if role == nil {
|
||||
role = &roleEntry{
|
||||
|
@ -499,6 +535,23 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||
role.Statements.Rotation = data.Get("rotation_statements").([]string)
|
||||
}
|
||||
|
||||
if credentialTypeRaw, ok := data.GetOk("credential_type"); ok {
|
||||
credentialType := credentialTypeRaw.(string)
|
||||
if err := role.setCredentialType(credentialType); err != nil {
|
||||
return logical.ErrorResponse(err.Error()), nil
|
||||
}
|
||||
}
|
||||
|
||||
var credentialConfig map[string]string
|
||||
if raw, ok := data.GetOk("credential_config"); ok {
|
||||
credentialConfig = raw.(map[string]string)
|
||||
} else if req.Operation == logical.CreateOperation {
|
||||
credentialConfig = data.Get("credential_config").(map[string]string)
|
||||
}
|
||||
if err := role.setCredentialConfig(credentialConfig); err != nil {
|
||||
return logical.ErrorResponse("credential_config validation failed: %s", err), nil
|
||||
}
|
||||
|
||||
// lvr represents the roles' LastVaultRotation
|
||||
lvr := role.StaticAccount.LastVaultRotation
|
||||
|
||||
|
@ -558,22 +611,85 @@ func (b *databaseBackend) pathStaticRoleCreateUpdate(ctx context.Context, req *l
|
|||
}
|
||||
|
||||
type roleEntry struct {
|
||||
DBName string `json:"db_name"`
|
||||
Statements v4.Statements `json:"statements"`
|
||||
DefaultTTL time.Duration `json:"default_ttl"`
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
StaticAccount *staticAccount `json:"static_account" mapstructure:"static_account"`
|
||||
DBName string `json:"db_name"`
|
||||
Statements v4.Statements `json:"statements"`
|
||||
DefaultTTL time.Duration `json:"default_ttl"`
|
||||
MaxTTL time.Duration `json:"max_ttl"`
|
||||
CredentialType v5.CredentialType `json:"credential_type"`
|
||||
CredentialConfig map[string]interface{} `json:"credential_config"`
|
||||
StaticAccount *staticAccount `json:"static_account" mapstructure:"static_account"`
|
||||
}
|
||||
|
||||
// setCredentialType sets the credential type for the role given its string form.
|
||||
// Returns an error if the given credential type string is unknown.
|
||||
func (r *roleEntry) setCredentialType(credentialType string) error {
|
||||
switch credentialType {
|
||||
case v5.CredentialTypePassword.String():
|
||||
r.CredentialType = v5.CredentialTypePassword
|
||||
case v5.CredentialTypeRSAPrivateKey.String():
|
||||
r.CredentialType = v5.CredentialTypeRSAPrivateKey
|
||||
default:
|
||||
return fmt.Errorf("invalid credential_type %q", credentialType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setCredentialConfig validates and sets the credential configuration
|
||||
// for the role using the role's credential type. It will also populate
|
||||
// all default values. Returns an error if the configuration is invalid.
|
||||
func (r *roleEntry) setCredentialConfig(config map[string]string) error {
|
||||
c := make(map[string]interface{})
|
||||
for k, v := range config {
|
||||
c[k] = v
|
||||
}
|
||||
|
||||
switch r.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
generator, err := newPasswordGenerator(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm, err := generator.configMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cm) > 0 {
|
||||
r.CredentialConfig = cm
|
||||
}
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
generator, err := newRSAKeyGenerator(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm, err := generator.configMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cm) > 0 {
|
||||
r.CredentialConfig = cm
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type staticAccount struct {
|
||||
// Username to create or assume management for static accounts
|
||||
Username string `json:"username"`
|
||||
|
||||
// Password is the current password for static accounts. As an input, this is
|
||||
// used/required when trying to assume management of an existing static
|
||||
// account. Return this on credential request if it exists.
|
||||
// Password is the current password credential for static accounts. As an input,
|
||||
// this is used/required when trying to assume management of an existing static
|
||||
// account. Returned on credential request if the role's credential type is
|
||||
// CredentialTypePassword.
|
||||
Password string `json:"password"`
|
||||
|
||||
// PrivateKey is the current private key credential for static accounts. As an input,
|
||||
// this is used/required when trying to assume management of an existing static
|
||||
// account. Returned on credential request if the role's credential type is
|
||||
// CredentialTypeRSAPrivateKey.
|
||||
PrivateKey []byte `json:"private_key"`
|
||||
|
||||
// LastVaultRotation represents the last time Vault rotated the password
|
||||
LastVaultRotation time.Time `json:"last_vault_rotation"`
|
||||
|
||||
|
@ -593,7 +709,7 @@ func (s *staticAccount) NextRotationTime() time.Time {
|
|||
return s.LastVaultRotation.Add(s.RotationPeriod)
|
||||
}
|
||||
|
||||
// PasswordTTL calculates the approximate time remaining until the password is
|
||||
// CredentialTTL calculates the approximate time remaining until the credential is
|
||||
// no longer valid. This is approximate because the periodic rotation is only
|
||||
// checked approximately every 5 seconds, and each rotation can take a small
|
||||
// amount of time to process. This can result in a negative TTL time while the
|
||||
|
@ -601,7 +717,7 @@ func (s *staticAccount) NextRotationTime() time.Time {
|
|||
// TTL is negative, zero is returned. Users should not trust passwords with a
|
||||
// Zero TTL, as they are likely in the process of being rotated and will quickly
|
||||
// be invalidated.
|
||||
func (s *staticAccount) PasswordTTL() time.Duration {
|
||||
func (s *staticAccount) CredentialTTL() time.Duration {
|
||||
next := s.NextRotationTime()
|
||||
ttl := next.Sub(time.Now()).Round(time.Second)
|
||||
if ttl < 0 {
|
||||
|
@ -662,7 +778,7 @@ rollback a change if needed.
|
|||
const pathStaticRoleHelpDesc = `
|
||||
This path lets you manage the static roles that can be created with this
|
||||
backend. Static Roles are associated with a single database user, and manage the
|
||||
password based on a rotation period, automatically rotating the password.
|
||||
credential based on a rotation period, automatically rotating the credential.
|
||||
|
||||
The "db_name" parameter is required and configures the name of the database
|
||||
connection to use.
|
||||
|
@ -675,7 +791,11 @@ and "}}" to be replaced.
|
|||
|
||||
* "name" - The random username generated for the DB user.
|
||||
|
||||
* "password" - The random password generated for the DB user.
|
||||
* "password" - The random password generated for the DB user. Populated if the
|
||||
static role's credential_type is 'password'.
|
||||
|
||||
* "public_key" - The public key generated for the DB user. Populated if the
|
||||
static role's credential_type is 'rsa_private_key'.
|
||||
|
||||
Example of a decent creation_statements for a postgresql database plugin:
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -12,10 +13,193 @@ import (
|
|||
postgreshelper "github.com/hashicorp/vault/helper/testhelpers/postgresql"
|
||||
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var dataKeys = []string{"username", "password", "last_vault_rotation", "rotation_period"}
|
||||
func TestBackend_Roles_CredentialTypes(t *testing.T) {
|
||||
config := logical.TestBackendConfig()
|
||||
config.System = logical.TestSystemView()
|
||||
config.StorageView = &logical.InmemStorage{}
|
||||
b, err := Factory(context.Background(), config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
credentialType v5.CredentialType
|
||||
credentialConfig map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
expectedResp map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "role with invalid credential type",
|
||||
args: args{
|
||||
credentialType: v5.CredentialType(10),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "role with invalid credential type and valid credential config",
|
||||
args: args{
|
||||
credentialType: v5.CredentialType(7),
|
||||
credentialConfig: map[string]string{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "role with password credential type",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypePassword,
|
||||
},
|
||||
expectedResp: map[string]interface{}{
|
||||
"credential_type": v5.CredentialTypePassword.String(),
|
||||
"credential_config": nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role with password credential type and configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypePassword,
|
||||
credentialConfig: map[string]string{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
},
|
||||
expectedResp: map[string]interface{}{
|
||||
"credential_type": v5.CredentialTypePassword.String(),
|
||||
"credential_config": map[string]interface{}{
|
||||
"password_policy": "test-policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role with rsa_private_key credential type and default configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
},
|
||||
expectedResp: map[string]interface{}{
|
||||
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
|
||||
"credential_config": map[string]interface{}{
|
||||
"key_bits": json.Number("2048"),
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role with rsa_private_key credential type and 2048 bit configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
credentialConfig: map[string]string{
|
||||
"key_bits": "2048",
|
||||
},
|
||||
},
|
||||
expectedResp: map[string]interface{}{
|
||||
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
|
||||
"credential_config": map[string]interface{}{
|
||||
"key_bits": json.Number("2048"),
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role with rsa_private_key credential type and 3072 bit configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
credentialConfig: map[string]string{
|
||||
"key_bits": "3072",
|
||||
},
|
||||
},
|
||||
expectedResp: map[string]interface{}{
|
||||
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
|
||||
"credential_config": map[string]interface{}{
|
||||
"key_bits": json.Number("3072"),
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role with rsa_private_key credential type and 4096 bit configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
credentialConfig: map[string]string{
|
||||
"key_bits": "4096",
|
||||
},
|
||||
},
|
||||
expectedResp: map[string]interface{}{
|
||||
"credential_type": v5.CredentialTypeRSAPrivateKey.String(),
|
||||
"credential_config": map[string]interface{}{
|
||||
"key_bits": json.Number("4096"),
|
||||
"format": "pkcs8",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role with rsa_private_key credential type invalid key_bits configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
credentialConfig: map[string]string{
|
||||
"key_bits": "256",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "role with rsa_private_key credential type invalid format configuration",
|
||||
args: args{
|
||||
credentialType: v5.CredentialTypeRSAPrivateKey,
|
||||
credentialConfig: map[string]string{
|
||||
"format": "pkcs1",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := &logical.Request{
|
||||
Operation: logical.CreateOperation,
|
||||
Path: "roles/test",
|
||||
Storage: config.StorageView,
|
||||
Data: map[string]interface{}{
|
||||
"db_name": "test-database",
|
||||
"creation_statements": "CREATE USER {{name}}",
|
||||
"credential_type": tt.args.credentialType.String(),
|
||||
"credential_config": tt.args.credentialConfig,
|
||||
},
|
||||
}
|
||||
|
||||
// Create the role
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
if tt.wantErr {
|
||||
assert.True(t, resp.IsError(), "expected error")
|
||||
return
|
||||
}
|
||||
assert.False(t, resp.IsError())
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Read the role
|
||||
req.Operation = logical.ReadOperation
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
assert.False(t, resp.IsError())
|
||||
assert.Nil(t, err)
|
||||
for k, v := range tt.expectedResp {
|
||||
assert.Equal(t, v, resp.Data[k])
|
||||
}
|
||||
|
||||
// Delete the role
|
||||
req.Operation = logical.DeleteOperation
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
assert.False(t, resp.IsError())
|
||||
assert.Nil(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_StaticRole_Config(t *testing.T) {
|
||||
cluster, sys := getCluster(t)
|
||||
|
@ -154,6 +338,7 @@ func TestBackend_StaticRole_Config(t *testing.T) {
|
|||
|
||||
expected := tc.expected
|
||||
actual := make(map[string]interface{})
|
||||
dataKeys := []string{"username", "password", "last_vault_rotation", "rotation_period"}
|
||||
for _, key := range dataKeys {
|
||||
if v, ok := resp.Data[key]; ok {
|
||||
actual[key] = v
|
||||
|
@ -549,7 +734,6 @@ func TestWALsStillTrackedAfterUpdate(t *testing.T) {
|
|||
Storage: storage,
|
||||
Data: map[string]interface{}{
|
||||
"username": "hashicorp",
|
||||
"dn": "uid=hashicorp,ou=users,dc=hashicorp,dc=com",
|
||||
"rotation_period": "600s",
|
||||
},
|
||||
})
|
||||
|
|
|
@ -30,8 +30,8 @@ func pathRotateRootCredentials(b *databaseBackend) []*framework.Path {
|
|||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: pathCredsCreateReadHelpSyn,
|
||||
HelpDescription: pathCredsCreateReadHelpDesc,
|
||||
HelpSynopsis: pathRotateCredentialsUpdateHelpSyn,
|
||||
HelpDescription: pathRotateCredentialsUpdateHelpDesc,
|
||||
},
|
||||
{
|
||||
Pattern: "rotate-role/" + framework.GenericNameRegex("name"),
|
||||
|
@ -50,8 +50,8 @@ func pathRotateRootCredentials(b *databaseBackend) []*framework.Path {
|
|||
},
|
||||
},
|
||||
|
||||
HelpSynopsis: pathCredsCreateReadHelpSyn,
|
||||
HelpDescription: pathCredsCreateReadHelpDesc,
|
||||
HelpSynopsis: pathRotateRoleCredentialsUpdateHelpSyn,
|
||||
HelpDescription: pathRotateRoleCredentialsUpdateHelpDesc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -96,11 +96,18 @@ func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationF
|
|||
delete(b.connections, name)
|
||||
}()
|
||||
|
||||
generator, err := newPasswordGenerator(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
generator.PasswordPolicy = config.PasswordPolicy
|
||||
|
||||
// Generate new credentials
|
||||
oldPassword := config.ConnectionDetails["password"].(string)
|
||||
newPassword, err := dbi.database.GeneratePassword(ctx, b.System(), config.PasswordPolicy)
|
||||
newPassword, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return nil, fmt.Errorf("failed to generate password: %s", err)
|
||||
}
|
||||
config.ConnectionDetails["password"] = newPassword
|
||||
|
||||
|
@ -116,7 +123,8 @@ func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationF
|
|||
}
|
||||
|
||||
updateReq := v5.UpdateUserRequest{
|
||||
Username: rootUsername,
|
||||
Username: rootUsername,
|
||||
CredentialType: v5.CredentialTypePassword,
|
||||
Password: &v5.ChangePassword{
|
||||
NewPassword: newPassword,
|
||||
Statements: v5.Statements{
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/database/dbplugin"
|
||||
|
||||
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
||||
"github.com/hashicorp/vault/sdk/logical"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
@ -94,7 +93,8 @@ func (b *databaseBackend) rollbackDatabaseCredentials(ctx context.Context, confi
|
|||
}()
|
||||
|
||||
updateReq := v5.UpdateUserRequest{
|
||||
Username: entry.UserName,
|
||||
Username: entry.UserName,
|
||||
CredentialType: v5.CredentialTypePassword,
|
||||
Password: &v5.ChangePassword{
|
||||
NewPassword: entry.OldPassword,
|
||||
Statements: v5.Statements{
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
||||
"github.com/hashicorp/vault/sdk/framework"
|
||||
|
@ -125,9 +124,12 @@ func (b *databaseBackend) runTicker(ctx context.Context, queueTickInterval time.
|
|||
// setCredentialsWAL is used to store information in a WAL that can retry a
|
||||
// credential setting or rotation in the event of partial failure.
|
||||
type setCredentialsWAL struct {
|
||||
NewPassword string `json:"new_password"`
|
||||
RoleName string `json:"role_name"`
|
||||
Username string `json:"username"`
|
||||
CredentialType v5.CredentialType `json:"credential_type"`
|
||||
NewPassword string `json:"new_password"`
|
||||
NewPublicKey []byte `json:"new_public_key"`
|
||||
NewPrivateKey []byte `json:"new_private_key"`
|
||||
RoleName string `json:"role_name"`
|
||||
Username string `json:"username"`
|
||||
|
||||
LastVaultRotation time.Time `json:"last_vault_rotation"`
|
||||
|
||||
|
@ -136,6 +138,24 @@ type setCredentialsWAL struct {
|
|||
walCreatedAt int64 // Unix time at which the WAL was created.
|
||||
}
|
||||
|
||||
// credentialIsSet returns true if the credential associated with the
|
||||
// CredentialType field is properly set. See field comments to for a
|
||||
// mapping of CredentialType values to respective credential fields.
|
||||
func (w *setCredentialsWAL) credentialIsSet() bool {
|
||||
if w == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch w.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
return w.NewPassword != ""
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
return len(w.NewPublicKey) > 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// rotateCredentials sets a new password for a static account. This method is
|
||||
// invoked in the runTicker method, which is in it's own go-routine, and invoked
|
||||
// periodically (approximately every 5 seconds).
|
||||
|
@ -287,16 +307,17 @@ type setStaticAccountOutput struct {
|
|||
WALID string
|
||||
}
|
||||
|
||||
// setStaticAccount sets the password for a static account associated with a
|
||||
// setStaticAccount sets the credential for a static account associated with a
|
||||
// Role. This method does many things:
|
||||
// - verifies role exists and is in the allowed roles list
|
||||
// - loads an existing WAL entry if WALID input is given, otherwise creates a
|
||||
// new WAL entry
|
||||
// - gets a database connection
|
||||
// - accepts an input password, otherwise generates a new one via gRPC to the
|
||||
// database plugin
|
||||
// - sets new password for the static account
|
||||
// - uses WAL for ensuring passwords are not lost if storage to Vault fails
|
||||
// - accepts an input credential, otherwise generates a new one for
|
||||
// the role's credential type
|
||||
// - sets new credential for the static account
|
||||
// - uses WAL for ensuring new credentials are not lost if storage to Vault fails,
|
||||
// resulting in a partial failure.
|
||||
//
|
||||
// This method does not perform any operations on the priority queue. Those
|
||||
// tasks must be handled outside of this method.
|
||||
|
@ -321,6 +342,12 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag
|
|||
return output, fmt.Errorf("%q is not an allowed role", input.RoleName)
|
||||
}
|
||||
|
||||
// If the plugin doesn't support the credential type, return an error
|
||||
if !dbConfig.SupportsCredentialType(input.Role.CredentialType) {
|
||||
return output, fmt.Errorf("unsupported credential_type: %q",
|
||||
input.Role.CredentialType.String())
|
||||
}
|
||||
|
||||
// Get the Database object
|
||||
dbi, err := b.GetConnection(ctx, s, input.Role.DBName)
|
||||
if err != nil {
|
||||
|
@ -330,61 +357,122 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag
|
|||
dbi.RLock()
|
||||
defer dbi.RUnlock()
|
||||
|
||||
// Use password from input if available. This happens if we're restoring from
|
||||
updateReq := v5.UpdateUserRequest{
|
||||
Username: input.Role.StaticAccount.Username,
|
||||
}
|
||||
statements := v5.Statements{
|
||||
Commands: input.Role.Statements.Rotation,
|
||||
}
|
||||
|
||||
// Use credential from input if available. This happens if we're restoring from
|
||||
// a WAL item or processing the rotation queue with an item that has a WAL
|
||||
// associated with it
|
||||
var newPassword string
|
||||
if output.WALID != "" {
|
||||
wal, err := b.findStaticWAL(ctx, s, output.WALID)
|
||||
if err != nil {
|
||||
return output, errwrap.Wrapf("error retrieving WAL entry: {{err}}", err)
|
||||
return output, fmt.Errorf("error retrieving WAL entry: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case wal != nil && wal.NewPassword != "":
|
||||
newPassword = wal.NewPassword
|
||||
default:
|
||||
if wal == nil {
|
||||
b.Logger().Error("expected role to have WAL, but WAL not found in storage", "role", input.RoleName, "WAL ID", output.WALID)
|
||||
} else {
|
||||
b.Logger().Error("expected WAL to have a new password set, but empty", "role", input.RoleName, "WAL ID", output.WALID)
|
||||
err = framework.DeleteWAL(ctx, s, output.WALID)
|
||||
if err != nil {
|
||||
b.Logger().Warn("failed to delete WAL with no new password", "error", err, "WAL ID", output.WALID)
|
||||
}
|
||||
}
|
||||
// If there's anything wrong with the WAL in storage, we'll need
|
||||
// to generate a fresh WAL and password
|
||||
case wal == nil:
|
||||
b.Logger().Error("expected role to have WAL, but WAL not found in storage", "role", input.RoleName, "WAL ID", output.WALID)
|
||||
|
||||
// Generate a new WAL entry and credential
|
||||
output.WALID = ""
|
||||
case !wal.credentialIsSet():
|
||||
b.Logger().Error("expected WAL to have a new credential set, but empty", "role", input.RoleName, "WAL ID", output.WALID)
|
||||
if err := framework.DeleteWAL(ctx, s, output.WALID); err != nil {
|
||||
b.Logger().Warn("failed to delete WAL with no new credential", "error", err, "WAL ID", output.WALID)
|
||||
}
|
||||
|
||||
// Generate a new WAL entry and credential
|
||||
output.WALID = ""
|
||||
case wal.CredentialType == v5.CredentialTypePassword:
|
||||
// Roll forward by using the credential in the existing WAL entry
|
||||
updateReq.CredentialType = v5.CredentialTypePassword
|
||||
updateReq.Password = &v5.ChangePassword{
|
||||
NewPassword: wal.NewPassword,
|
||||
Statements: statements,
|
||||
}
|
||||
input.Role.StaticAccount.Password = wal.NewPassword
|
||||
case wal.CredentialType == v5.CredentialTypeRSAPrivateKey:
|
||||
// Roll forward by using the credential in the existing WAL entry
|
||||
updateReq.CredentialType = v5.CredentialTypeRSAPrivateKey
|
||||
updateReq.PublicKey = &v5.ChangePublicKey{
|
||||
NewPublicKey: wal.NewPublicKey,
|
||||
Statements: statements,
|
||||
}
|
||||
input.Role.StaticAccount.PrivateKey = wal.NewPrivateKey
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a new credential
|
||||
if output.WALID == "" {
|
||||
newPassword, err = dbi.database.GeneratePassword(ctx, b.System(), dbConfig.PasswordPolicy)
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
output.WALID, err = framework.PutWAL(ctx, s, staticWALKey, &setCredentialsWAL{
|
||||
walEntry := &setCredentialsWAL{
|
||||
RoleName: input.RoleName,
|
||||
Username: input.Role.StaticAccount.Username,
|
||||
NewPassword: newPassword,
|
||||
LastVaultRotation: input.Role.StaticAccount.LastVaultRotation,
|
||||
})
|
||||
}
|
||||
|
||||
switch input.Role.CredentialType {
|
||||
case v5.CredentialTypePassword:
|
||||
generator, err := newPasswordGenerator(input.Role.CredentialConfig)
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Fall back to database config-level password policy if not set on role
|
||||
if generator.PasswordPolicy == "" {
|
||||
generator.PasswordPolicy = dbConfig.PasswordPolicy
|
||||
}
|
||||
|
||||
// Generate the password
|
||||
newPassword, err := generator.generate(ctx, b, dbi.database)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
return output, fmt.Errorf("failed to generate password: %s", err)
|
||||
}
|
||||
|
||||
// Set new credential in WAL entry and update user request
|
||||
walEntry.NewPassword = newPassword
|
||||
updateReq.CredentialType = v5.CredentialTypePassword
|
||||
updateReq.Password = &v5.ChangePassword{
|
||||
NewPassword: newPassword,
|
||||
Statements: statements,
|
||||
}
|
||||
|
||||
// Set new credential in static account
|
||||
input.Role.StaticAccount.Password = newPassword
|
||||
case v5.CredentialTypeRSAPrivateKey:
|
||||
generator, err := newRSAKeyGenerator(input.Role.CredentialConfig)
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("failed to construct credential generator: %s", err)
|
||||
}
|
||||
|
||||
// Generate the RSA key pair
|
||||
public, private, err := generator.generate(b.GetRandomReader())
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("failed to generate RSA key pair: %s", err)
|
||||
}
|
||||
|
||||
// Set new credential in WAL entry and update user request
|
||||
walEntry.NewPublicKey = public
|
||||
updateReq.CredentialType = v5.CredentialTypeRSAPrivateKey
|
||||
updateReq.PublicKey = &v5.ChangePublicKey{
|
||||
NewPublicKey: public,
|
||||
Statements: statements,
|
||||
}
|
||||
|
||||
// Set new credential in static account
|
||||
input.Role.StaticAccount.PrivateKey = private
|
||||
}
|
||||
|
||||
output.WALID, err = framework.PutWAL(ctx, s, staticWALKey, walEntry)
|
||||
if err != nil {
|
||||
return output, fmt.Errorf("error writing WAL entry: %w", err)
|
||||
}
|
||||
b.Logger().Debug("writing WAL", "role", input.RoleName, "WAL ID", output.WALID)
|
||||
}
|
||||
|
||||
updateReq := v5.UpdateUserRequest{
|
||||
Username: input.Role.StaticAccount.Username,
|
||||
Password: &v5.ChangePassword{
|
||||
NewPassword: newPassword,
|
||||
Statements: v5.Statements{
|
||||
Commands: input.Role.Statements.Rotation,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = dbi.database.UpdateUser(ctx, updateReq, false)
|
||||
if err != nil {
|
||||
b.CloseIfShutdown(dbi, err)
|
||||
|
@ -395,7 +483,6 @@ func (b *databaseBackend) setStaticAccount(ctx context.Context, s logical.Storag
|
|||
// lvr is the known LastVaultRotation
|
||||
lvr := time.Now()
|
||||
input.Role.StaticAccount.LastVaultRotation = lvr
|
||||
input.Role.StaticAccount.Password = newPassword
|
||||
output.RotationTime = lvr
|
||||
|
||||
entry, err := logical.StorageEntryJSON(databaseStaticRolePath+input.RoleName, input.Role)
|
||||
|
|
|
@ -2,12 +2,10 @@ package database
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/vault/helper/random"
|
||||
v4 "github.com/hashicorp/vault/sdk/database/dbplugin"
|
||||
v5 "github.com/hashicorp/vault/sdk/database/dbplugin/v5"
|
||||
"github.com/hashicorp/vault/sdk/helper/pluginutil"
|
||||
|
@ -229,39 +227,6 @@ func (d databaseVersionWrapper) Close() error {
|
|||
return d.v4.Close()
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////////
|
||||
// Password generation
|
||||
// /////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type passwordGenerator interface {
|
||||
GeneratePasswordFromPolicy(ctx context.Context, policyName string) (password string, err error)
|
||||
}
|
||||
|
||||
var defaultPasswordGenerator = random.DefaultStringGenerator
|
||||
|
||||
// GeneratePassword either from the v4 database or by using the provided password policy. If using a v5 database
|
||||
// and no password policy is specified, this will have a reasonable default password generator.
|
||||
func (d databaseVersionWrapper) GeneratePassword(ctx context.Context, generator passwordGenerator, passwordPolicy string) (password string, err error) {
|
||||
if !d.isV5() && !d.isV4() {
|
||||
return "", fmt.Errorf("no underlying database specified")
|
||||
}
|
||||
|
||||
// If using the legacy database, use GenerateCredentials instead of password policies
|
||||
// This will keep the existing behavior even though passwords can be generated with a policy
|
||||
if d.isV4() {
|
||||
password, err := d.v4.GenerateCredentials(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
if passwordPolicy == "" {
|
||||
return defaultPasswordGenerator.Generate(ctx, rand.Reader)
|
||||
}
|
||||
return generator.GeneratePasswordFromPolicy(ctx, passwordPolicy)
|
||||
}
|
||||
|
||||
func (d databaseVersionWrapper) isV5() bool {
|
||||
return d.v5 != nil
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -176,176 +175,6 @@ func TestInitDatabase_legacyDB(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
type fakePasswordGenerator struct {
|
||||
password string
|
||||
err error
|
||||
}
|
||||
|
||||
func (pg fakePasswordGenerator) GeneratePasswordFromPolicy(ctx context.Context, policy string) (string, error) {
|
||||
return pg.password, pg.err
|
||||
}
|
||||
|
||||
func TestGeneratePassword_missingDB(t *testing.T) {
|
||||
dbw := databaseVersionWrapper{}
|
||||
|
||||
gen := fakePasswordGenerator{
|
||||
err: fmt.Errorf("this shouldn't be called"),
|
||||
}
|
||||
pass, err := dbw.GeneratePassword(context.Background(), gen, "policy")
|
||||
if err == nil {
|
||||
t.Fatalf("err expected, got nil")
|
||||
}
|
||||
|
||||
if pass != "" {
|
||||
t.Fatalf("Password should be empty but was: %s", pass)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePassword_legacy(t *testing.T) {
|
||||
type testCase struct {
|
||||
legacyPassword string
|
||||
legacyErr error
|
||||
legacyCalls int
|
||||
|
||||
expectedPassword string
|
||||
expectErr bool
|
||||
}
|
||||
|
||||
tests := map[string]testCase{
|
||||
"legacy password generation": {
|
||||
legacyPassword: "legacy_password",
|
||||
legacyErr: nil,
|
||||
legacyCalls: 1,
|
||||
|
||||
expectedPassword: "legacy_password",
|
||||
expectErr: false,
|
||||
},
|
||||
"legacy password failure": {
|
||||
legacyPassword: "",
|
||||
legacyErr: fmt.Errorf("failed :("),
|
||||
legacyCalls: 1,
|
||||
|
||||
expectedPassword: "",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
legacyDB := new(mockLegacyDatabase)
|
||||
legacyDB.On("GenerateCredentials", mock.Anything).
|
||||
Return(test.legacyPassword, test.legacyErr)
|
||||
defer legacyDB.AssertNumberOfCalls(t, "GenerateCredentials", test.legacyCalls)
|
||||
|
||||
dbw := databaseVersionWrapper{
|
||||
v4: legacyDB,
|
||||
}
|
||||
|
||||
passGen := fakePasswordGenerator{
|
||||
err: fmt.Errorf("this should not be called"),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
password, err := dbw.GeneratePassword(ctx, passGen, "test_policy")
|
||||
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 password != test.expectedPassword {
|
||||
t.Fatalf("Actual password: %s Expected password: %s", password, test.expectedPassword)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePassword_policies(t *testing.T) {
|
||||
type testCase struct {
|
||||
passwordPolicyPassword string
|
||||
passwordPolicyErr error
|
||||
|
||||
expectedPassword string
|
||||
expectErr bool
|
||||
}
|
||||
|
||||
tests := map[string]testCase{
|
||||
"password policy generation": {
|
||||
passwordPolicyPassword: "new_password",
|
||||
|
||||
expectedPassword: "new_password",
|
||||
expectErr: false,
|
||||
},
|
||||
"password policy error": {
|
||||
passwordPolicyPassword: "",
|
||||
passwordPolicyErr: fmt.Errorf("test error"),
|
||||
|
||||
expectedPassword: "",
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
newDB := new(mockNewDatabase)
|
||||
defer newDB.AssertExpectations(t)
|
||||
|
||||
dbw := databaseVersionWrapper{
|
||||
v5: newDB,
|
||||
}
|
||||
|
||||
passGen := fakePasswordGenerator{
|
||||
password: test.passwordPolicyPassword,
|
||||
err: test.passwordPolicyErr,
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
password, err := dbw.GeneratePassword(ctx, passGen, "test_policy")
|
||||
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 password != test.expectedPassword {
|
||||
t.Fatalf("Actual password: %s Expected password: %s", password, test.expectedPassword)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePassword_no_policy(t *testing.T) {
|
||||
newDB := new(mockNewDatabase)
|
||||
defer newDB.AssertExpectations(t)
|
||||
|
||||
dbw := databaseVersionWrapper{
|
||||
v5: newDB,
|
||||
}
|
||||
|
||||
passGen := fakePasswordGenerator{
|
||||
password: "",
|
||||
err: fmt.Errorf("should not be called"),
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
password, err := dbw.GeneratePassword(ctx, passGen, "")
|
||||
if err != nil {
|
||||
t.Fatalf("no error expected, got: %s", err)
|
||||
}
|
||||
if password == "" {
|
||||
t.Fatalf("missing password")
|
||||
}
|
||||
|
||||
rawRegex := "^[a-zA-Z0-9-]{20}$"
|
||||
re := regexp.MustCompile(rawRegex)
|
||||
if !re.MatchString(password) {
|
||||
t.Fatalf("password %q did not match regex: %q", password, rawRegex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUser_missingDB(t *testing.T) {
|
||||
dbw := databaseVersionWrapper{}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
**Snowflake Database Plugin**: Adds ability to manage RSA key pair credentials for dynamic and static Snowflake users.
|
||||
```
|
2
go.mod
2
go.mod
|
@ -118,7 +118,7 @@ require (
|
|||
github.com/hashicorp/vault/api v1.5.0
|
||||
github.com/hashicorp/vault/api/auth/approle v0.1.0
|
||||
github.com/hashicorp/vault/api/auth/userpass v0.1.0
|
||||
github.com/hashicorp/vault/sdk v0.4.2-0.20220426194706-f8e907e0deda
|
||||
github.com/hashicorp/vault/sdk v0.4.2-0.20220511000023-fa93782110bf
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2
|
||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"unicode"
|
||||
|
||||
"github.com/hashicorp/vault/sdk/database/dbplugin/v5/proto"
|
||||
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
@ -60,8 +59,10 @@ func TestConversionsHaveAllFields(t *testing.T) {
|
|||
"rollback_statement",
|
||||
},
|
||||
},
|
||||
Password: "password",
|
||||
Expiration: time.Now(),
|
||||
CredentialType: CredentialTypeRSAPrivateKey,
|
||||
PublicKey: []byte("-----BEGIN PUBLIC KEY-----"),
|
||||
Password: "password",
|
||||
Expiration: time.Now(),
|
||||
}
|
||||
|
||||
protoReq, err := newUserReqToProto(req)
|
||||
|
@ -85,7 +86,8 @@ func TestConversionsHaveAllFields(t *testing.T) {
|
|||
|
||||
t.Run("updateUserReqToProto", func(t *testing.T) {
|
||||
req := UpdateUserRequest{
|
||||
Username: "username",
|
||||
Username: "username",
|
||||
CredentialType: CredentialTypeRSAPrivateKey,
|
||||
Password: &ChangePassword{
|
||||
NewPassword: "newpassword",
|
||||
Statements: Statements{
|
||||
|
@ -94,6 +96,14 @@ func TestConversionsHaveAllFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
PublicKey: &ChangePublicKey{
|
||||
NewPublicKey: []byte("-----BEGIN PUBLIC KEY-----"),
|
||||
Statements: Statements{
|
||||
Commands: []string{
|
||||
"statement",
|
||||
},
|
||||
},
|
||||
},
|
||||
Expiration: &ChangeExpiration{
|
||||
NewExpiration: time.Now(),
|
||||
Statements: Statements{
|
||||
|
@ -154,7 +164,8 @@ func TestConversionsHaveAllFields(t *testing.T) {
|
|||
|
||||
t.Run("getUpdateUserRequest", func(t *testing.T) {
|
||||
req := &proto.UpdateUserRequest{
|
||||
Username: "username",
|
||||
Username: "username",
|
||||
CredentialType: int32(CredentialTypeRSAPrivateKey),
|
||||
Password: &proto.ChangePassword{
|
||||
NewPassword: "newpass",
|
||||
Statements: &proto.Statements{
|
||||
|
@ -163,6 +174,14 @@ func TestConversionsHaveAllFields(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
PublicKey: &proto.ChangePublicKey{
|
||||
NewPublicKey: []byte("-----BEGIN PUBLIC KEY-----"),
|
||||
Statements: &proto.Statements{
|
||||
Commands: []string{
|
||||
"statement",
|
||||
},
|
||||
},
|
||||
},
|
||||
Expiration: &proto.ChangeExpiration{
|
||||
NewExpiration: timestamppb.Now(),
|
||||
Statements: &proto.Statements{
|
||||
|
|
|
@ -71,6 +71,22 @@ type InitializeResponse struct {
|
|||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
// SupportedCredentialTypesKey is used to get and set the supported
|
||||
// CredentialType values in database plugins and Vault.
|
||||
const SupportedCredentialTypesKey = "supported_credential_types"
|
||||
|
||||
// SetSupportedCredentialTypes sets the CredentialType values that are
|
||||
// supported by the database plugin. It can be used by database plugins
|
||||
// to communicate what CredentialType values it supports managing.
|
||||
func (ir InitializeResponse) SetSupportedCredentialTypes(credTypes []CredentialType) {
|
||||
sct := make([]string, 0, len(credTypes))
|
||||
for _, t := range credTypes {
|
||||
sct = append(sct, t.String())
|
||||
}
|
||||
|
||||
ir.Config[SupportedCredentialTypesKey] = sct
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////////
|
||||
// NewUser()
|
||||
// ///////////////////////////////////////////////////////
|
||||
|
@ -90,9 +106,20 @@ type NewUserRequest struct {
|
|||
// if the new user creation process fails.
|
||||
RollbackStatements Statements
|
||||
|
||||
// Password credentials to use when creating the user
|
||||
// CredentialType is the type of credential to use when creating a user.
|
||||
// Respective fields for the credential type will contain the credential
|
||||
// value that was generated by Vault.
|
||||
CredentialType CredentialType
|
||||
|
||||
// Password credential to use when creating the user.
|
||||
// Value is set when the credential type is CredentialTypePassword.
|
||||
Password string
|
||||
|
||||
// PublicKey credential to use when creating the user.
|
||||
// The value is a PKIX marshaled, PEM encoded public key.
|
||||
// The value is set when the credential type is CredentialTypeRSAPrivateKey.
|
||||
PublicKey []byte
|
||||
|
||||
// Expiration of the user. Not all database plugins will support this.
|
||||
Expiration time.Time
|
||||
}
|
||||
|
@ -110,6 +137,25 @@ type NewUserResponse struct {
|
|||
Username string
|
||||
}
|
||||
|
||||
// CredentialType is a type of database credential.
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
CredentialTypePassword CredentialType = iota
|
||||
CredentialTypeRSAPrivateKey
|
||||
)
|
||||
|
||||
func (k CredentialType) String() string {
|
||||
switch k {
|
||||
case CredentialTypePassword:
|
||||
return "password"
|
||||
case CredentialTypeRSAPrivateKey:
|
||||
return "rsa_private_key"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ///////////////////////////////////////////////////////
|
||||
// UpdateUser()
|
||||
// ///////////////////////////////////////////////////////
|
||||
|
@ -118,15 +164,37 @@ type UpdateUserRequest struct {
|
|||
// Username to make changes to.
|
||||
Username string
|
||||
|
||||
// CredentialType is the type of credential to use when updating a user.
|
||||
// Respective fields for the credential type will contain the credential
|
||||
// value that was generated by Vault.
|
||||
CredentialType CredentialType
|
||||
|
||||
// Password indicates the new password to change to.
|
||||
// The value is set when the credential type is CredentialTypePassword.
|
||||
// If nil, no change is requested.
|
||||
Password *ChangePassword
|
||||
|
||||
// PublicKey indicates the new public key to change to.
|
||||
// The value is set when the credential type is CredentialTypeRSAPrivateKey.
|
||||
// If nil, no change is requested.
|
||||
PublicKey *ChangePublicKey
|
||||
|
||||
// Expiration indicates the new expiration date to change to.
|
||||
// If nil, no change is requested.
|
||||
Expiration *ChangeExpiration
|
||||
}
|
||||
|
||||
// ChangePublicKey of a given user
|
||||
type ChangePublicKey struct {
|
||||
// NewPublicKey is the new public key credential for the user.
|
||||
// The value is a PKIX marshaled, PEM encoded public key.
|
||||
NewPublicKey []byte
|
||||
|
||||
// Statements is an ordered list of commands to run within the database
|
||||
// when changing the user's public key credential.
|
||||
Statements Statements
|
||||
}
|
||||
|
||||
// ChangePassword of a given user
|
||||
type ChangePassword struct {
|
||||
// NewPassword for the user
|
||||
|
|
|
@ -95,8 +95,10 @@ func newUserReqToProto(req NewUserRequest) (*proto.NewUserRequest, error) {
|
|||
DisplayName: req.UsernameConfig.DisplayName,
|
||||
RoleName: req.UsernameConfig.RoleName,
|
||||
},
|
||||
Password: req.Password,
|
||||
Expiration: expiration,
|
||||
CredentialType: int32(req.CredentialType),
|
||||
Password: req.Password,
|
||||
PublicKey: req.PublicKey,
|
||||
Expiration: expiration,
|
||||
Statements: &proto.Statements{
|
||||
Commands: req.Statements.Commands,
|
||||
},
|
||||
|
@ -138,6 +140,7 @@ func updateUserReqToProto(req UpdateUserRequest) (*proto.UpdateUserRequest, erro
|
|||
}
|
||||
|
||||
if (req.Password == nil || req.Password.NewPassword == "") &&
|
||||
(req.PublicKey == nil || len(req.PublicKey.NewPublicKey) == 0) &&
|
||||
(req.Expiration == nil || req.Expiration.NewExpiration.IsZero()) {
|
||||
return nil, fmt.Errorf("missing changes")
|
||||
}
|
||||
|
@ -157,10 +160,22 @@ func updateUserReqToProto(req UpdateUserRequest) (*proto.UpdateUserRequest, erro
|
|||
}
|
||||
}
|
||||
|
||||
var publicKey *proto.ChangePublicKey
|
||||
if req.PublicKey != nil && len(req.PublicKey.NewPublicKey) > 0 {
|
||||
publicKey = &proto.ChangePublicKey{
|
||||
NewPublicKey: req.PublicKey.NewPublicKey,
|
||||
Statements: &proto.Statements{
|
||||
Commands: req.PublicKey.Statements.Commands,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
rpcReq := &proto.UpdateUserRequest{
|
||||
Username: req.Username,
|
||||
Password: password,
|
||||
Expiration: expiration,
|
||||
Username: req.Username,
|
||||
CredentialType: int32(req.CredentialType),
|
||||
Password: password,
|
||||
PublicKey: publicKey,
|
||||
Expiration: expiration,
|
||||
}
|
||||
return rpcReq, nil
|
||||
}
|
||||
|
|
|
@ -159,7 +159,9 @@ func (g *gRPCServer) NewUser(ctx context.Context, req *proto.NewUserRequest) (*p
|
|||
DisplayName: req.GetUsernameConfig().GetDisplayName(),
|
||||
RoleName: req.GetUsernameConfig().GetRoleName(),
|
||||
},
|
||||
CredentialType: CredentialType(req.GetCredentialType()),
|
||||
Password: req.GetPassword(),
|
||||
PublicKey: req.GetPublicKey(),
|
||||
Expiration: expiration,
|
||||
Statements: getStatementsFromProto(req.GetStatements()),
|
||||
RollbackStatements: getStatementsFromProto(req.GetRollbackStatements()),
|
||||
|
@ -207,6 +209,14 @@ func getUpdateUserRequest(req *proto.UpdateUserRequest) (UpdateUserRequest, erro
|
|||
}
|
||||
}
|
||||
|
||||
var publicKey *ChangePublicKey
|
||||
if req.GetPublicKey() != nil && len(req.GetPublicKey().GetNewPublicKey()) > 0 {
|
||||
publicKey = &ChangePublicKey{
|
||||
NewPublicKey: req.GetPublicKey().GetNewPublicKey(),
|
||||
Statements: getStatementsFromProto(req.GetPublicKey().GetStatements()),
|
||||
}
|
||||
}
|
||||
|
||||
var expiration *ChangeExpiration
|
||||
if req.GetExpiration() != nil && req.GetExpiration().GetNewExpiration() != nil {
|
||||
newExpiration, err := ptypes.Timestamp(req.GetExpiration().GetNewExpiration())
|
||||
|
@ -221,9 +231,11 @@ func getUpdateUserRequest(req *proto.UpdateUserRequest) (UpdateUserRequest, erro
|
|||
}
|
||||
|
||||
dbReq := UpdateUserRequest{
|
||||
Username: req.GetUsername(),
|
||||
Password: password,
|
||||
Expiration: expiration,
|
||||
Username: req.GetUsername(),
|
||||
CredentialType: CredentialType(req.GetCredentialType()),
|
||||
Password: password,
|
||||
PublicKey: publicKey,
|
||||
Expiration: expiration,
|
||||
}
|
||||
|
||||
if !hasChange(dbReq) {
|
||||
|
@ -237,6 +249,9 @@ func hasChange(dbReq UpdateUserRequest) bool {
|
|||
if dbReq.Password != nil && dbReq.Password.NewPassword != "" {
|
||||
return true
|
||||
}
|
||||
if dbReq.PublicKey != nil && len(dbReq.PublicKey.NewPublicKey) > 0 {
|
||||
return true
|
||||
}
|
||||
if dbReq.Expiration != nil && !dbReq.Expiration.NewExpiration.IsZero() {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -137,6 +137,8 @@ type NewUserRequest struct {
|
|||
Expiration *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||
Statements *Statements `protobuf:"bytes,4,opt,name=statements,proto3" json:"statements,omitempty"`
|
||||
RollbackStatements *Statements `protobuf:"bytes,5,opt,name=rollback_statements,json=rollbackStatements,proto3" json:"rollback_statements,omitempty"`
|
||||
CredentialType int32 `protobuf:"varint,6,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty"`
|
||||
PublicKey []byte `protobuf:"bytes,7,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||
}
|
||||
|
||||
func (x *NewUserRequest) Reset() {
|
||||
|
@ -206,6 +208,20 @@ func (x *NewUserRequest) GetRollbackStatements() *Statements {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *NewUserRequest) GetCredentialType() int32 {
|
||||
if x != nil {
|
||||
return x.CredentialType
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *NewUserRequest) GetPublicKey() []byte {
|
||||
if x != nil {
|
||||
return x.PublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type UsernameConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -316,9 +332,11 @@ type UpdateUserRequest struct {
|
|||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Password *ChangePassword `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Expiration *ChangeExpiration `protobuf:"bytes,3,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"`
|
||||
Password *ChangePassword `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Expiration *ChangeExpiration `protobuf:"bytes,3,opt,name=expiration,proto3" json:"expiration,omitempty"`
|
||||
PublicKey *ChangePublicKey `protobuf:"bytes,4,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
|
||||
CredentialType int32 `protobuf:"varint,5,opt,name=credential_type,json=credentialType,proto3" json:"credential_type,omitempty"`
|
||||
}
|
||||
|
||||
func (x *UpdateUserRequest) Reset() {
|
||||
|
@ -374,6 +392,20 @@ func (x *UpdateUserRequest) GetExpiration() *ChangeExpiration {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *UpdateUserRequest) GetPublicKey() *ChangePublicKey {
|
||||
if x != nil {
|
||||
return x.PublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *UpdateUserRequest) GetCredentialType() int32 {
|
||||
if x != nil {
|
||||
return x.CredentialType
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ChangePassword struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -429,6 +461,61 @@ func (x *ChangePassword) GetStatements() *Statements {
|
|||
return nil
|
||||
}
|
||||
|
||||
type ChangePublicKey struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
NewPublicKey []byte `protobuf:"bytes,1,opt,name=new_public_key,json=newPublicKey,proto3" json:"new_public_key,omitempty"`
|
||||
Statements *Statements `protobuf:"bytes,2,opt,name=statements,proto3" json:"statements,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ChangePublicKey) Reset() {
|
||||
*x = ChangePublicKey{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ChangePublicKey) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ChangePublicKey) ProtoMessage() {}
|
||||
|
||||
func (x *ChangePublicKey) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[7]
|
||||
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 ChangePublicKey.ProtoReflect.Descriptor instead.
|
||||
func (*ChangePublicKey) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *ChangePublicKey) GetNewPublicKey() []byte {
|
||||
if x != nil {
|
||||
return x.NewPublicKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *ChangePublicKey) GetStatements() *Statements {
|
||||
if x != nil {
|
||||
return x.Statements
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ChangeExpiration struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
@ -441,7 +528,7 @@ type ChangeExpiration struct {
|
|||
func (x *ChangeExpiration) Reset() {
|
||||
*x = ChangeExpiration{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[7]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[8]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -454,7 +541,7 @@ func (x *ChangeExpiration) String() string {
|
|||
func (*ChangeExpiration) ProtoMessage() {}
|
||||
|
||||
func (x *ChangeExpiration) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[7]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[8]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -467,7 +554,7 @@ func (x *ChangeExpiration) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use ChangeExpiration.ProtoReflect.Descriptor instead.
|
||||
func (*ChangeExpiration) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{7}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{8}
|
||||
}
|
||||
|
||||
func (x *ChangeExpiration) GetNewExpiration() *timestamppb.Timestamp {
|
||||
|
@ -493,7 +580,7 @@ type UpdateUserResponse struct {
|
|||
func (x *UpdateUserResponse) Reset() {
|
||||
*x = UpdateUserResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[8]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[9]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -506,7 +593,7 @@ func (x *UpdateUserResponse) String() string {
|
|||
func (*UpdateUserResponse) ProtoMessage() {}
|
||||
|
||||
func (x *UpdateUserResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[8]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[9]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -519,7 +606,7 @@ func (x *UpdateUserResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use UpdateUserResponse.ProtoReflect.Descriptor instead.
|
||||
func (*UpdateUserResponse) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{8}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{9}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
@ -537,7 +624,7 @@ type DeleteUserRequest struct {
|
|||
func (x *DeleteUserRequest) Reset() {
|
||||
*x = DeleteUserRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[9]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[10]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -550,7 +637,7 @@ func (x *DeleteUserRequest) String() string {
|
|||
func (*DeleteUserRequest) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[9]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[10]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -563,7 +650,7 @@ func (x *DeleteUserRequest) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use DeleteUserRequest.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteUserRequest) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{9}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{10}
|
||||
}
|
||||
|
||||
func (x *DeleteUserRequest) GetUsername() string {
|
||||
|
@ -589,7 +676,7 @@ type DeleteUserResponse struct {
|
|||
func (x *DeleteUserResponse) Reset() {
|
||||
*x = DeleteUserResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[10]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[11]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -602,7 +689,7 @@ func (x *DeleteUserResponse) String() string {
|
|||
func (*DeleteUserResponse) ProtoMessage() {}
|
||||
|
||||
func (x *DeleteUserResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[10]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[11]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -615,7 +702,7 @@ func (x *DeleteUserResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use DeleteUserResponse.ProtoReflect.Descriptor instead.
|
||||
func (*DeleteUserResponse) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{10}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{11}
|
||||
}
|
||||
|
||||
/////////////////
|
||||
|
@ -632,7 +719,7 @@ type TypeResponse struct {
|
|||
func (x *TypeResponse) Reset() {
|
||||
*x = TypeResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[11]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[12]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -645,7 +732,7 @@ func (x *TypeResponse) String() string {
|
|||
func (*TypeResponse) ProtoMessage() {}
|
||||
|
||||
func (x *TypeResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[11]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[12]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -658,7 +745,7 @@ func (x *TypeResponse) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use TypeResponse.ProtoReflect.Descriptor instead.
|
||||
func (*TypeResponse) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{11}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{12}
|
||||
}
|
||||
|
||||
func (x *TypeResponse) GetType() string {
|
||||
|
@ -682,7 +769,7 @@ type Statements struct {
|
|||
func (x *Statements) Reset() {
|
||||
*x = Statements{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[12]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[13]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -695,7 +782,7 @@ func (x *Statements) String() string {
|
|||
func (*Statements) ProtoMessage() {}
|
||||
|
||||
func (x *Statements) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[12]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[13]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -708,7 +795,7 @@ func (x *Statements) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Statements.ProtoReflect.Descriptor instead.
|
||||
func (*Statements) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{12}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{13}
|
||||
}
|
||||
|
||||
func (x *Statements) GetCommands() []string {
|
||||
|
@ -727,7 +814,7 @@ type Empty struct {
|
|||
func (x *Empty) Reset() {
|
||||
*x = Empty{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[13]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[14]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
@ -740,7 +827,7 @@ func (x *Empty) String() string {
|
|||
func (*Empty) ProtoMessage() {}
|
||||
|
||||
func (x *Empty) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[13]
|
||||
mi := &file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[14]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
|
@ -753,7 +840,7 @@ func (x *Empty) ProtoReflect() protoreflect.Message {
|
|||
|
||||
// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
|
||||
func (*Empty) Descriptor() ([]byte, []int) {
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{13}
|
||||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP(), []int{14}
|
||||
}
|
||||
|
||||
var File_sdk_database_dbplugin_v5_proto_database_proto protoreflect.FileDescriptor
|
||||
|
@ -779,7 +866,7 @@ var file_sdk_database_dbplugin_v5_proto_database_proto_rawDesc = []byte{
|
|||
0x0b, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x0a, 0x63, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0xb1, 0x02, 0x0a, 0x0e, 0x4e, 0x65, 0x77, 0x55,
|
||||
0x66, 0x69, 0x67, 0x44, 0x61, 0x74, 0x61, 0x22, 0xf9, 0x02, 0x0a, 0x0e, 0x4e, 0x65, 0x77, 0x55,
|
||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x0f, 0x75, 0x73,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76,
|
||||
|
@ -798,29 +885,47 @@ var file_sdk_database_dbplugin_v5_proto_database_proto_rawDesc = []byte{
|
|||
0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
|
||||
0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35, 0x2e, 0x53, 0x74, 0x61,
|
||||
0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x12, 0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63,
|
||||
0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x50, 0x0a, 0x0e, 0x55,
|
||||
0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a,
|
||||
0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65,
|
||||
0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a,
|
||||
0x0f, 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xa7, 0x01, 0x0a,
|
||||
0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
|
||||
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37,
|
||||
0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
|
||||
0x32, 0x1b, 0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35, 0x2e, 0x43,
|
||||
0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70,
|
||||
0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x3d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x62,
|
||||
0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69,
|
||||
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x6c, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65,
|
||||
0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f,
|
||||
0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b,
|
||||
0x6e, 0x65, 0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x37, 0x0a, 0x0a, 0x73,
|
||||
0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x63,
|
||||
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
|
||||
0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b,
|
||||
0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63,
|
||||
0x4b, 0x65, 0x79, 0x22, 0x50, 0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x43,
|
||||
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73,
|
||||
0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x6f, 0x6c, 0x65,
|
||||
0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x6f, 0x6c,
|
||||
0x65, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x2d, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x22, 0x8d, 0x02, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55,
|
||||
0x73, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,
|
||||
0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75,
|
||||
0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61, 0x73,
|
||||
0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12,
|
||||
0x3d, 0x0a, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20,
|
||||
0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76,
|
||||
0x35, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3b,
|
||||
0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35,
|
||||
0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79,
|
||||
0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x63,
|
||||
0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05,
|
||||
0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c,
|
||||
0x54, 0x79, 0x70, 0x65, 0x22, 0x6c, 0x0a, 0x0e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x61,
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x61,
|
||||
0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65,
|
||||
0x77, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x37, 0x0a, 0x0a, 0x73, 0x74, 0x61,
|
||||
0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
|
||||
0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35, 0x2e, 0x53, 0x74, 0x61, 0x74,
|
||||
0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e,
|
||||
0x74, 0x73, 0x22, 0x70, 0x0a, 0x0f, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x75, 0x62, 0x6c,
|
||||
0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x65, 0x77, 0x5f, 0x70, 0x75, 0x62,
|
||||
0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x6e,
|
||||
0x65, 0x77, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x37, 0x0a, 0x0a, 0x73,
|
||||
0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
|
||||
0x17, 0x2e, 0x64, 0x62, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x76, 0x35, 0x2e, 0x53, 0x74,
|
||||
0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d,
|
||||
|
@ -893,7 +998,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_rawDescGZIP() []byte {
|
|||
return file_sdk_database_dbplugin_v5_proto_database_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
|
||||
var file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
var file_sdk_database_dbplugin_v5_proto_database_proto_goTypes = []interface{}{
|
||||
(*InitializeRequest)(nil), // 0: dbplugin.v5.InitializeRequest
|
||||
(*InitializeResponse)(nil), // 1: dbplugin.v5.InitializeResponse
|
||||
|
@ -902,46 +1007,49 @@ var file_sdk_database_dbplugin_v5_proto_database_proto_goTypes = []interface{}{
|
|||
(*NewUserResponse)(nil), // 4: dbplugin.v5.NewUserResponse
|
||||
(*UpdateUserRequest)(nil), // 5: dbplugin.v5.UpdateUserRequest
|
||||
(*ChangePassword)(nil), // 6: dbplugin.v5.ChangePassword
|
||||
(*ChangeExpiration)(nil), // 7: dbplugin.v5.ChangeExpiration
|
||||
(*UpdateUserResponse)(nil), // 8: dbplugin.v5.UpdateUserResponse
|
||||
(*DeleteUserRequest)(nil), // 9: dbplugin.v5.DeleteUserRequest
|
||||
(*DeleteUserResponse)(nil), // 10: dbplugin.v5.DeleteUserResponse
|
||||
(*TypeResponse)(nil), // 11: dbplugin.v5.TypeResponse
|
||||
(*Statements)(nil), // 12: dbplugin.v5.Statements
|
||||
(*Empty)(nil), // 13: dbplugin.v5.Empty
|
||||
(*structpb.Struct)(nil), // 14: google.protobuf.Struct
|
||||
(*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
|
||||
(*ChangePublicKey)(nil), // 7: dbplugin.v5.ChangePublicKey
|
||||
(*ChangeExpiration)(nil), // 8: dbplugin.v5.ChangeExpiration
|
||||
(*UpdateUserResponse)(nil), // 9: dbplugin.v5.UpdateUserResponse
|
||||
(*DeleteUserRequest)(nil), // 10: dbplugin.v5.DeleteUserRequest
|
||||
(*DeleteUserResponse)(nil), // 11: dbplugin.v5.DeleteUserResponse
|
||||
(*TypeResponse)(nil), // 12: dbplugin.v5.TypeResponse
|
||||
(*Statements)(nil), // 13: dbplugin.v5.Statements
|
||||
(*Empty)(nil), // 14: dbplugin.v5.Empty
|
||||
(*structpb.Struct)(nil), // 15: google.protobuf.Struct
|
||||
(*timestamppb.Timestamp)(nil), // 16: google.protobuf.Timestamp
|
||||
}
|
||||
var file_sdk_database_dbplugin_v5_proto_database_proto_depIdxs = []int32{
|
||||
14, // 0: dbplugin.v5.InitializeRequest.config_data:type_name -> google.protobuf.Struct
|
||||
14, // 1: dbplugin.v5.InitializeResponse.config_data:type_name -> google.protobuf.Struct
|
||||
15, // 0: dbplugin.v5.InitializeRequest.config_data:type_name -> google.protobuf.Struct
|
||||
15, // 1: dbplugin.v5.InitializeResponse.config_data:type_name -> google.protobuf.Struct
|
||||
3, // 2: dbplugin.v5.NewUserRequest.username_config:type_name -> dbplugin.v5.UsernameConfig
|
||||
15, // 3: dbplugin.v5.NewUserRequest.expiration:type_name -> google.protobuf.Timestamp
|
||||
12, // 4: dbplugin.v5.NewUserRequest.statements:type_name -> dbplugin.v5.Statements
|
||||
12, // 5: dbplugin.v5.NewUserRequest.rollback_statements:type_name -> dbplugin.v5.Statements
|
||||
16, // 3: dbplugin.v5.NewUserRequest.expiration:type_name -> google.protobuf.Timestamp
|
||||
13, // 4: dbplugin.v5.NewUserRequest.statements:type_name -> dbplugin.v5.Statements
|
||||
13, // 5: dbplugin.v5.NewUserRequest.rollback_statements:type_name -> dbplugin.v5.Statements
|
||||
6, // 6: dbplugin.v5.UpdateUserRequest.password:type_name -> dbplugin.v5.ChangePassword
|
||||
7, // 7: dbplugin.v5.UpdateUserRequest.expiration:type_name -> dbplugin.v5.ChangeExpiration
|
||||
12, // 8: dbplugin.v5.ChangePassword.statements:type_name -> dbplugin.v5.Statements
|
||||
15, // 9: dbplugin.v5.ChangeExpiration.new_expiration:type_name -> google.protobuf.Timestamp
|
||||
12, // 10: dbplugin.v5.ChangeExpiration.statements:type_name -> dbplugin.v5.Statements
|
||||
12, // 11: dbplugin.v5.DeleteUserRequest.statements:type_name -> dbplugin.v5.Statements
|
||||
0, // 12: dbplugin.v5.Database.Initialize:input_type -> dbplugin.v5.InitializeRequest
|
||||
2, // 13: dbplugin.v5.Database.NewUser:input_type -> dbplugin.v5.NewUserRequest
|
||||
5, // 14: dbplugin.v5.Database.UpdateUser:input_type -> dbplugin.v5.UpdateUserRequest
|
||||
9, // 15: dbplugin.v5.Database.DeleteUser:input_type -> dbplugin.v5.DeleteUserRequest
|
||||
13, // 16: dbplugin.v5.Database.Type:input_type -> dbplugin.v5.Empty
|
||||
13, // 17: dbplugin.v5.Database.Close:input_type -> dbplugin.v5.Empty
|
||||
1, // 18: dbplugin.v5.Database.Initialize:output_type -> dbplugin.v5.InitializeResponse
|
||||
4, // 19: dbplugin.v5.Database.NewUser:output_type -> dbplugin.v5.NewUserResponse
|
||||
8, // 20: dbplugin.v5.Database.UpdateUser:output_type -> dbplugin.v5.UpdateUserResponse
|
||||
10, // 21: dbplugin.v5.Database.DeleteUser:output_type -> dbplugin.v5.DeleteUserResponse
|
||||
11, // 22: dbplugin.v5.Database.Type:output_type -> dbplugin.v5.TypeResponse
|
||||
13, // 23: dbplugin.v5.Database.Close:output_type -> dbplugin.v5.Empty
|
||||
18, // [18:24] is the sub-list for method output_type
|
||||
12, // [12:18] is the sub-list for method input_type
|
||||
12, // [12:12] is the sub-list for extension type_name
|
||||
12, // [12:12] is the sub-list for extension extendee
|
||||
0, // [0:12] is the sub-list for field type_name
|
||||
8, // 7: dbplugin.v5.UpdateUserRequest.expiration:type_name -> dbplugin.v5.ChangeExpiration
|
||||
7, // 8: dbplugin.v5.UpdateUserRequest.public_key:type_name -> dbplugin.v5.ChangePublicKey
|
||||
13, // 9: dbplugin.v5.ChangePassword.statements:type_name -> dbplugin.v5.Statements
|
||||
13, // 10: dbplugin.v5.ChangePublicKey.statements:type_name -> dbplugin.v5.Statements
|
||||
16, // 11: dbplugin.v5.ChangeExpiration.new_expiration:type_name -> google.protobuf.Timestamp
|
||||
13, // 12: dbplugin.v5.ChangeExpiration.statements:type_name -> dbplugin.v5.Statements
|
||||
13, // 13: dbplugin.v5.DeleteUserRequest.statements:type_name -> dbplugin.v5.Statements
|
||||
0, // 14: dbplugin.v5.Database.Initialize:input_type -> dbplugin.v5.InitializeRequest
|
||||
2, // 15: dbplugin.v5.Database.NewUser:input_type -> dbplugin.v5.NewUserRequest
|
||||
5, // 16: dbplugin.v5.Database.UpdateUser:input_type -> dbplugin.v5.UpdateUserRequest
|
||||
10, // 17: dbplugin.v5.Database.DeleteUser:input_type -> dbplugin.v5.DeleteUserRequest
|
||||
14, // 18: dbplugin.v5.Database.Type:input_type -> dbplugin.v5.Empty
|
||||
14, // 19: dbplugin.v5.Database.Close:input_type -> dbplugin.v5.Empty
|
||||
1, // 20: dbplugin.v5.Database.Initialize:output_type -> dbplugin.v5.InitializeResponse
|
||||
4, // 21: dbplugin.v5.Database.NewUser:output_type -> dbplugin.v5.NewUserResponse
|
||||
9, // 22: dbplugin.v5.Database.UpdateUser:output_type -> dbplugin.v5.UpdateUserResponse
|
||||
11, // 23: dbplugin.v5.Database.DeleteUser:output_type -> dbplugin.v5.DeleteUserResponse
|
||||
12, // 24: dbplugin.v5.Database.Type:output_type -> dbplugin.v5.TypeResponse
|
||||
14, // 25: dbplugin.v5.Database.Close:output_type -> dbplugin.v5.Empty
|
||||
20, // [20:26] is the sub-list for method output_type
|
||||
14, // [14:20] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_sdk_database_dbplugin_v5_proto_database_proto_init() }
|
||||
|
@ -1035,7 +1143,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ChangeExpiration); i {
|
||||
switch v := v.(*ChangePublicKey); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1047,7 +1155,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*UpdateUserResponse); i {
|
||||
switch v := v.(*ChangeExpiration); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1059,7 +1167,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeleteUserRequest); i {
|
||||
switch v := v.(*UpdateUserResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1071,7 +1179,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*DeleteUserResponse); i {
|
||||
switch v := v.(*DeleteUserRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1083,7 +1191,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*TypeResponse); i {
|
||||
switch v := v.(*DeleteUserResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1095,7 +1203,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Statements); i {
|
||||
switch v := v.(*TypeResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
|
@ -1107,6 +1215,18 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Statements); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_sdk_database_dbplugin_v5_proto_database_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Empty); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
|
@ -1125,7 +1245,7 @@ func file_sdk_database_dbplugin_v5_proto_database_proto_init() {
|
|||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_sdk_database_dbplugin_v5_proto_database_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 14,
|
||||
NumMessages: 15,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
|
|
@ -27,6 +27,8 @@ message NewUserRequest {
|
|||
google.protobuf.Timestamp expiration = 3;
|
||||
Statements statements = 4;
|
||||
Statements rollback_statements = 5;
|
||||
int32 credential_type = 6;
|
||||
bytes public_key = 7;
|
||||
}
|
||||
|
||||
message UsernameConfig {
|
||||
|
@ -45,6 +47,8 @@ message UpdateUserRequest {
|
|||
string username = 1;
|
||||
ChangePassword password = 2;
|
||||
ChangeExpiration expiration = 3;
|
||||
ChangePublicKey public_key = 4;
|
||||
int32 credential_type = 5;
|
||||
}
|
||||
|
||||
message ChangePassword {
|
||||
|
@ -52,6 +56,11 @@ message ChangePassword {
|
|||
Statements statements = 2;
|
||||
}
|
||||
|
||||
message ChangePublicKey {
|
||||
bytes new_public_key = 1;
|
||||
Statements statements = 2;
|
||||
}
|
||||
|
||||
message ChangeExpiration {
|
||||
google.protobuf.Timestamp new_expiration = 1;
|
||||
Statements statements = 2;
|
||||
|
|
Loading…
Reference in New Issue