database/mssql: set default root rotation stmt for contained db (#29399)

* database/mssql: set default root rotation stmt for contained db

* changelog

* add rotate root test

* fix test

* update passwords to make mssql happy

* create admin user

* update contained user create query

* remove test
This commit is contained in:
John-Michael Faircloth 2025-01-24 14:42:27 -06:00 committed by GitHub
parent 9d31bb8586
commit 04e75372fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 44 additions and 10 deletions

3
changelog/29399.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
database/mssql: Fix a bug where contained databases would silently fail root rotation if a custom root rotation statement was not provided.
```

View File

@ -345,8 +345,11 @@ func (m *MSSQL) UpdateUser(ctx context.Context, req dbplugin.UpdateUserRequest)
func (m *MSSQL) updateUserPass(ctx context.Context, username string, changePass *dbplugin.ChangePassword) error { func (m *MSSQL) updateUserPass(ctx context.Context, username string, changePass *dbplugin.ChangePassword) error {
stmts := changePass.Statements.Commands stmts := changePass.Statements.Commands
if len(stmts) == 0 && !m.containedDB { if len(stmts) == 0 {
stmts = []string{alterLoginSQL} stmts = []string{alterLoginSQL}
if m.containedDB {
stmts = []string{alterUserContainedSQL}
}
} }
password := changePass.NewPassword password := changePass.NewPassword
@ -384,6 +387,11 @@ func (m *MSSQL) updateUserPass(ctx context.Context, username string, changePass
_ = tx.Rollback() _ = tx.Rollback()
}() }()
if len(stmts) == 0 {
// should not happen, but guard against it anyway
return errors.New("no statement provided")
}
for _, stmt := range stmts { for _, stmt := range stmts {
for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") { for _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") {
query = strings.TrimSpace(query) query = strings.TrimSpace(query)
@ -431,3 +439,7 @@ EXEC (@stmt)`
const alterLoginSQL = ` const alterLoginSQL = `
ALTER LOGIN [{{username}}] WITH PASSWORD = '{{password}}' ALTER LOGIN [{{username}}] WITH PASSWORD = '{{password}}'
` `
const alterUserContainedSQL = `
ALTER USER [{{username}}] WITH PASSWORD = '{{password}}'
`

View File

@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestInitialize(t *testing.T) { func TestMSSQLInitialize(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t) cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup() defer cleanup()
@ -79,7 +79,7 @@ func TestInitialize(t *testing.T) {
} }
} }
func TestNewUser(t *testing.T) { func TestMSSQLNewUser(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t) cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup() defer cleanup()
@ -185,7 +185,7 @@ func TestNewUser(t *testing.T) {
} }
} }
func TestUpdateUser_password(t *testing.T) { func TestMSSQLUpdateUser_password(t *testing.T) {
type testCase struct { type testCase struct {
req dbplugin.UpdateUserRequest req dbplugin.UpdateUserRequest
expectErr bool expectErr bool
@ -312,7 +312,7 @@ func TestUpdateUser_password(t *testing.T) {
} }
} }
func TestDeleteUser(t *testing.T) { func TestMSSQLDeleteUser(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t) cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup() defer cleanup()
@ -358,7 +358,7 @@ func TestDeleteUser(t *testing.T) {
assertCredsDoNotExist(t, connURL, dbUser, initPassword) assertCredsDoNotExist(t, connURL, dbUser, initPassword)
} }
func TestDeleteUserContainedDB(t *testing.T) { func TestMSSQLDeleteUserContainedDB(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t) cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup() defer cleanup()
@ -405,7 +405,7 @@ func TestDeleteUserContainedDB(t *testing.T) {
assertContainedDBCredsDoNotExist(t, connURL, dbUser) assertContainedDBCredsDoNotExist(t, connURL, dbUser)
} }
func TestContainedDBSQLSanitization(t *testing.T) { func TestMSSQLContainedDBSQLSanitization(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t) cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup() defer cleanup()
@ -443,7 +443,7 @@ func TestContainedDBSQLSanitization(t *testing.T) {
assert.EqualError(t, err, "mssql: Cannot alter the login 'vaultuser]', because it does not exist or you do not have permission.") assert.EqualError(t, err, "mssql: Cannot alter the login 'vaultuser]', because it does not exist or you do not have permission.")
} }
func TestSQLSanitization(t *testing.T) { func TestMSSQLSanitization(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t) cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup() defer cleanup()
@ -576,3 +576,11 @@ const testMSSQLContainedLogin = `
CREATE LOGIN [{{name}}] WITH PASSWORD = '{{password}}'; CREATE LOGIN [{{name}}] WITH PASSWORD = '{{password}}';
CREATE USER [{{name}}] FOR LOGIN [{{name}}]; CREATE USER [{{name}}] FOR LOGIN [{{name}}];
` `
const testMSSQLContainedLoginAdmin = `
CREATE USER [{{name}}] WITH PASSWORD = '{{password}}';
ALTER ROLE db_datareader ADD MEMBER [{{name}}];
ALTER ROLE db_datawriter ADD MEMBER [{{name}}];
ALTER ROLE db_owner ADD MEMBER [{{name}}];
`

View File

@ -115,13 +115,23 @@ the proper permission, it can generate credentials.
## Example for Azure SQL database ## Example for Azure SQL database
Here is a complete example using Azure SQL Database. Note that databases in Azure SQL Database are [contained databases](https://docs.microsoft.com/en-us/sql/relational-databases/databases/contained-databases) and that we do not create a login for the user; instead, we associate the password directly with the user itself. Also note that you will need a separate connection and role for each Azure SQL database for which you want to generate dynamic credentials. You can use a single database backend mount for all these databases or use a separate mount for each of them. In this example, we use a custom path for the database backend. Here is a complete example using Azure SQL Database. Note that databases in
Azure SQL Database are [contained databases](https://docs.microsoft.com/en-us/sql/relational-databases/databases/contained-databases)
and that we do not create a login for the user; instead, we associate the
password directly with the user itself. Also note that you will need a separate
connection and role for each Azure SQL database for which you want to generate
dynamic credentials. You can use a single database backend mount for all these
databases or use a separate mount for each of them. In this example, we use a
custom path for the database backend.
<Note> <Note>
Azure SQL databases may use different authentication mechanism that are configured on the SQL server. Vault only supports SQL authentication. Azure AD authentication is not supported. Azure SQL databases may use different authentication mechanism that are configured on the SQL server. Vault only supports SQL authentication. Azure AD authentication is not supported.
</Note> </Note>
First, we mount a database backend at the azuresql path with `vault secrets enable -path=azuresql database`. Then we configure a connection called "testvault" to connect to a database called "test-vault", using "azuresql" at the beginning of our path: First, we mount a database backend at the azuresql path with `vault secrets
enable -path=azuresql database`. Then we configure a connection called
"testvault" to connect to a database called "test-vault", using "azuresql" at
the beginning of our path and set the `contained_db` field:
~> Note: If you are using a windows vault client with cmd.exe, change the single quotes to double quotes in the connection string. Windows cmd.exe does not interpret single quotes as a continous string ~> Note: If you are using a windows vault client with cmd.exe, change the single quotes to double quotes in the connection string. Windows cmd.exe does not interpret single quotes as a continous string
@ -129,6 +139,7 @@ First, we mount a database backend at the azuresql path with `vault secrets enab
$ vault write azuresql/config/testvault \ $ vault write azuresql/config/testvault \
plugin_name=mssql-database-plugin \ plugin_name=mssql-database-plugin \
connection_url='server=hashisqlserver.database.windows.net;port=1433;user id=admin;password=pAssw0rd;database=test-vault;app name=vault;' \ connection_url='server=hashisqlserver.database.windows.net;port=1433;user id=admin;password=pAssw0rd;database=test-vault;app name=vault;' \
contained_db=true \
allowed_roles="test" allowed_roles="test"
``` ```