open-vault/builtin/logical/postgresql/secret_creds.go
Jeff Mitchell 2d8e3b35f2 Revoke permissions before dropping user in postgresql.
Currently permissions are not revoked, which can lead revocation to not
actually work properly. This attempts to revoke all permissions and only
then drop the role.

Fixes issue #699
2015-10-30 11:58:52 -04:00

171 lines
4.1 KiB
Go

package postgresql
import (
"fmt"
"time"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
"github.com/lib/pq"
)
const SecretCredsType = "creds"
func secretCreds(b *backend) *framework.Secret {
return &framework.Secret{
Type: SecretCredsType,
Fields: map[string]*framework.FieldSchema{
"username": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Username",
},
"password": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Password",
},
},
DefaultDuration: 1 * time.Hour,
DefaultGracePeriod: 10 * time.Minute,
Renew: b.secretCredsRenew,
Revoke: b.secretCredsRevoke,
}
}
func (b *backend) secretCredsRenew(
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(req.Storage)
if err != nil {
return nil, err
}
// Get the lease information
lease, err := b.Lease(req.Storage)
if err != nil {
return nil, err
}
if lease == nil {
lease = &configLease{Lease: 1 * time.Hour}
}
f := framework.LeaseExtend(lease.Lease, lease.LeaseMax, false)
resp, err := f(req, d)
if err != nil {
return nil, err
}
// Make sure we increase the VALID UNTIL endpoint for this user.
if expireTime := resp.Secret.ExpirationTime(); !expireTime.IsZero() {
expiration := expireTime.Add(10 * time.Minute).
Format("2006-01-02 15:04:05-0700")
query := fmt.Sprintf(
"ALTER ROLE %s VALID UNTIL '%s';",
pq.QuoteIdentifier(username),
expiration)
stmt, err := db.Prepare(query)
if err != nil {
return nil, err
}
defer stmt.Close()
if _, err := stmt.Exec(); err != nil {
return nil, err
}
}
return resp, nil
}
func (b *backend) secretCredsRevoke(
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(req.Storage)
if err != nil {
return nil, err
}
// Query for permissions; we need to revoke permissions before we can drop
// 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(fmt.Sprintf(
"SELECT DISTINCT table_schema FROM information_schema.role_column_grants WHERE grantee='%s';",
username))
if err != nil {
return nil, err
}
defer stmt.Close()
rows, err := stmt.Query()
if err != nil {
return nil, err
}
defer rows.Close()
var revocationStmts []string
for rows.Next() {
var schema string
err = rows.Scan(&schema)
if err != nil {
// keep going; remove as many permissions as possible right now
continue
}
revocationStmts = append(revocationStmts, fmt.Sprintf(
"REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA %s FROM %s;",
schema, pq.QuoteIdentifier(username)))
}
// again, here, we do not stop on error, as we want to remove as
// many permissions as possible right now
var lastStmtError error
for _, query := range revocationStmts {
stmt, err := db.Prepare(query)
if err != nil {
lastStmtError = err
continue
}
_, err = stmt.Exec()
if err != nil {
lastStmtError = err
}
}
// can't drop if not all privileges are revoked
if rows.Err() != nil {
return logical.ErrorResponse(fmt.Sprintf("could not generate revocation statements for all rows: %v", rows.Err())), nil
}
if lastStmtError != nil {
return logical.ErrorResponse(fmt.Sprintf("could not perform all revocation statements: %v", lastStmtError)), nil
}
// Drop this user
stmt, err = db.Prepare(fmt.Sprintf(
"DROP ROLE IF EXISTS %s;", pq.QuoteIdentifier(username)))
if err != nil {
return nil, err
}
defer stmt.Close()
if _, err := stmt.Exec(); err != nil {
return nil, err
}
return nil, nil
}