From 128f25c13d6dc93a509313d0ab2cd16e980f43f2 Mon Sep 17 00:00:00 2001 From: Brian Kassouf Date: Tue, 11 Apr 2017 11:50:34 -0700 Subject: [PATCH] Update help text and comments --- builtin/logical/database/dbplugin/client.go | 4 +- .../database/dbplugin/databasemiddleware.go | 4 + builtin/logical/database/dbplugin/plugin.go | 4 + builtin/logical/database/dbplugin/server.go | 5 +- .../database/path_config_connection.go | 87 ++++--- builtin/logical/database/path_role_create.go | 78 ++++--- builtin/logical/database/path_roles.go | 218 ++++++++++-------- builtin/logical/database/secret_creds.go | 190 +++++++-------- logical/system_view.go | 4 +- 9 files changed, 323 insertions(+), 271 deletions(-) diff --git a/builtin/logical/database/dbplugin/client.go b/builtin/logical/database/dbplugin/client.go index da39ed425..5bdc3a01a 100644 --- a/builtin/logical/database/dbplugin/client.go +++ b/builtin/logical/database/dbplugin/client.go @@ -10,7 +10,7 @@ import ( "github.com/hashicorp/vault/helper/pluginutil" ) -// DatabasePluginClient embeds a databasePluginRPCClient and wraps it's close +// DatabasePluginClient embeds a databasePluginRPCClient and wraps it's Close // method to also call Kill() on the plugin.Client. type DatabasePluginClient struct { client *plugin.Client @@ -64,7 +64,7 @@ func newPluginClient(sys pluginutil.Wrapper, pluginRunner *pluginutil.PluginRunn // ---- RPC client domain ---- -// databasePluginRPCClient impliments DatabaseType and is used on the client to +// databasePluginRPCClient implements DatabaseType and is used on the client to // make RPC calls to a plugin. type databasePluginRPCClient struct { client *rpc.Client diff --git a/builtin/logical/database/dbplugin/databasemiddleware.go b/builtin/logical/database/dbplugin/databasemiddleware.go index 1df7be3bb..2137cd9c3 100644 --- a/builtin/logical/database/dbplugin/databasemiddleware.go +++ b/builtin/logical/database/dbplugin/databasemiddleware.go @@ -9,6 +9,8 @@ import ( // ---- Tracing Middleware Domain ---- +// databaseTracingMiddleware wraps a implementation of DatabaseType and executes +// trace logging on function call. type databaseTracingMiddleware struct { next DatabaseType logger log.Logger @@ -77,6 +79,8 @@ func (mw *databaseTracingMiddleware) Close() (err error) { // ---- Metrics Middleware Domain ---- +// databaseMetricsMiddleware wraps an implementation of DatabaseTypes and on +// function call logs metrics about this instance. type databaseMetricsMiddleware struct { next DatabaseType diff --git a/builtin/logical/database/dbplugin/plugin.go b/builtin/logical/database/dbplugin/plugin.go index 39655bf46..dadb6639e 100644 --- a/builtin/logical/database/dbplugin/plugin.go +++ b/builtin/logical/database/dbplugin/plugin.go @@ -40,11 +40,13 @@ func PluginFactory(pluginName string, sys pluginutil.LookWrapper, logger log.Log return nil, ErrEmptyPluginName } + // Look for plugin in the plugin catalog pluginMeta, err := sys.LookupPlugin(pluginName) if err != nil { return nil, err } + // create a DatabasePluginClient instance db, err := newPluginClient(sys, pluginMeta) if err != nil { return nil, err @@ -76,6 +78,8 @@ var handshakeConfig = plugin.HandshakeConfig{ MagicCookieValue: "926a0820-aea2-be28-51d6-83cdf00e8edb", } +// DatabasePlugin implements go-plugin's Plugin interface. It has methods for +// retrieving a server and a client instance of the plugin. type DatabasePlugin struct { impl DatabaseType } diff --git a/builtin/logical/database/dbplugin/server.go b/builtin/logical/database/dbplugin/server.go index 5c1b41a3d..326e25103 100644 --- a/builtin/logical/database/dbplugin/server.go +++ b/builtin/logical/database/dbplugin/server.go @@ -8,7 +8,7 @@ import ( ) // NewPluginServer is called from within a plugin and wraps the provided -// DatabaseType implimentation in a databasePluginRPCServer object and starts a +// DatabaseType implementation in a databasePluginRPCServer object and starts a // RPC server. func NewPluginServer(db DatabaseType) { dbPlugin := &DatabasePlugin{ @@ -35,7 +35,8 @@ func NewPluginServer(db DatabaseType) { // ---- RPC server domain ---- -// databasePluginRPCServer impliments DatabaseType and is run inside a plugin +// databasePluginRPCServer implements an RPC version of DatabaseType and is run +// inside a plugin. It wraps an underlying implementation of DatabaseType. type databasePluginRPCServer struct { impl DatabaseType } diff --git a/builtin/logical/database/path_config_connection.go b/builtin/logical/database/path_config_connection.go index c242aa339..5817f53c2 100644 --- a/builtin/logical/database/path_config_connection.go +++ b/builtin/logical/database/path_config_connection.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/vault/logical/framework" ) +// pathResetConnection configures a path to reset a plugin. func pathResetConnection(b *databaseBackend) *framework.Path { return &framework.Path{ Pattern: fmt.Sprintf("reset/%s", framework.GenericNameRegex("name")), @@ -20,32 +21,36 @@ func pathResetConnection(b *databaseBackend) *framework.Path { }, Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.UpdateOperation: b.pathConnectionReset, + logical.UpdateOperation: b.pathConnectionReset(), }, - HelpSynopsis: pathConfigConnectionHelpSyn, - HelpDescription: pathConfigConnectionHelpDesc, + HelpSynopsis: pathResetConnectionHelpSyn, + HelpDescription: pathResetConnectionHelpDesc, } } -func (b *databaseBackend) pathConnectionReset(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - name := data.Get("name").(string) - if name == "" { - return logical.ErrorResponse("Empty name attribute given"), nil +// pathConnectionReset resets a plugin by closing the existing instance and +// creating a new one. +func (b *databaseBackend) pathConnectionReset() framework.OperationFunc { + return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + name := data.Get("name").(string) + if name == "" { + return logical.ErrorResponse("Empty name attribute given"), nil + } + + // Grab the mutex lock + b.Lock() + defer b.Unlock() + + b.clearConnection(name) + + _, err := b.getOrCreateDBObj(req.Storage, name) + if err != nil { + return nil, err + } + + return nil, nil } - - // Grab the mutex lock - b.Lock() - defer b.Unlock() - - b.clearConnection(name) - - _, err := b.getOrCreateDBObj(req.Storage, name) - if err != nil { - return nil, err - } - - return nil, nil } // pathConfigurePluginConnection returns a configured framework.Path setup to @@ -60,15 +65,17 @@ func pathConfigurePluginConnection(b *databaseBackend) *framework.Path { }, "verify_connection": &framework.FieldSchema{ - Type: framework.TypeBool, - Default: true, - Description: `If set, connection_url is verified by actually connecting to the database`, + Type: framework.TypeBool, + Default: true, + Description: `If set, the connection details are verified by + actually connecting to the database`, }, "plugin_name": &framework.FieldSchema{ Type: framework.TypeString, - Description: `Maximum amount of time a connection may be reused; - a zero or negative value reuses connections forever.`, + Description: `The name of a builtin or previously registered + plugin known to vault. This endpoint will create an instance of + that plugin type.`, }, }, @@ -198,16 +205,32 @@ func (b *databaseBackend) connectionWriteHandler() framework.OperationFunc { } const pathConfigConnectionHelpSyn = ` -Configure the connection string to talk to PostgreSQL. +Configure connection details to a database plugin. ` const pathConfigConnectionHelpDesc = ` -This path configures the connection string used to connect to PostgreSQL. -The value of the string can be a URL, or a PG style string in the -format of "user=foo host=bar" etc. +This path configures the connection details used to connect to a particular +database. This path runs the provided plugin name and passes the configured +connection details to the plugin. See the documentation for the plugin specified +for a full list of accepted connection details. -The URL looks like: -"postgresql://user:pass@host:port/dbname" +In addition to the database specific connection details, this endpoing also +accepts: -When configuring the connection string, the backend will verify its validity. + * "plugin_name" (required) - The name of a builtin or previously registered + plugin known to vault. This endpoint will create an instance of that + plugin type. + + * "verify_connection" - A boolean value denoting if the plugin should verify + it is able to connect to the database using the provided connection + details. +` + +const pathResetConnectionHelpSyn = ` +Resets a database plugin. +` + +const pathResetConnectionHelpDesc = ` +This path resets the database connection by closing the existing database plugin +instance and running a new one. ` diff --git a/builtin/logical/database/path_role_create.go b/builtin/logical/database/path_role_create.go index 5a16c8926..59584e943 100644 --- a/builtin/logical/database/path_role_create.go +++ b/builtin/logical/database/path_role_create.go @@ -19,7 +19,7 @@ func pathRoleCreate(b *databaseBackend) *framework.Path { }, Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.pathRoleCreateRead, + logical.ReadOperation: b.pathRoleCreateRead(), }, HelpSynopsis: pathRoleCreateReadHelpSyn, @@ -27,45 +27,47 @@ func pathRoleCreate(b *databaseBackend) *framework.Path { } } -func (b *databaseBackend) pathRoleCreateRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - name := data.Get("name").(string) +func (b *databaseBackend) pathRoleCreateRead() framework.OperationFunc { + return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + name := data.Get("name").(string) - // Get the role - role, err := b.Role(req.Storage, name) - if err != nil { - return nil, err + // Get the role + role, err := b.Role(req.Storage, name) + if err != nil { + return nil, err + } + if role == nil { + return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", name)), nil + } + + b.Lock() + defer b.Unlock() + + // Get the Database object + db, err := b.getOrCreateDBObj(req.Storage, role.DBName) + if err != nil { + // TODO: return a resp error instead? + return nil, fmt.Errorf("cound not retrieve db with name: %s, got error: %s", role.DBName, err) + } + + expiration := time.Now().Add(role.DefaultTTL) + + // Create the user + username, password, err := db.CreateUser(role.Statements, req.DisplayName, expiration) + if err != nil { + return nil, err + } + + resp := b.Secret(SecretCredsType).Response(map[string]interface{}{ + "username": username, + "password": password, + }, map[string]interface{}{ + "username": username, + "role": name, + }) + resp.Secret.TTL = role.DefaultTTL + return resp, nil } - if role == nil { - return logical.ErrorResponse(fmt.Sprintf("Unknown role: %s", name)), nil - } - - b.Lock() - defer b.Unlock() - - // Get the Database object - db, err := b.getOrCreateDBObj(req.Storage, role.DBName) - if err != nil { - // TODO: return a resp error instead? - return nil, fmt.Errorf("cound not retrieve db with name: %s, got error: %s", role.DBName, err) - } - - expiration := time.Now().Add(role.DefaultTTL) - - // Create the user - username, password, err := db.CreateUser(role.Statements, req.DisplayName, expiration) - if err != nil { - return nil, err - } - - resp := b.Secret(SecretCredsType).Response(map[string]interface{}{ - "username": username, - "password": password, - }, map[string]interface{}{ - "username": username, - "role": name, - }) - resp.Secret.TTL = role.DefaultTTL - return resp, nil } const pathRoleCreateReadHelpSyn = ` diff --git a/builtin/logical/database/path_roles.go b/builtin/logical/database/path_roles.go index a6989df24..263a555e6 100644 --- a/builtin/logical/database/path_roles.go +++ b/builtin/logical/database/path_roles.go @@ -14,7 +14,7 @@ func pathListRoles(b *databaseBackend) *framework.Path { Pattern: "roles/?$", Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ListOperation: b.pathRoleList, + logical.ListOperation: b.pathRoleList(), }, HelpSynopsis: pathRoleHelpSyn, @@ -35,12 +35,13 @@ func pathRoles(b *databaseBackend) *framework.Path { Type: framework.TypeString, Description: "Name of the database this role acts on.", }, - "creation_statements": { - Type: framework.TypeString, - Description: "SQL string to create a user. See help for more info.", + Type: framework.TypeString, + Description: `Statements to be executed to create a user. Must be a semicolon-separated + string, a base64-encoded semicolon-separated string, a serialized JSON string + array, or a base64-encoded serialized JSON string array. The '{{name}}', + '{{password}}', and '{{expiration}}' values will be substituted.`, }, - "revocation_statements": { Type: framework.TypeString, Description: `Statements to be executed to revoke a user. Must be a semicolon-separated @@ -75,9 +76,9 @@ func pathRoles(b *databaseBackend) *framework.Path { }, Callbacks: map[logical.Operation]framework.OperationFunc{ - logical.ReadOperation: b.pathRoleRead, - logical.UpdateOperation: b.pathRoleCreate, - logical.DeleteOperation: b.pathRoleDelete, + logical.ReadOperation: b.pathRoleRead(), + logical.UpdateOperation: b.pathRoleCreate(), + logical.DeleteOperation: b.pathRoleDelete(), }, HelpSynopsis: pathRoleHelpSyn, @@ -85,101 +86,107 @@ func pathRoles(b *databaseBackend) *framework.Path { } } -func (b *databaseBackend) pathRoleDelete(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - err := req.Storage.Delete("role/" + data.Get("name").(string)) - if err != nil { - return nil, err - } +func (b *databaseBackend) pathRoleDelete() framework.OperationFunc { + return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + err := req.Storage.Delete("role/" + data.Get("name").(string)) + if err != nil { + return nil, err + } - return nil, nil -} - -func (b *databaseBackend) pathRoleRead(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - role, err := b.Role(req.Storage, data.Get("name").(string)) - if err != nil { - return nil, err - } - if role == nil { return nil, nil } - - return &logical.Response{ - Data: map[string]interface{}{ - "creation_statements": role.Statements.CreationStatements, - "revocation_statements": role.Statements.RevocationStatements, - "rollback_statements": role.Statements.RollbackStatements, - "renew_statements": role.Statements.RenewStatements, - "default_ttl": role.DefaultTTL.String(), - "max_ttl": role.MaxTTL.String(), - }, - }, nil } -func (b *databaseBackend) pathRoleList(req *logical.Request, d *framework.FieldData) (*logical.Response, error) { - entries, err := req.Storage.List("role/") - if err != nil { - return nil, err - } +func (b *databaseBackend) pathRoleRead() framework.OperationFunc { + return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + role, err := b.Role(req.Storage, data.Get("name").(string)) + if err != nil { + return nil, err + } + if role == nil { + return nil, nil + } - return logical.ListResponse(entries), nil + return &logical.Response{ + Data: map[string]interface{}{ + "creation_statements": role.Statements.CreationStatements, + "revocation_statements": role.Statements.RevocationStatements, + "rollback_statements": role.Statements.RollbackStatements, + "renew_statements": role.Statements.RenewStatements, + "default_ttl": role.DefaultTTL.String(), + "max_ttl": role.MaxTTL.String(), + }, + }, nil + } } -func (b *databaseBackend) pathRoleCreate(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - name := data.Get("name").(string) - if name == "" { - return logical.ErrorResponse("Empty role name attribute given"), nil +func (b *databaseBackend) pathRoleList() framework.OperationFunc { + return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + entries, err := req.Storage.List("role/") + if err != nil { + return nil, err + } + + return logical.ListResponse(entries), nil } +} - dbName := data.Get("db_name").(string) - if dbName == "" { - return logical.ErrorResponse("Empty database name attribute given"), nil +func (b *databaseBackend) pathRoleCreate() framework.OperationFunc { + return func(req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + name := data.Get("name").(string) + if name == "" { + return logical.ErrorResponse("Empty role name attribute given"), nil + } + + dbName := data.Get("db_name").(string) + if dbName == "" { + return logical.ErrorResponse("Empty database name attribute given"), nil + } + + // Get statements + creationStmts := data.Get("creation_statements").(string) + revocationStmts := data.Get("revocation_statements").(string) + rollbackStmts := data.Get("rollback_statements").(string) + renewStmts := data.Get("renew_statements").(string) + + // Get TTLs + defaultTTLRaw := data.Get("default_ttl").(string) + maxTTLRaw := data.Get("max_ttl").(string) + + defaultTTL, err := time.ParseDuration(defaultTTLRaw) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf( + "Invalid default_ttl: %s", err)), nil + } + maxTTL, err := time.ParseDuration(maxTTLRaw) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf( + "Invalid max_ttl: %s", err)), nil + } + + statements := dbplugin.Statements{ + CreationStatements: creationStmts, + RevocationStatements: revocationStmts, + RollbackStatements: rollbackStmts, + RenewStatements: renewStmts, + } + + // Store it + entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{ + DBName: dbName, + Statements: statements, + DefaultTTL: defaultTTL, + MaxTTL: maxTTL, + }) + if err != nil { + return nil, err + } + if err := req.Storage.Put(entry); err != nil { + return nil, err + } + + return nil, nil } - - // Get statements - creationStmts := data.Get("creation_statements").(string) - revocationStmts := data.Get("revocation_statements").(string) - rollbackStmts := data.Get("rollback_statements").(string) - renewStmts := data.Get("renew_statements").(string) - - // Get TTLs - defaultTTLRaw := data.Get("default_ttl").(string) - maxTTLRaw := data.Get("max_ttl").(string) - - defaultTTL, err := time.ParseDuration(defaultTTLRaw) - if err != nil { - return logical.ErrorResponse(fmt.Sprintf( - "Invalid default_ttl: %s", err)), nil - } - maxTTL, err := time.ParseDuration(maxTTLRaw) - if err != nil { - return logical.ErrorResponse(fmt.Sprintf( - "Invalid max_ttl: %s", err)), nil - } - - statements := dbplugin.Statements{ - CreationStatements: creationStmts, - RevocationStatements: revocationStmts, - RollbackStatements: rollbackStmts, - RenewStatements: renewStmts, - } - - // TODO: Think about preparing the statments to test. - - // Store it - entry, err := logical.StorageEntryJSON("role/"+name, &roleEntry{ - DBName: dbName, - Statements: statements, - DefaultTTL: defaultTTL, - MaxTTL: maxTTL, - }) - if err != nil { - return nil, err - } - if err := req.Storage.Put(entry); err != nil { - return nil, err - } - - return nil, nil } type roleEntry struct { @@ -196,10 +203,14 @@ Manage the roles that can be created with this backend. const pathRoleHelpDesc = ` This path lets you manage the roles that can be created with this backend. -The "sql" parameter customizes the SQL string used to create the role. -This can be a sequence of SQL queries. Some substitution will be done to the -SQL string for certain keys. The names of the variables must be surrounded -by "{{" and "}}" to be replaced. +The "db_name" parameter is required and configures the name of the database +connection to use. + +The "creation_statements" parameter customizes the string used to create the +credentials. This can be a sequence of SQL queries, or other statement formats +for a particular database type. Some substitution will be done to the statement +strings for certain keys. The names of the variables must be surrounded by "{{" +and "}}" to be replaced. * "name" - The random username generated for the DB user. @@ -207,7 +218,7 @@ by "{{" and "}}" to be replaced. * "expiration" - The timestamp when this user will expire. -Example of a decent SQL query to use: +Example of a decent creation_statements for a postgresql database plugin: CREATE ROLE "{{name}}" WITH LOGIN @@ -215,14 +226,17 @@ Example of a decent SQL query to use: VALID UNTIL '{{expiration}}'; GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}"; -Note the above user would be able to access everything in schema public. -For more complex GRANT clauses, see the PostgreSQL manual. - -The "revocation_sql" parameter customizes the SQL string used to revoke a user. -Example of a decent revocation SQL query to use: +The "revocation_statements" parameter customizes the statement string used to +revoke a user. Example of a decent revocation_statements for a postgresql +database plugin: REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM {{name}}; REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM {{name}}; REVOKE USAGE ON SCHEMA public FROM {{name}}; DROP ROLE IF EXISTS {{name}}; + +The "renew_statements" parameter customizes the statement string used to renew a +user. +The "rollback_statements' parameter customizes the statement string used to +rollback a change if needed. ` diff --git a/builtin/logical/database/secret_creds.go b/builtin/logical/database/secret_creds.go index 5701e373a..ffc59cf3f 100644 --- a/builtin/logical/database/secret_creds.go +++ b/builtin/logical/database/secret_creds.go @@ -14,112 +14,116 @@ func secretCreds(b *databaseBackend) *framework.Secret { Type: SecretCredsType, Fields: map[string]*framework.FieldSchema{}, - Renew: b.secretCredsRenew, - Revoke: b.secretCredsRevoke, + Renew: b.secretCredsRenew(), + Revoke: b.secretCredsRevoke(), } } -func (b *databaseBackend) 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) - - roleNameRaw, ok := req.Secret.InternalData["role"] - if !ok { - return nil, fmt.Errorf("could not find role with name: %s", req.Secret.InternalData["role"]) - } - - role, err := b.Role(req.Storage, roleNameRaw.(string)) - if err != nil { - return nil, err - } - if role == nil { - return nil, fmt.Errorf("could not find role with name: %s", req.Secret.InternalData["role"]) - } - - f := framework.LeaseExtend(role.DefaultTTL, role.MaxTTL, b.System()) - resp, err := f(req, d) - if err != nil { - return nil, err - } - - // Grab the read lock - b.Lock() - defer b.Unlock() - - // Get our connection - db, err := b.getOrCreateDBObj(req.Storage, role.DBName) - if err != nil { - return nil, fmt.Errorf("could not find connection with name %s, got err: %s", role.DBName, err) - } - - // Make sure we increase the VALID UNTIL endpoint for this user. - if expireTime := resp.Secret.ExpirationTime(); !expireTime.IsZero() { - err := db.RenewUser(role.Statements, username, expireTime) - if err != nil { - return nil, err +func (b *databaseBackend) secretCredsRenew() framework.OperationFunc { + return func(req *logical.Request, data *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) - return resp, nil -} + roleNameRaw, ok := req.Secret.InternalData["role"] + if !ok { + return nil, fmt.Errorf("could not find role with name: %s", req.Secret.InternalData["role"]) + } -func (b *databaseBackend) 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) - - var resp *logical.Response - - roleNameRaw, ok := req.Secret.InternalData["role"] - if !ok { - return nil, fmt.Errorf("no role name was provided") - } - - role, err := b.Role(req.Storage, roleNameRaw.(string)) - if err != nil { - return nil, err - } - if role == nil { - return nil, fmt.Errorf("could not find role with name: %s", req.Secret.InternalData["role"]) - } - - /* TODO: think about how to handle this case. - if !ok { role, err := b.Role(req.Storage, roleNameRaw.(string)) if err != nil { return nil, err } if role == nil { - if resp == nil { - resp = &logical.Response{} - } - resp.AddWarning(fmt.Sprintf("Role %q cannot be found. Using default revocation SQL.", roleNameRaw.(string))) - } else { - revocationSQL = role.RevocationStatement + return nil, fmt.Errorf("could not find role with name: %s", req.Secret.InternalData["role"]) } - }*/ - // Grab the read lock - b.Lock() - defer b.Unlock() + f := framework.LeaseExtend(role.DefaultTTL, role.MaxTTL, b.System()) + resp, err := f(req, data) + if err != nil { + return nil, err + } - // Get our connection - db, err := b.getOrCreateDBObj(req.Storage, role.DBName) - if err != nil { - return nil, fmt.Errorf("could not find database with name: %s, got error: %s", role.DBName, err) + // Grab the read lock + b.Lock() + defer b.Unlock() + + // Get our connection + db, err := b.getOrCreateDBObj(req.Storage, role.DBName) + if err != nil { + return nil, fmt.Errorf("could not find connection with name %s, got err: %s", role.DBName, err) + } + + // Make sure we increase the VALID UNTIL endpoint for this user. + if expireTime := resp.Secret.ExpirationTime(); !expireTime.IsZero() { + err := db.RenewUser(role.Statements, username, expireTime) + if err != nil { + return nil, err + } + } + + return resp, nil + } +} + +func (b *databaseBackend) secretCredsRevoke() framework.OperationFunc { + return func(req *logical.Request, data *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) + + var resp *logical.Response + + roleNameRaw, ok := req.Secret.InternalData["role"] + if !ok { + return nil, fmt.Errorf("no role name was provided") + } + + role, err := b.Role(req.Storage, roleNameRaw.(string)) + if err != nil { + return nil, err + } + if role == nil { + return nil, fmt.Errorf("could not find role with name: %s", req.Secret.InternalData["role"]) + } + + /* TODO: think about how to handle this case. + if !ok { + role, err := b.Role(req.Storage, roleNameRaw.(string)) + if err != nil { + return nil, err + } + if role == nil { + if resp == nil { + resp = &logical.Response{} + } + resp.AddWarning(fmt.Sprintf("Role %q cannot be found. Using default revocation SQL.", roleNameRaw.(string))) + } else { + revocationSQL = role.RevocationStatement + } + }*/ + + // Grab the read lock + b.Lock() + defer b.Unlock() + + // Get our connection + db, err := b.getOrCreateDBObj(req.Storage, role.DBName) + if err != nil { + return nil, fmt.Errorf("could not find database with name: %s, got error: %s", role.DBName, err) + } + + err = db.RevokeUser(role.Statements, username) + if err != nil { + return nil, err + } + + return resp, nil } - - err = db.RevokeUser(role.Statements, username) - if err != nil { - return nil, err - } - - return resp, nil } diff --git a/logical/system_view.go b/logical/system_view.go index b69f27090..b6ab14b1f 100644 --- a/logical/system_view.go +++ b/logical/system_view.go @@ -88,11 +88,11 @@ func (d StaticSystemView) ReplicationState() consts.ReplicationState { } func (d StaticSystemView) ResponseWrapData(data map[string]interface{}, ttl time.Duration, jwt bool) (string, error) { - return "", errors.New("ResponseWrapData is not implimented in StaticSystemView") + return "", errors.New("ResponseWrapData is not implemented in StaticSystemView") } func (d StaticSystemView) LookupPlugin(name string) (*pluginutil.PluginRunner, error) { - return nil, errors.New("LookupPlugin is not implimented in StaticSystemView") + return nil, errors.New("LookupPlugin is not implemented in StaticSystemView") } func (d StaticSystemView) MlockDisabled() bool {