Support control groups for authorizing paths
Summary
This feature would implement second-party authorization for protected resources. A control-group restriction placed on a path would require a user to obtain prior authorization before the action would succeed.
Control groups utilize the existing response-wrapping workflow because of the asynchronous character, and usage would follow this general pattern (via API endpoints):
- A path is assigned a control-group policy
- A user requesting the protected path receives a wrapped response with a
wrapping_accessorandwrapping_token. - Using the
wrapping_accessor, a control group member can review and authorize the request. - Access to the wrapped response via
wrapping_tokenwill now succeed once approved.
A second phase might add a notification workflow, but this is not essential:
- A user requests access to the protected resource
- Members of the control group receive a notification of the request (on logging in to OpenBao web UI?)
- The original requester receives notification of the control-group approval when it occurs
Problem Statement
An organization may want to review access to certain paths (eg, pki
issuance) on a case-by-case basis, and/or requires a "second set
of eyes" to approve access to a path. The reviewers are indicated by
specifying a control group, whose members can grant access by using
an authorize endpoint. However, the control group members should not be able to
grant their own personal access.
User-facing Description
Two users are implicated in the control-group workflow: a requester and an approver. The requester will attempt to access a protected path, but will receive a wrapped response. The approver can access metadata about the request (entity, path, etc) and approve it. Once approved, the response can be unwrapped.
Technical Description
The control group implementation will consist of the following.
- A control-group access policy:
- Denies data access by default
- Specifies the control group, number of authorizations, ttl
- Allows authorizing action to control group members
- Allows data access to authorized entity
- An authorizing endpoint (in
sys/control-group):- Using wrapping accessor
- Self authorization not permitted
- Authorization storage
- Request, authorization details, and response will be stored in the metadata for the wrapping token.
Reusing the response wrapping framework should be familiar, but the
developer will need to add new control-group paths to the system
backend. Additional logic will be needed in the wrapping/unwrap
handlers, as well as policy application.
API Details
Policy Extension
Control groups could be implemented as a new object in the ACL policy for a path. Multiple named "factors" can be specified, eg:
path "pki/issue/*" {
capabilities = ["read", "update"]
control-group = {
factor "pki-approvers" {
controlled_capabilities = ["update"]
identity {
# Control groups that can authorize access
group_names = ["pki-approvers", "security-team"]
approvals = 1
# Max duration for which authorization is valid
ttl = "5d"
# Allow self-authorization when true
self_authorization = false
}
}
}
}
Using a path having the policy would look like the following:
# Normal request by some user (blocked by control group by default)
POST pki/issue/web-server
{
"common_name": "web.example.com",
"ttl": "24h"
}
# Which returns a wrapped response:
{
"wrap_info": {
"token": "TOKEN",
"accessor": "ACCESSOR"
}
}
An authorizer can view and approve the request:
# Authorizer inspecting the request
POST auth/token/lookup-accessor
{
"accessor": "ACCESSOR"
}
# Which returns the wrapping token entry with request and applicable control-group policy in the metadata:
{
"request_id": "...",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"accessor": "ACCESSOR",
"creation_time": 1705412345,
"creation_ttl": 600,
"display_name": "token",
"entity_id": "",
"expire_time": "2024-01-16T15:00:00Z",
"explicit_max_ttl": 0,
"id": "",
"meta": {
"control_group": "{\"ttl\":3600000000000,\"factors\":[{\"name\":\"require-secops-approval\",\"controlled_capabilities\":[\"read\"],\"identity\":{\"group_names\":[\"secops\",\"security-admins\"],\"approvals\":2},\"authorizations\":[]}]}",
"request": "{\"id\":\"f4d37c6a-1b2e-9c3d-8a4f-5b6e7c8d9e0f\",\"operation\":\"create\",\"path\":\"pki/issue/web-server\",\"map\":{\"version\":\"0\"},\"client_token_accessor\":\"\",\"display_name\":\"token-developer-ldap\",\"mount_point\":pki/\",\"mount_type\":\"pki\",\"entity_id\":\"abc12345-6789-def0-1234-567890abcdef\"}"
},
"num_uses": 1,
"orphan": true,
"path": "auth/token/create",
"policies": [
"response-wrapping"
],
"renewable": false,
"ttl": 598,
"type": "service"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
# Authorizer approves the request
POST auth/token/approve-accessor
{
"accessor": "ACCESSOR"
}
# Which returns the updated token entry (approver is added to control_group/factors/authorizations):
{
"request_id": "...",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"accessor": "ACCESSOR",
"creation_time": 1705412345,
"creation_ttl": 600,
"display_name": "token",
"entity_id": "",
"expire_time": "2024-01-16T15:00:00Z",
"explicit_max_ttl": 0,
"id": "",
"meta": {
"control_group": "{\"ttl\":3600000000000,\"factors\":[{\"name\":\"require-secops-approval\",\"controlled_capabilities\":[\"read\"],\"identity\":{\"group_names\":[\"secops\",\"security-admins\"],\"approvals\":2},\"authorizations\":[{\"timestamp\": \"20250115T23:10:09\",\"approver\":\"john.doe@example.com\"}]}]}",
"request": "{\"id\":\"f4d37c6a-1b2e-9c3d-8a4f-5b6e7c8d9e0f\",\"operation\":\"create\",\"path\":\"pki/issue/web-server\",\"map\":{\"version\":\"0\"},\"client_token_accessor\":\"\",\"display_name\":\"token-developer-ldap\",\"mount_point\":\"pki/\",\"mount_type\":\"pki\",\"entity_id\":\"abc12345-6789-def0-1234-567890abcdef\"}"
},
"num_uses": 1,
"orphan": true,
"path": "auth/token/create",
"policies": [
"response-wrapping"
],
"renewable": false,
"ttl": 598,
"type": "service"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
Requesting user can now retrieve the response if the nomber of authorizations is equal to the approvers attribute:
# Unwrap request
POST /sys/wrapping/unwrap
{
"token": "TOKEN"
}
# Which now returns the original response data:
{
"request_id": "12345678-abcd-efgh-ijkl-123456789abc",
"lease_id": "",
"renewable": false,
"lease_duration": 0,
"data": {
"ca_chain": [
"-----BEGIN CERTIFICATE-----\nMIIBuDCCAV6g...\n-----END CERTIFICATE-----"
],
"certificate": "-----BEGIN CERTIFICATE-----\nMIIDATCCAemgAwIBAgIUK8ZQgT...\n-----END CERTIFICATE-----",
"expiration": 1701504000,
"issuing_ca": "-----BEGIN CERTIFICATE-----\nMIIBuDCCAV6gAwIBAgIUK8ZQ...\n-----END CERTIFICATE-----",
"not_before": 1701500400,
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0B...\n-----END PRIVATE KEY-----",
"private_key_type": "rsa",
"serial_number": "2b:c6:50:81:3f:e4:2a:1d:f3:4c:5e:6f:8a:9b:0c:1d:2e:3f:4a:5b"
},
"warnings": null,
"auth": null
}
Rationale and Alternatives
The control group concept has a number of benefits:
- The administrator can delegate authorization responsibility to a group, any member of which can grant access
- By inhibiting self-approval, the control group policy can ensure "second set of eyes"
- The wrapped_response workflow provides some additional security
A workflow for requesting/granting in the UI could make admistration easier.
As an alternative: the same second-person security arrangement can be achieved with a basic policy granting access to a path. However, this requires policy-granting permission for the "control group" member, and extra work to subsequently remove the policy.
Downsides
Involving a second actor in securing a path creates some complexity and impedence for users.
Security Implications
Using response wrapping and requiring authorization should increase security. Only the original token holder (the requester) will be able to obtain the original response.
User/Developer Experience
The api is following Vault Enterprise, so it should be familiar for users who are already using the equivalent feature there. For new users, we will need some documentation describing usage and value. For users who do not need, it can be ignored.
Unresolved Questions
Relying on sys/wrapping and the auth/token endpoints might conflict
with namespace partitioning.