Add create-only action for Secrets Manager permissions
What does this MR do and why?
Adds a create action to Secrets Manager permissions, sitting between read and write. Today write fans out to both the OpenBao create and update capabilities, so there is no way to let someone add new secrets without also letting them change existing ones.
Deviation from the issue's implementation plan
The plan says create should map to only the create capability on both the data and metadata paths. That is not achievable while the user performs the metadata writes.
A secret's data and metadata paths come into existence together in KV v2, so whichever one is written second counts as an update as far as OpenBao is concerned. Creating a secret needs both writes, so in either order one of them requires update — reordering does not help, and moving only the second metadata write is not enough either.
Instead, the create flow's metadata writes now run on the application client, leaving the value write as the user-authorized step. create maps to create on the data path and nothing on the metadata path, so OpenBao refuses updates on its own and no Rails-side check is needed.
A tradeoff worth naming explicitly: OpenBao no longer independently verifies metadata writes during creation, for every grant type rather than just create-only. Creation is authorized by create on the data path plus the mutation's own permission check. The metadata writes only ever run downstream of the value write, which OpenBao has already authorized for that user — so the ordering in start_secret_creation! is load-bearing.
Frontend scope
The issue puts the "Create" checkbox in the permissions modal out of scope, and this MR does not add it. With this MR merged, create can still only be granted by calling the permissions mutation directly.
The permissions table renders whatever actions the backend returns, so without the label a create grant shows as undefined.
Note: The Create checkbox will need to account for union semantics. From my understanding, ticking Create for a user who is also a Maintainer will not produce a create-only user, because the seeded role grant for maintainers still supplies update. The UI has to either adjust the role grant or warn that a role default is overriding the narrower grant. Related: an Owner who removes a seeded create-only grant currently cannot restore it, since the modal has no Create checkbox.
Acceptance criteria
All met, with two notes:
- The mapping AC is met as described above, not as originally planned.
- The principal-type matrix is not varied for the new action. Principal type determines the policy name, never the capabilities, so a per-principal matrix would assert that code which ignores the principal ignores the principal. Happy to add it if reviewers disagree.
References
- Issue: #623435 (closed)
- Unblocks the Developer default role: #623438 (closed)
- The
readalias rename referenced in the corrected comments: #623488
Two follow-ups worth filing, neither blocking:
- A "Create" checkbox in the grant modal, so Owners can grant this themselves.
- Gating the secret Edit button on
updateSecrets, which already returns the correct value. This also fixes an existing gap where read-only users see an Edit button and hit an error on submit.
Screenshots or screen recordings
The only visible change is the Secrets Manager permissions table under Settings > General > Visibility, project features, permissions, which can now render a Create scope. Before this MR a create-only grant could not exist.
| Before | After |
|---|---|
No Create scope |
Scope column reads Read metadata, Create |
After:
With create manually set through console/graphql, Create label is visible in the permissions table:
How to set up and validate locally
-
Enable Secrets Manager on a project and create a secret as an Owner. Add your test member as a Developer or Reporter, not a Maintainer — master seeds a default Maintainer grant of create + update, and effective capabilities are the union of all applicable principal policies, so a Maintainer would keep
updateno matter what you grant them as a user. -
Grant a member create-only access via GraphiQL:
mutation { projectSecretsPermissionUpdate(input: { projectPath: "your-group/your-project" principal: { id: <USER_ID>, type: USER } actions: [READ, CREATE] }) { secretsPermission { actions } errors } }It should read back as
["READ", "CREATE"]. Sending[READ, CREATE, WRITE]should read back as["READ", "WRITE"]. -
In Settings > General > Visibility, project features, permissions, the member's Scope column reads
Read metadata, Create. -
As that member, create a new secret. This should succeed — it only works because the metadata writes run on the application client.
-
As that member, edit an existing secret and change only the description. Expect the denial to come from OpenBao, surfacing as
The resource that you are attempting to access does not exist or you don't have permission to perform this action. -
Repeat for the value and for
environment. Same denial. -
Update the test user's permission to include write, and observe that the updates work after that.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
