open-vault/builtin/logical/mssql/path_creds_create.go
Christopher Swenson aa6d61477e
VAULT-5827 Don't prepare SQL queries before executing them (#15166)
VAULT-5827 Don't prepare SQL queries before executing them

We don't support proper prepared statements, i.e., preparing once and
executing many times since we do our own templating. So preparing our
queries does not really accomplish anything, and can have severe
performance impacts (see
https://github.com/hashicorp/vault-plugin-database-snowflake/issues/13
for example).

This behavior seems to have been copy-pasted for many years but not for
any particular reason that we have been able to find. First use was in
https://github.com/hashicorp/vault/pull/15

So here we switch to new methods suffixed with `Direct` to indicate
that they don't `Prepare` before running `Exec`, and switch everything
here to use those. We maintain the older methods with the existing
behavior (with `Prepare`) for backwards compatibility.
2022-04-26 12:47:06 -07:00

130 lines
3 KiB
Go

package mssql
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/go-secure-stdlib/strutil"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/dbtxn"
"github.com/hashicorp/vault/sdk/logical"
)
func pathCredsCreate(b *backend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeString,
Description: "Name of the role.",
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathCredsCreateRead,
},
HelpSynopsis: pathCredsCreateHelpSyn,
HelpDescription: pathCredsCreateHelpDesc,
}
}
func (b *backend) pathCredsCreateRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
name := data.Get("name").(string)
// Get the role
role, err := b.Role(ctx, req.Storage, name)
if err != nil {
return nil, err
}
if role == nil {
return logical.ErrorResponse(fmt.Sprintf("unknown role: %s", name)), nil
}
// Determine if we have a lease configuration
leaseConfig, err := b.LeaseConfig(ctx, req.Storage)
if err != nil {
return nil, err
}
if leaseConfig == nil {
leaseConfig = &configLease{}
}
// Generate our username and password
displayName := req.DisplayName
if len(displayName) > 10 {
displayName = displayName[:10]
}
userUUID, err := uuid.GenerateUUID()
if err != nil {
return nil, err
}
username := fmt.Sprintf("%s-%s", displayName, userUUID)
password, err := uuid.GenerateUUID()
if err != nil {
return nil, err
}
// Get our handle
db, err := b.DB(ctx, req.Storage)
if err != nil {
return nil, err
}
// Start a transaction
tx, err := db.Begin()
if err != nil {
return nil, err
}
defer tx.Rollback()
// Always reset database to default db of connection. Since it is in a
// transaction, all statements will be on the same connection in the pool.
roleSQL := fmt.Sprintf("USE [%s]; %s", b.defaultDb, role.SQL)
// Execute each query
for _, query := range strutil.ParseArbitraryStringSlice(roleSQL, ";") {
query = strings.TrimSpace(query)
if len(query) == 0 {
continue
}
m := map[string]string{
"name": username,
"password": password,
}
if err := dbtxn.ExecuteTxQueryDirect(ctx, tx, m, query); err != nil {
return nil, err
}
}
// Commit the transaction
if err := tx.Commit(); err != nil {
return nil, err
}
// Return the secret
resp := b.Secret(SecretCredsType).Response(map[string]interface{}{
"username": username,
"password": password,
}, map[string]interface{}{
"username": username,
})
resp.Secret.TTL = leaseConfig.TTL
resp.Secret.MaxTTL = leaseConfig.TTLMax
return resp, nil
}
const pathCredsCreateHelpSyn = `
Request database credentials for a certain role.
`
const pathCredsCreateHelpDesc = `
This path reads database credentials for a certain role. The
database credentials will be generated on demand and will be automatically
revoked when the lease is up.
`