mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-15 02:57:04 +02:00
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.
150 lines
3.4 KiB
Go
150 lines
3.4 KiB
Go
package postgresql
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"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"
|
|
_ "github.com/lib/pq"
|
|
)
|
|
|
|
func pathRoleCreate(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.pathRoleCreateRead,
|
|
},
|
|
|
|
HelpSynopsis: pathRoleCreateReadHelpSyn,
|
|
HelpDescription: pathRoleCreateReadHelpDesc,
|
|
}
|
|
}
|
|
|
|
func (b *backend) pathRoleCreateRead(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
|
|
lease, err := b.Lease(ctx, req.Storage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Unlike some other backends we need a lease here (can't leave as 0 and
|
|
// let core fill it in) because Postgres also expires users as a safety
|
|
// measure, so cannot be zero
|
|
if lease == nil {
|
|
lease = &configLease{
|
|
Lease: b.System().DefaultLeaseTTL(),
|
|
}
|
|
}
|
|
|
|
// Generate the username, password and expiration. PG limits user to 63 characters
|
|
displayName := req.DisplayName
|
|
if len(displayName) > 26 {
|
|
displayName = displayName[:26]
|
|
}
|
|
userUUID, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
username := fmt.Sprintf("%s-%s", displayName, userUUID)
|
|
if len(username) > 63 {
|
|
username = username[:63]
|
|
}
|
|
password, err := uuid.GenerateUUID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ttl, _, err := framework.CalculateTTL(b.System(), 0, lease.Lease, 0, lease.LeaseMax, 0, time.Time{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
expiration := time.Now().
|
|
Add(ttl).
|
|
Format("2006-01-02 15:04:05-0700")
|
|
|
|
// 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 func() {
|
|
tx.Rollback()
|
|
}()
|
|
|
|
// Execute each query
|
|
for _, query := range strutil.ParseArbitraryStringSlice(role.SQL, ";") {
|
|
query = strings.TrimSpace(query)
|
|
if len(query) == 0 {
|
|
continue
|
|
}
|
|
|
|
m := map[string]string{
|
|
"name": username,
|
|
"password": password,
|
|
"expiration": expiration,
|
|
}
|
|
|
|
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,
|
|
"role": name,
|
|
})
|
|
resp.Secret.TTL = lease.Lease
|
|
resp.Secret.MaxTTL = lease.LeaseMax
|
|
return resp, nil
|
|
}
|
|
|
|
const pathRoleCreateReadHelpSyn = `
|
|
Request database credentials for a certain role.
|
|
`
|
|
|
|
const pathRoleCreateReadHelpDesc = `
|
|
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.
|
|
`
|