181 lines
4.4 KiB
Go
181 lines
4.4 KiB
Go
package mssql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/helper/dbtxn"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
const SecretCredsType = "creds"
|
|
|
|
func secretCreds(b *backend) *framework.Secret {
|
|
return &framework.Secret{
|
|
Type: SecretCredsType,
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"username": {
|
|
Type: framework.TypeString,
|
|
Description: "Username",
|
|
},
|
|
|
|
"password": {
|
|
Type: framework.TypeString,
|
|
Description: "Password",
|
|
},
|
|
},
|
|
|
|
Renew: b.secretCredsRenew,
|
|
Revoke: b.secretCredsRevoke,
|
|
}
|
|
}
|
|
|
|
func (b *backend) secretCredsRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
// Get the lease information
|
|
leaseConfig, err := b.LeaseConfig(ctx, req.Storage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if leaseConfig == nil {
|
|
leaseConfig = &configLease{}
|
|
}
|
|
|
|
resp := &logical.Response{Secret: req.Secret}
|
|
resp.Secret.TTL = leaseConfig.TTL
|
|
resp.Secret.MaxTTL = leaseConfig.TTLMax
|
|
return resp, nil
|
|
}
|
|
|
|
func (b *backend) secretCredsRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
// Get the username from the internal data
|
|
usernameRaw, ok := req.Secret.InternalData["username"]
|
|
if !ok {
|
|
return nil, fmt.Errorf("secret is missing username internal data")
|
|
}
|
|
username, ok := usernameRaw.(string)
|
|
|
|
// Get our connection
|
|
db, err := b.DB(ctx, req.Storage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// First disable server login
|
|
disableStmt, err := db.Prepare(fmt.Sprintf("ALTER LOGIN [%s] DISABLE;", username))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer disableStmt.Close()
|
|
if _, err := disableStmt.Exec(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Query for sessions for the login so that we can kill any outstanding
|
|
// sessions. There cannot be any active sessions before we drop the logins
|
|
// This isn't done in a transaction because even if we fail along the way,
|
|
// we want to remove as much access as possible
|
|
sessionStmt, err := db.Prepare("SELECT session_id FROM sys.dm_exec_sessions WHERE login_name = @p1;")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer sessionStmt.Close()
|
|
|
|
sessionRows, err := sessionStmt.Query(username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer sessionRows.Close()
|
|
|
|
var revokeStmts []string
|
|
for sessionRows.Next() {
|
|
var sessionID int
|
|
err = sessionRows.Scan(&sessionID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
revokeStmts = append(revokeStmts, fmt.Sprintf("KILL %d;", sessionID))
|
|
}
|
|
|
|
// Query for database users using undocumented stored procedure for now since
|
|
// it is the easiest way to get this information;
|
|
// we need to drop the database users before we can drop the login and the role
|
|
// This isn't done in a transaction because even if we fail along the way,
|
|
// we want to remove as much access as possible
|
|
stmt, err := db.Prepare("EXEC master.dbo.sp_msloginmappings @p1;")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
rows, err := stmt.Query(username)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
var loginName, dbName, qUsername, aliasName sql.NullString
|
|
err = rows.Scan(&loginName, &dbName, &qUsername, &aliasName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !dbName.Valid {
|
|
continue
|
|
}
|
|
revokeStmts = append(revokeStmts, fmt.Sprintf(dropUserSQL, dbName.String, username, username))
|
|
}
|
|
|
|
// we do not stop on error, as we want to remove as
|
|
// many permissions as possible right now
|
|
var lastStmtError error
|
|
for _, query := range revokeStmts {
|
|
if err := dbtxn.ExecuteDBQuery(ctx, db, nil, query); err != nil {
|
|
lastStmtError = err
|
|
continue
|
|
}
|
|
}
|
|
|
|
// can't drop if not all database users are dropped
|
|
if rows.Err() != nil {
|
|
return nil, fmt.Errorf("could not generate sql statements for all rows: %w", rows.Err())
|
|
}
|
|
if lastStmtError != nil {
|
|
return nil, fmt.Errorf("could not perform all sql statements: %w", lastStmtError)
|
|
}
|
|
|
|
// Drop this login
|
|
stmt, err = db.Prepare(fmt.Sprintf(dropLoginSQL, username, username))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer stmt.Close()
|
|
if _, err := stmt.Exec(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
const dropUserSQL = `
|
|
USE [%s]
|
|
IF EXISTS
|
|
(SELECT name
|
|
FROM sys.database_principals
|
|
WHERE name = N'%s')
|
|
BEGIN
|
|
DROP USER [%s]
|
|
END
|
|
`
|
|
|
|
const dropLoginSQL = `
|
|
IF EXISTS
|
|
(SELECT name
|
|
FROM master.sys.server_principals
|
|
WHERE name = N'%s')
|
|
BEGIN
|
|
DROP LOGIN [%s]
|
|
END
|
|
`
|