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 {
stmts := changePass.Statements.Commands
if len(stmts) == 0 && !m.containedDB {
if len(stmts) == 0 {
stmts = []string{alterLoginSQL}
if m.containedDB {
stmts = []string{alterUserContainedSQL}
}
}
password := changePass.NewPassword
@ -384,6 +387,11 @@ func (m *MSSQL) updateUserPass(ctx context.Context, username string, changePass
_ = 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 _, query := range strutil.ParseArbitraryStringSlice(stmt, ";") {
query = strings.TrimSpace(query)
@ -431,3 +439,7 @@ EXEC (@stmt)`
const alterLoginSQL = `
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"
)
func TestInitialize(t *testing.T) {
func TestMSSQLInitialize(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
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)
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 {
req dbplugin.UpdateUserRequest
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)
defer cleanup()
@ -358,7 +358,7 @@ func TestDeleteUser(t *testing.T) {
assertCredsDoNotExist(t, connURL, dbUser, initPassword)
}
func TestDeleteUserContainedDB(t *testing.T) {
func TestMSSQLDeleteUserContainedDB(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup()
@ -405,7 +405,7 @@ func TestDeleteUserContainedDB(t *testing.T) {
assertContainedDBCredsDoNotExist(t, connURL, dbUser)
}
func TestContainedDBSQLSanitization(t *testing.T) {
func TestMSSQLContainedDBSQLSanitization(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
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.")
}
func TestSQLSanitization(t *testing.T) {
func TestMSSQLSanitization(t *testing.T) {
cleanup, connURL := mssqlhelper.PrepareMSSQLTestContainer(t)
defer cleanup()
@ -576,3 +576,11 @@ const testMSSQLContainedLogin = `
CREATE LOGIN [{{name}}] WITH PASSWORD = '{{password}}';
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
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>
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>
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
@ -129,6 +139,7 @@ First, we mount a database backend at the azuresql path with `vault secrets enab
$ vault write azuresql/config/testvault \
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;' \
contained_db=true \
allowed_roles="test"
```