Skip to content

Commit 4b5aa39

Browse files
Merge remote-tracking branch 'remotes/from/ce/main'
2 parents de11b02 + 23fd753 commit 4b5aa39

File tree

9 files changed

+339
-109
lines changed

9 files changed

+339
-109
lines changed

builtin/logical/database/backend.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,7 @@ func Backend(conf *logical.BackendConfig) *databaseBackend {
131131
WALRollback: b.walRollback,
132132
WALRollbackMinAge: minRootCredRollbackAge,
133133
BackendType: logical.TypeLogical,
134-
RotateCredential: func(ctx context.Context, request *logical.Request) error {
135-
name, err := b.getDatabaseConfigNameFromRotationID(request.RotationID)
136-
if err != nil {
137-
return err
138-
}
139-
_, err = b.rotateRootCredentials(ctx, request, name)
140-
return err
141-
},
134+
RotateCredential: b.rotateRootCredential,
142135
}
143136

144137
b.logger = conf.Logger

builtin/logical/database/path_creds_create.go

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -156,37 +156,19 @@ func (b *databaseBackend) pathCredsCreateRead() framework.OperationFunc {
156156
// Generate the credential based on the role's credential type
157157
switch role.CredentialType {
158158
case v5.CredentialTypePassword:
159-
generator, err := newPasswordGenerator(role.CredentialConfig)
159+
password, err := b.generateNewPassword(ctx, role.CredentialConfig, dbConfig.PasswordPolicy, dbi)
160160
if err != nil {
161-
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
162-
}
163-
164-
// Fall back to database config-level password policy if not set on role
165-
if generator.PasswordPolicy == "" {
166-
generator.PasswordPolicy = dbConfig.PasswordPolicy
167-
}
168-
169-
// Generate the password
170-
password, err := generator.generate(ctx, b, dbi.database)
171-
if err != nil {
172-
b.CloseIfShutdown(dbi, err)
173-
return nil, fmt.Errorf("failed to generate password: %s", err)
161+
return nil, err
174162
}
175163

176164
// Set input credential
177165
newUserReq.CredentialType = v5.CredentialTypePassword
178166
newUserReq.Password = password
179167

180168
case v5.CredentialTypeRSAPrivateKey:
181-
generator, err := newRSAKeyGenerator(role.CredentialConfig)
182-
if err != nil {
183-
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
184-
}
185-
186-
// Generate the RSA key pair
187-
public, private, err := generator.generate(b.GetRandomReader())
169+
public, private, err := b.generateNewKeypair(role.CredentialConfig)
188170
if err != nil {
189-
return nil, fmt.Errorf("failed to generate RSA key pair: %s", err)
171+
return nil, err
190172
}
191173

192174
// Set input credential

builtin/logical/database/path_roles_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func TestBackend_Roles_CredentialTypes(t *testing.T) {
173173
Storage: config.StorageView,
174174
Data: map[string]interface{}{
175175
"db_name": "test-database",
176-
"creation_statements": "CREATE USER {{name}}",
176+
"creation_statements": `CREATE USER "{{name}}"`,
177177
"credential_type": tt.args.credentialType.String(),
178178
"credential_config": tt.args.credentialConfig,
179179
},

builtin/logical/database/path_rotate_credentials.go

Lines changed: 88 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,21 @@ func pathRotateRootCredentials(b *databaseBackend) []*framework.Path {
7474
}
7575
}
7676

77+
func (b *databaseBackend) rotateRootCredential(ctx context.Context, req *logical.Request) error {
78+
name, err := b.getDatabaseConfigNameFromRotationID(req.RotationID)
79+
if err != nil {
80+
return err
81+
}
82+
83+
_, err = b.performRootRotation(ctx, req, name)
84+
85+
return err
86+
}
87+
7788
func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationFunc {
7889
return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (resp *logical.Response, err error) {
7990
name := data.Get("name").(string)
80-
resp, err = b.rotateRootCredentials(ctx, req, name)
91+
resp, err = b.performRootRotation(ctx, req, name)
8192
if err != nil {
8293
b.Logger().Error("failed to rotate root credential on user request", "path", req.Path, "error", err.Error())
8394
} else {
@@ -87,7 +98,7 @@ func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationF
8798
}
8899
}
89100

90-
func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logical.Request, name string) (resp *logical.Response, err error) {
101+
func (b *databaseBackend) performRootRotation(ctx context.Context, req *logical.Request, name string) (resp *logical.Response, err error) {
91102
if name == "" {
92103
return logical.ErrorResponse(respErrEmptyName), nil
93104
}
@@ -118,21 +129,37 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
118129
}
119130
}()
120131

121-
rootUsername, ok := config.ConnectionDetails["username"].(string)
122-
if !ok || rootUsername == "" {
132+
rootUsername, userOk := config.ConnectionDetails["username"].(string)
133+
if !userOk || rootUsername == "" {
123134
return nil, fmt.Errorf("unable to rotate root credentials: no username in configuration")
124135
}
125136

126-
rootPassword, ok := config.ConnectionDetails["password"].(string)
127-
if !ok || rootPassword == "" {
128-
return nil, fmt.Errorf("unable to rotate root credentials: no password in configuration")
129-
}
130-
131137
dbi, err := b.GetConnection(ctx, req.Storage, name)
132138
if err != nil {
133139
return nil, err
134140
}
135141

142+
dbType, err := dbi.database.Type()
143+
if err != nil {
144+
return nil, fmt.Errorf("unable to determine database type: %w", err)
145+
}
146+
147+
rootPassword, passOk := config.ConnectionDetails["password"].(string)
148+
isPasswordSet := passOk && rootPassword != ""
149+
150+
rootPrivateKey, pkeyOk := config.ConnectionDetails["private_key"].(string)
151+
isPrivateKeySet := pkeyOk && rootPrivateKey != ""
152+
153+
// If both are unset, return an error. If we get past this, we know at least one is set.
154+
if !isPasswordSet && !isPrivateKeySet {
155+
return nil, fmt.Errorf("unable to rotate root credentials: both private_key and password fields are missing from the configuration")
156+
}
157+
158+
// If both are set, return an error.
159+
if isPasswordSet && isPrivateKeySet {
160+
return nil, fmt.Errorf("unable to rotate root credentials: both private_key and password fields are set in the configuration")
161+
}
162+
136163
// Take the write lock on the instance
137164
dbi.Lock()
138165
defer func() {
@@ -148,42 +175,67 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
148175
}
149176
}()
150177

151-
generator, err := newPasswordGenerator(nil)
152-
if err != nil {
153-
return nil, fmt.Errorf("failed to construct credential generator: %s", err)
178+
var walEntry *rotateRootCredentialsWAL
179+
var updateReq v5.UpdateUserRequest
180+
// If private key is set, use it. This takes precedence over password.
181+
if isPrivateKeySet {
182+
// For now snowflake is the only database type to support private key rotation.
183+
if dbType == "snowflake" {
184+
newKeypairCredentialConfig := map[string]interface{}{
185+
"format": "pkcs8",
186+
"key_bits": 4096,
187+
}
188+
newPublicKey, newPrivateKey, err := b.generateNewKeypair(newKeypairCredentialConfig)
189+
if err != nil {
190+
return nil, err
191+
}
192+
config.ConnectionDetails["private_key"] = string(newPrivateKey)
193+
194+
oldPrivateKey := config.ConnectionDetails["private_key"].(string)
195+
walEntry = NewRotateRootCredentialsWALPrivateKeyEntry(name, rootUsername, string(newPublicKey), string(newPrivateKey), oldPrivateKey)
196+
updateReq = v5.UpdateUserRequest{
197+
Username: rootUsername,
198+
CredentialType: v5.CredentialTypeRSAPrivateKey,
199+
PublicKey: &v5.ChangePublicKey{
200+
NewPublicKey: newPublicKey,
201+
Statements: v5.Statements{
202+
Commands: config.RootCredentialsRotateStatements,
203+
},
204+
},
205+
}
206+
}
207+
} else {
208+
// Private key isn't set so we're using the password field.
209+
newPassword, err := b.generateNewPassword(ctx, nil, config.PasswordPolicy, dbi)
210+
if err != nil {
211+
return nil, err
212+
}
213+
config.ConnectionDetails["password"] = newPassword
214+
215+
oldPassword := config.ConnectionDetails["password"].(string)
216+
walEntry = NewRotateRootCredentialsWALPasswordEntry(name, rootUsername, newPassword, oldPassword)
217+
updateReq = v5.UpdateUserRequest{
218+
Username: rootUsername,
219+
CredentialType: v5.CredentialTypePassword,
220+
Password: &v5.ChangePassword{
221+
NewPassword: newPassword,
222+
Statements: v5.Statements{
223+
Commands: config.RootCredentialsRotateStatements,
224+
},
225+
},
226+
}
154227
}
155-
generator.PasswordPolicy = config.PasswordPolicy
156228

157-
// Generate new credentials
158-
oldPassword := config.ConnectionDetails["password"].(string)
159-
newPassword, err := generator.generate(ctx, b, dbi.database)
160-
if err != nil {
161-
b.CloseIfShutdown(dbi, err)
162-
return nil, fmt.Errorf("failed to generate password: %s", err)
229+
if walEntry == nil {
230+
return nil, fmt.Errorf("unable to rotate root credentials: no valid credential type found")
163231
}
164-
config.ConnectionDetails["password"] = newPassword
165232

166233
// Write a WAL entry
167-
walID, err := framework.PutWAL(ctx, req.Storage, rotateRootWALKey, &rotateRootCredentialsWAL{
168-
ConnectionName: name,
169-
UserName: rootUsername,
170-
OldPassword: oldPassword,
171-
NewPassword: newPassword,
172-
})
234+
walID, err := framework.PutWAL(ctx, req.Storage, rotateRootWALKey, walEntry)
173235
if err != nil {
174236
return nil, err
175237
}
176238

177-
updateReq := v5.UpdateUserRequest{
178-
Username: rootUsername,
179-
CredentialType: v5.CredentialTypePassword,
180-
Password: &v5.ChangePassword{
181-
NewPassword: newPassword,
182-
Statements: v5.Statements{
183-
Commands: config.RootCredentialsRotateStatements,
184-
},
185-
},
186-
}
187239
newConfigDetails, err := dbi.database.UpdateUser(ctx, updateReq, true)
188240
if err != nil {
189241
return nil, fmt.Errorf("failed to update user: %w", err)

0 commit comments

Comments
 (0)