secrets/database: adds ability to manage alternative credential types and configuration (#15376)

This commit is contained in:
Austin Gebauer 2022-05-17 09:21:26 -07:00 committed by GitHub
parent 60acf9ad6e
commit d3629ab49d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1627 additions and 400 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

3
changelog/15376.txt Normal file
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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