Documentation
¶
Overview ¶
Package keysmithbridge lets Keysmith's warden_hook extension drive Warden.
Keysmith defines a small WardenBridge interface of its own so that it never imports Warden. Bridge is the Warden side of that seam: it has the same three methods, so you can hand one straight to the hook:
eng, _ := warden.NewEngine(warden.WithStore(st)) hook := wardenhook.New(keysmithbridge.New(eng))
Neither module imports the other. The match is structural, and the test in this package keeps it honest.
Every method needs a tenant. In Warden an empty tenant in a filter matches every tenant, so the bridge refuses one instead of letting a key event touch rows it does not own. Everything the bridge reads or writes lives in the tenant's root namespace.
The role a key is given must already exist. The bridge does not create roles, because choosing what a role may do is an administrator's decision. Create the role (Keysmith's hook defaults to the slug "api-key") and attach the permissions it should grant. SyncScopesToPermissions only makes sure the permissions named by a key's scopes exist; it never attaches them to a role.
Scope names to permissions ¶
Keysmith writes a scope action first: "read:users" is a child of "read" in Keysmith's own scope tree. Warden names a permission the other way round, because its evaluator matches a check against Resource + ":" + Action and every permission Warden writes itself is named that way. The bridge reads the scope Keysmith's way and names the permission Warden's way:
- "read:users" becomes name "users:read", resource "users", action "read". A check for action "read" on resource type "users" then finds it.
- The split is at the first colon, and the resource is everything after it. So "read:users:profile" becomes name "users:profile:read", resource "users:profile", action "read".
- A scope with no colon, such as "read" or "admin", is skipped. It names an action on every resource, and Warden can only say that with a wildcard, which the bridge never creates. There is no error, and the rest of the list still syncs. If a key should hold that much, put a wildcard on its role yourself.
- A scope with an empty side (":users", "read:") or a "*" anywhere in it is refused with an error. The bridge never creates a wildcard permission, because one grant would then cover every matching check. If a key should hold a broad grant, give its role a wildcard on purpose.
An existing permission with the same name is left exactly as it is.
Caches and hooks ¶
Each assignment write drops the key's cached decisions through Engine.InvalidateSubject, so a new or revoked key takes effect at once. The bridge also emits the same plugin events the REST API does (role assigned and unassigned, permission created, and the audit event), so audit plugins see keysmith-driven changes.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Bridge ¶
type Bridge struct {
// contains filtered or unexported fields
}
Bridge implements Keysmith's warden_hook WardenBridge on top of a Warden engine. Build one with New. It holds no state beyond the engine, so one value is safe to share across goroutines.
func (*Bridge) AssignRoleToAPIKey ¶
AssignRoleToAPIKey gives the API key keyID the role with slug roleSlug, in the tenant's root namespace. The role must exist: a missing one is an error that names the slug. Assigning a role the key already holds is a no-op. A role with a member cap refuses a new key once it is full, as it would for any other subject.
func (*Bridge) SyncScopesToPermissions ¶
func (b *Bridge) SyncScopesToPermissions(ctx context.Context, tenantID string, scopes []string) error
SyncScopesToPermissions makes sure a permission exists for every scope, in the tenant's root namespace. Keysmith writes a scope action first, so "read:users" becomes the permission "users:read" with resource "users" and action "read". A scope with no colon, such as "read", is skipped without an error, because it covers every resource and only a wildcard could say that. See the package comment for the full mapping. Permissions that already exist are left alone, and a concurrent create of the same permission counts as success.
Every scope is tried even when an earlier one fails. The returned error joins one error per scope that could not be synced, each naming its scope.
func (*Bridge) UnassignRoleFromAPIKey ¶
UnassignRoleFromAPIKey removes every assignment the API key keyID holds in the tenant, whatever the role, namespace or resource scope. A key with no assignments is a no-op.