Profiles for Cross-Plugin Communication
OpenBao and upstream lack server-side cross-pluign communication.
As recently seen with an OIDC feature, this shortcoming often needs to be worked around on the client side, potentially exposing sensitive information.
There's usually two paths discussed for cross-plugin communication:
- Requests bound under the context of the original user token.
- Designing some other authorization system or an internal API based design.
Both have some trade-offs: the security model of the first makes more intuitive sense to reason about the complexity of its implementation. There's no additional design necessary for restricting access to other plugins. However, it has some undesirable side-effects: it prevents a trusted (server-bound) channel between two plugins. As we later decided to revisit the OIDC changes, we opted to allow auth plugins to write to an internal data field that becomes accessible on the entity. This prevented the need for the client (an partially trusted party) from needing to hold the sensitive OIDC tokens but allowed any plugin to be able to access its value. However, plugins would still need to be updated to read this field; thus it has limited utility.
While the need for a trusted channel still exists, I'd like to talk about a more flexible idea for improving the developer experience of OpenBao, that could potentially address ACL-bound requests. A profile system.
One of the major learnings of ACME is that simplicity of automation and integration greatly improve the developer experience of a protocol. OpenBao, from a developer's perspective, is rather hard to understand: there's all sorts of auth and secrets engines that one can use and it is down to an organization's policies and technical decisions which ones they'll use. Unless they're interacting with a KMS, however, many applications' usage of secrets is transactional: single requests to many engines to collect all the secrets they need to operate.
Let's introduce an application profile: a way for operators to define a single endpoint which collects a series of requests into a single place on behalf of a caller. Perhaps this includes four major components: input
from the request, an optional auth
step (to transparently handle authentication on behalf of the caller), one or more request
blocks to collect the necessary secrets, and a output
block to format the response.
This looks a lot like the ACL policy system with some extra directives:
input {
authenticated = true
data = {
"common_name": "string"
}
}
request "cert" {
path = "/v1/pki/issue/example"
operation = "read"
err = fatal
data = {
"common_name": input.data.common_name
}
}
request "dbpass" {
path = "/v1/database/creds/example"
operation = "read"
err = fatal
}
output {
data = {
"listener": {
"certificate": request.cert.data.certificate,
"private_key": request.cert.data.private_key,
"issuer": request.cert.data.issuing_ca
},
"database": {
"username": request.dbpass.data.username,
"password": request.dbpass.data.password
},
"leases": [
request.cert.lease_id,
request.dbpass.lease_id
]
}
}
Importantly, each step in this example is authenticated using the required input token, which must have access to all requested resources (/v1/pki/issue/example
and /v1/database/creds/example
) but also the ability to execute this profile as well (perhaps via /v1/sys/profiles/execute/:name
). Administrators could modify profiles (perhaps via /v1/sys/profiles/modify/:name
) and pre-configure them for applications. With namespaces, this could help the organization of profiles, limiting the profile to paths under the given namespace.
We could perhaps get something more similar to ACME, with JWT-based authentication and a signature over the input fields (potentially including a server controlled and validated nonce as well). In this way, unless the profile author specifies it, the requester would not see the underlying token at all, though obviously would be able to acquire one directly from the JWT auth method with their token.
Additionally, a capability such as direct-deny
could be added to the ACL system, allowing an operator to deny direct usage of OpenBao (while allowing the profile system to continue to operate). This would be in contract to the existing deny
which would continue to deny all usage.
Either with more complex templating instructions than the profile system or by creative block names, we could support operations such as chaining queries from the result of LIST
operations (thus manually building ListWithInfo
responses) or iterated queries. With cross-request transactions, we could prohibit certain write calls from succeeding if all requests in the profile do not succeed. Or, maybe this language just ends up looking like OpenTofu...
Regardless, there are a lot of ideas left to explore before this feature becomes a reality.
A profile system is an important step in helping to make OpenBao more usable and approachable to application developers. And if standardized, it encourages broader compatibility across the entire secrets management landscape -- beyond OpenBao and HashiCorp Vault.
Have requirements for the new profile system or interested in helping to design and implement it? Let us know!