§ How-to · Share and protect
Give people roles
Say which readers of a sealed state may change it and which may only read and plan, pin the roles in the state so that nobody promotes themselves by editing a copy of the readers file, promote a member, take a member off, and give a CI job the role its work needs.
On this page
A sealed state says who may read it. Everyone on that list may also change it, until the list says otherwise. This page gives each member a role, so that some may change the state, some may only read it and plan, and one or two may change who reads and who writes. It then pins the roles in the state, promotes a member, takes one off, and gives a CI job the role its work needs.
| Role | May |
|---|---|
reader |
Open the state, read it, plan, ask why, and store a plan with plan --store. Read a secret that the state keeps. |
writer |
What a reader may, and change the state: apply, destroy, import, state mv, state rm, discover adopt. Add and edit a policy. Set a secret. Approve a plan. |
admin |
What a writer may, and change who reads the state, change the roles, remove a policy, remove a secret, collect garbage and take the seal off. Apply a plan without the approvals a policy asks for. |
Note
Every command below runs against tools/fakes3, the in-memory S3 server in pravo's source, on port 9000. The three people, ada, dan and eve, are three identity directories on one machine, and PRAVO_CONFIG_DIR picks one. Each command is written as the person who runs it; a command without a prefix is ada's.
1. Start from a sealed state
The root is team/, with one resource, and its state is sealed to ada, dan and eve, as Seal the state does it. Ada has applied it once.
package team
import (
"pravo.dev/pravo"
rnd "pravo.dev/providers/hashicorp/random"
)
$settings: pravo.#Settings & {
state: pravo.#S3State & {
backend: "s3"
bucket: "team-state"
prefix: "prod"
region: "eu-west-1"
endpoint: "http://127.0.0.1:9000"
path_style: true
}
}
$random: rnd.#Provider & {$version: "~> 3.6"}
pet: $random.#Resource.random_pet & {length: 2}
ada ssh-ed25519 <ssh-key> x25519:<key>
dan ssh-ed25519 <ssh-key> x25519:<key>
eve ssh-ed25519 <ssh-key> x25519:<key>
Sealed on · format 3 · 3 readers (ada dan eve) · recovery none
Roles off
Roles off: no line has a role, so each of the three may do everything, which is how a sealed state starts.
2. Give each line a role
The role is a fifth word on the line of the readers file: role:admin, role:writer or role:reader. Ada is the admin, dan writes, eve reads. As soon as one line has a role, a line without one is a reader, so give every line one.
ada ssh-ed25519 <ssh-key> x25519:<key> role:admin
dan ssh-ed25519 <ssh-key> x25519:<key> role:writer
eve ssh-ed25519 <ssh-key> x25519:<key> role:reader
Roles on · 1 admin · 1 writer · 1 reader · from the readers file, not pinned (pravo protect roles) · you are an admin
Records 0 bound to a transaction of a writer · 1 from before roles
The roles are in force from this line on, for every pravo that reads this checkout. No rewrap is needed, because a role is not a key: the data keys of the state stay as they are. The Records line is new as well. With roles, the signature of a writer's run names each record the run wrote, and protect status checks every record against the transactions. The one record of this state is from before the roles, which is in order; a record that no writer's run accounts for is reported as untrusted, and refused with exit code 7 before a plan reads it. The file is a request, though, and the next step makes it the state's.
3. Pin the roles in the state
A reader who edits their own line in their own checkout is an admin there, for as long as that checkout lasts. pravo protect roles closes the gap. An admin runs it: it writes the roles to the state as a signed version, and adds one line to the readers file, the digest of that first version. From then on the roles in the state decide, a new version has to be signed by an admin of the version before it, and a state whose chain was replaced or removed is refused with exit code 7.
ada: admin
dan: writer
eve: reader
Stored the roles as version 1.
Added roles-genesis <digest> to .pravo/access/readers.txt. Commit it: until it is in the repository the roles of the file decide.
roles-genesis <digest>
Roles on · 1 admin · 1 writer · 1 reader · pinned, version 1 in the state · you are an admin
Records 0 bound to a transaction of a writer · 1 from before roles
Commit the line with the file.
git add .pravo/access/readers.txt
git commit -m "Give the members of the production state their roles"
git push
4. See what each role may do
Eve, a reader, adds a resource and tries to apply it.
package team
second: $random.#Resource.random_pet & {length: 2, prefix: "eve"}
Error: your identity is a reader in .pravo/access/readers.txt, and a writer is needed to change the state.
exit=5
A reader plans, and the plan stays in memory: the records it refreshed are not written to the bucket. The one thing a reader writes is a stored plan, and a writer can apply it.
Stored the plan as plan-<id>; unless it is applied it is removed after 7 days. To apply exactly these actions, run:
plan-<id> pending eve just now +1 ~0 -0 expires in 7 days
Dan, a writer, applies it by its id. The apply checks the plan as it checks any stored plan, and the transaction is signed by dan, not by eve.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed. [txn <id>]
signed by dan (verified)
signed by ada (verified)
What dan may not do is change who reads the state, or the roles:
Error: your identity is a writer in .pravo/access/readers.txt, and an admin is needed to change who reads the state.
exit=5
Error: your identity is a writer in the roles in the state, and an admin is needed to store the roles.
exit=5
pravo serve shows the same in its State section: each member with their role, whether the roles are pinned, and whether every record was written by someone who may change the state. Explore an estate in a browser has the picture.
5. Promote a member
Eve becomes a writer. The change is an edit of her line, and the command again, run by an admin.
eve: reader -> writer
Stored the roles as version 2.
Roles on · 1 admin · 2 writer · 0 reader · pinned, version 2 in the state · you are an admin
Commit the file. A demotion is the same edit the other way. What a demoted writer wrote earlier stays valid as far as the admin vouched for it, which the next step shows.
6. Take a member off
Dan leaves. His line goes from the file, the data keys are made again so that nothing new is sealed to him, and the roles record him as a former key and vouch for what he wrote. The first two are what Remove a reader does; the third is new.
Readers in .pravo/access/readers.txt: ada eve (dan removed)
New data keys for 1 reader group. Records are sealed again as they are next written.
dan: removed, now a former key
dan: vouched for 1 transaction (records they wrote stay valid)
Stored the roles as version 3.
The transaction that dan ran in step 4 stays verified, because the version that ada signed vouches for it: the record second that he wrote is still trusted, and a plan still reads it. What dan cannot do is add anything. A run signed with his key is refused by every honest pravo, with exit code 7, and he cannot open the state any more:
Roles on · 1 admin · 1 writer · 0 reader · pinned, version 3 in the state · you are an admin
Records 0 bound to a transaction of a writer · 1 vouched for by an admin (the key left or was demoted) · 1 from before roles
Error: you are not a reader of this state (identity dan is not a reader).
Readers are listed in .pravo/access/readers.txt. Ask one of them to add yours.
exit=5
A copy of the bucket made before this step still opens with dan's key, and the records that were sealed in the old group do until they are written again. pravo protect rewrap --all seals them again now, pravo protect gc drops the old group afterwards, and the credentials of the bucket are taken from dan where they are kept: Remove a reader has the three.
7. Give a CI job a role
A job holds an identity of its own, made with --ci as Let a CI job read does. Give it the smallest role that its work needs: a job that plans and stores is a reader, and a job that applies is a writer. No job is an admin, because an admin may change who reads the state and may apply without the approvals a policy asks for.
Readers in .pravo/access/readers.txt: ada eve deploy-bot
Wrapped the data keys for deploy-bot (2 reader groups, 2 new wrapped keys)
deploy-bot: writer
Stored the roles as version 4.
Store the file as the secret PRAVO_IDENTITY of the apply job, delete it, and commit the readers file. A job that only plans gets an identity of its own with role:reader, and the credentials of the bucket to match.
8. Give the bucket the same rules
The roles are checked by every pravo that reads the readers file and the roles in the state. Someone who holds the credentials of the bucket, the data key and a pravo of their own can still write an object: the next honest run finds the record, refuses it with exit code 7, and does not stop it from having been written. What stops it is the bucket. Give its credentials by role:
| Role | Credentials on the bucket |
|---|---|
reader |
Get and list on the whole prefix. Put on plans/. |
writer |
Get, put, delete and list on the prefix, except meta.json, policy/, roles/ and secret/, where a writer has get and list and may put a new object under policy/ and under secret/. |
admin |
Everything on the prefix, meta.json, policy/, roles/ and secret/ included. |
An object lock on roles/ keeps the newest versions of the chain from being deleted, which the pin cannot see.
Recap
- A role is a fifth word on a line of the readers file. The file is a request;
pravo protect roles, run by an admin, makes it the state's and pins it with one more line. - A reader reads, plans and stores a plan. A writer changes the state and approves plans. An admin changes who reads, changes the roles, removes policies, and needs no approvals.
- A promotion or a demotion is an edit of the file and the command again.
- A member who leaves is removed from the file;
rewrapstops sealing to them,protect rolesrecords them as a former key and vouches for what they wrote, andrewrap --allwithgctakes their old key out of the bucket. - A job gets the smallest role its work needs, and never admin. The credentials of the bucket follow the roles.
What next
- Approve a plan uses the roles: a policy makes a plan wait for the approval of a second writer.
pravo protect roleslists what each role may do, what the command writes, what is refused and with which message.- Signatures and roles explains what a role rests on and what it does not do.
- Run pravo in a pipeline gives the plan job and the apply job their identities.