diff --git a/builtin/logical/database/credentials.go b/builtin/logical/database/credentials.go new file mode 100644 index 000000000..a6e54678d --- /dev/null +++ b/builtin/logical/database/credentials.go @@ -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 +} diff --git a/builtin/logical/database/credentials_test.go b/builtin/logical/database/credentials_test.go new file mode 100644 index 000000000..32ddc2685 --- /dev/null +++ b/builtin/logical/database/credentials_test.go @@ -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) + }) + } +} diff --git a/builtin/logical/database/path_config_connection.go b/builtin/logical/database/path_config_connection.go index 2c2f7b4b0..ac5a623d7 100644 --- a/builtin/logical/database/path_config_connection.go +++ b/builtin/logical/database/path_config_connection.go @@ -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 { diff --git a/builtin/logical/database/path_creds_create.go b/builtin/logical/database/path_creds_create.go index 9a5bcb91b..e57516259 100644 --- a/builtin/logical/database/path_creds_create.go +++ b/builtin/logical/database/path_creds_create.go @@ -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 } } diff --git a/builtin/logical/database/path_roles.go b/builtin/logical/database/path_roles.go index 6c0d90183..02a199c53 100644 --- a/builtin/logical/database/path_roles.go +++ b/builtin/logical/database/path_roles.go @@ -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: diff --git a/builtin/logical/database/path_roles_test.go b/builtin/logical/database/path_roles_test.go index b66f85d54..bfb206338 100644 --- a/builtin/logical/database/path_roles_test.go +++ b/builtin/logical/database/path_roles_test.go @@ -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", }, }) diff --git a/builtin/logical/database/path_rotate_credentials.go b/builtin/logical/database/path_rotate_credentials.go index 040784a73..2b197d59e 100644 --- a/builtin/logical/database/path_rotate_credentials.go +++ b/builtin/logical/database/path_rotate_credentials.go @@ -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{ diff --git a/builtin/logical/database/rollback.go b/builtin/logical/database/rollback.go index c8221f3c4..a9810e816 100644 --- a/builtin/logical/database/rollback.go +++ b/builtin/logical/database/rollback.go @@ -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{ diff --git a/builtin/logical/database/rotation.go b/builtin/logical/database/rotation.go index 3e732ff2e..665f2e8b0 100644 --- a/builtin/logical/database/rotation.go +++ b/builtin/logical/database/rotation.go @@ -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) diff --git a/builtin/logical/database/version_wrapper.go b/builtin/logical/database/version_wrapper.go index e625404e5..3dc6c69a5 100644 --- a/builtin/logical/database/version_wrapper.go +++ b/builtin/logical/database/version_wrapper.go @@ -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 } diff --git a/builtin/logical/database/version_wrapper_test.go b/builtin/logical/database/version_wrapper_test.go index 2680de93c..054241f97 100644 --- a/builtin/logical/database/version_wrapper_test.go +++ b/builtin/logical/database/version_wrapper_test.go @@ -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{} diff --git a/changelog/15376.txt b/changelog/15376.txt new file mode 100644 index 000000000..68ebd653c --- /dev/null +++ b/changelog/15376.txt @@ -0,0 +1,3 @@ +```release-note:feature +**Snowflake Database Plugin**: Adds ability to manage RSA key pair credentials for dynamic and static Snowflake users. +``` diff --git a/go.mod b/go.mod index 073c6ae23..4c9fe6a32 100644 --- a/go.mod +++ b/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 diff --git a/sdk/database/dbplugin/v5/conversions_test.go b/sdk/database/dbplugin/v5/conversions_test.go index 374c0c2be..6207f0f39 100644 --- a/sdk/database/dbplugin/v5/conversions_test.go +++ b/sdk/database/dbplugin/v5/conversions_test.go @@ -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{ diff --git a/sdk/database/dbplugin/v5/database.go b/sdk/database/dbplugin/v5/database.go index 225e94280..a1393489f 100644 --- a/sdk/database/dbplugin/v5/database.go +++ b/sdk/database/dbplugin/v5/database.go @@ -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 diff --git a/sdk/database/dbplugin/v5/grpc_client.go b/sdk/database/dbplugin/v5/grpc_client.go index e3c51aaa0..06534b32a 100644 --- a/sdk/database/dbplugin/v5/grpc_client.go +++ b/sdk/database/dbplugin/v5/grpc_client.go @@ -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 } diff --git a/sdk/database/dbplugin/v5/grpc_server.go b/sdk/database/dbplugin/v5/grpc_server.go index 88c65d4d2..4d29a5a62 100644 --- a/sdk/database/dbplugin/v5/grpc_server.go +++ b/sdk/database/dbplugin/v5/grpc_server.go @@ -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 } diff --git a/sdk/database/dbplugin/v5/proto/database.pb.go b/sdk/database/dbplugin/v5/proto/database.pb.go index 3699a9d66..a5f52dab9 100644 --- a/sdk/database/dbplugin/v5/proto/database.pb.go +++ b/sdk/database/dbplugin/v5/proto/database.pb.go @@ -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, }, diff --git a/sdk/database/dbplugin/v5/proto/database.proto b/sdk/database/dbplugin/v5/proto/database.proto index be38548ee..c5d4c5562 100644 --- a/sdk/database/dbplugin/v5/proto/database.proto +++ b/sdk/database/dbplugin/v5/proto/database.proto @@ -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;