@@ -74,10 +74,21 @@ func pathRotateRootCredentials(b *databaseBackend) []*framework.Path {
74
74
}
75
75
}
76
76
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
+
77
88
func (b * databaseBackend ) pathRotateRootCredentialsUpdate () framework.OperationFunc {
78
89
return func (ctx context.Context , req * logical.Request , data * framework.FieldData ) (resp * logical.Response , err error ) {
79
90
name := data .Get ("name" ).(string )
80
- resp , err = b .rotateRootCredentials (ctx , req , name )
91
+ resp , err = b .performRootRotation (ctx , req , name )
81
92
if err != nil {
82
93
b .Logger ().Error ("failed to rotate root credential on user request" , "path" , req .Path , "error" , err .Error ())
83
94
} else {
@@ -87,7 +98,7 @@ func (b *databaseBackend) pathRotateRootCredentialsUpdate() framework.OperationF
87
98
}
88
99
}
89
100
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 ) {
91
102
if name == "" {
92
103
return logical .ErrorResponse (respErrEmptyName ), nil
93
104
}
@@ -118,21 +129,37 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
118
129
}
119
130
}()
120
131
121
- rootUsername , ok := config .ConnectionDetails ["username" ].(string )
122
- if ! ok || rootUsername == "" {
132
+ rootUsername , userOk := config .ConnectionDetails ["username" ].(string )
133
+ if ! userOk || rootUsername == "" {
123
134
return nil , fmt .Errorf ("unable to rotate root credentials: no username in configuration" )
124
135
}
125
136
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
-
131
137
dbi , err := b .GetConnection (ctx , req .Storage , name )
132
138
if err != nil {
133
139
return nil , err
134
140
}
135
141
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
+
136
163
// Take the write lock on the instance
137
164
dbi .Lock ()
138
165
defer func () {
@@ -148,42 +175,67 @@ func (b *databaseBackend) rotateRootCredentials(ctx context.Context, req *logica
148
175
}
149
176
}()
150
177
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
+ }
154
227
}
155
- generator .PasswordPolicy = config .PasswordPolicy
156
228
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" )
163
231
}
164
- config .ConnectionDetails ["password" ] = newPassword
165
232
166
233
// 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 )
173
235
if err != nil {
174
236
return nil , err
175
237
}
176
238
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
- }
187
239
newConfigDetails , err := dbi .database .UpdateUser (ctx , updateReq , true )
188
240
if err != nil {
189
241
return nil , fmt .Errorf ("failed to update user: %w" , err )
0 commit comments