Dev.to Security πŸ” Cybersecurity πŸ‘ 0 πŸ“– 12 min read

Invite by email, grant one environment, shut a leaver out in 60 seconds: roles and teams in dotvault

This is part 9 of the series "dotvault, Start to Finish: One Command from Your Laptop to CI, Your Team, and Your AI Agents." dotvault will be released free of charge when this series ends. In the demo, Remove from org d

Invite by email, grant one environment, shut a leaver out in 60 seconds: roles and teams in dotvault

This is part 9 of the series "dotvault, Start to Finish: One Command from Your Laptop to CI, Your Team, and Your AI Agents." dotvault will be released free of charge when this series ends.

In the demo, Remove from org did four things at once. It revoked the service token the person had created, deleted their project grants, took them out of their team, and stopped their pulls. Their token was refused about five seconds later, and their own login about fifteen seconds later.

That is the difference from handing .env files around in chat. Once a value has been pasted to someone, there is no way to take it back, and changing a shared password does nothing to the API keys and database URLs sitting in their .env. With dotvault, cutting someone off is one action in the console. The only work left is replacing the values they could read, because whatever they already pulled is still on their laptop.

This part walks through the whole team setup in a demo company, Acme Shop: converting a Personal org to a Company org, inviting people by email, granting roles per project and per environment, and then the order of steps when someone leaves.

This is part 9 of 12.

The short version

  • Convert to a Company org, then invite. Convert under User Settings β†’ Organization, then use + Invite member in Members with an email, a role and an optional first grant (project, role, environments).
  • A project has four roles: owner, admin, writer and reader. Writer and reader can be limited to specific environments, so a person granted development only cannot pull production. Grant a team, and everyone in it gets the same access.
  • When someone leaves: note their access, check the audit log, transfer ownership, Remove from org, confirm, replace values. Removing them also revokes their tokens, grants and team memberships. A project owner cannot be removed until ownership moves.
  • In the demo, both their token and their login were refused within fifteen seconds. The last step is replacing the values they could read with dotvault set.

Before you read on

This builds on part 2 (pull) and part 7 (service tokens). Part 10 covers reading the audit log.

1. Convert your Personal org to a Company org

Your first login to dotvault creates an org that belongs to you alone, a Personal org. You cannot invite anyone into it. To work as a team, convert it to a Company org.

Three demo users appear in this part:

Demo user Org role Part they play
dev-b org_admin Runs Acme Shop: invites people, grants access, removes people
dev-c member An employee who leaves in the second half
dev-d guest An outside collaborator

The person who will run the org (dev-b) converts it under User Settings β†’ Organization. Enter the company name and press Convert to Company, and the console asks for confirmation.

The Organization panel of User Settings in the console, cropped. Under "Convert your current Personal organization to a Company organization. Projects, keys and members are carried over as-is." is the Organization name (optional) field with Acme Shop in it. An orange box below reads "Convert organization β€œPersonal” to a Company organization? Projects, keys and members are carried over as-is. This cannot be undone.", followed by the Yes, convert and Cancel buttons.

As it says, this cannot be undone; there is no way back to Personal. Projects and the values in them carry over. After Yes, convert, the console shows "Converted to a Company organization." and the sidebar gains My Organizations and Teams. The conversion made dev-b the owner of acme-shop, the project created before it.

2. Three org roles and four project roles

A Company org has two kinds of roles. The org role decides what someone can do in the org. The project role decides whether they can read or write the values of a given project. The Members page lists both:

Role What the console says
owner Exactly one per project. Full access + can appoint/remove admins + transfer ownership.
admin Manages this project's permissions (grant/revoke writer/reader) + read/write on all envs. Cannot appoint/remove owner or admin (owner only).
writer Read + write (equivalent to a token's member role). Can be scoped to specific envs.
reader Read only, i.e. pull (equivalent to a token's viewer role). Can be scoped to specific envs.
org_admin Can always manage the org (members, owner transfers, project settings). Cannot read/write values β€” grant that to yourself if needed (audited).
member Org role: a regular member who can see the workspace (project list, member list, Teams). Data access depends on project role.
guest Org role: sees nothing except projects explicitly granted (list views are hidden too). For external collaborators.

The first four are project roles; the last three are org roles. Note that org_admin manages the org but cannot read values by itself. To read them, an org_admin grants themselves a project role, and that grant is recorded in the audit log.

3. Invite by email and grant development only

dev-b invites dev-c with + Invite member in Members: dev-c's email address, Org role Member. Checking Add initial access (optional) lets you set the first grant in the same step. In the demo: Target acme-shop, Role writer, and Selected with development only.

The Members page, cropped. The role descriptions for owner, admin, writer, reader, org_admin, member and guest are at the top. Below, the Invite a member panel: dev-c's email address, Org role Member, and the Send invitation and Cancel buttons. Add initial access (optional) is checked, Target is acme-shop, Role is writer, and Selected has only development checked (staging, production and qa are not).

After Send invitation, dev-c appears in the list as Invited, with acme-shop: writer (development) in the Access column: the access the invitation will grant.

Press Resend on that row and a fresh invite link appears, starting with https://app.dotvault.io/invite/. Copy it and send it to the person yourself, in chat or however your team talks.

The Members list, cropped. Three rows: dev-b (org_admin, acme-batch: owner, acme-shop: owner), dev-d (guest, acme-shop: reader (development)) and dev-c (member, Invited, acme-shop: writer (development), with Resend and Revoke buttons). A green box below reads "A new invite link has been issued. Automatic email resend is not available yet, so please share this link directly." with a link starting https://app.dotvault.io/invite/ (the rest blacked out) and Copy.

The end of the link is different for every invitation, so it is blacked out here. Send it to the invitee only.

4. The invitee joins and can pull only what was granted

In the demo, dev-c logged in at app.dotvault.io before opening the link. At login, a banner across the top already said "You have 1 pending organization invitation(s). View β†’".

The link opens "Invitation to Acme Shop" with the role (Member) and a Join button. After Join, the page says "You joined Acme Shop", and Go to console opens Acme Shop. If the person will use the CLI, the same page links to "Using the CLI (dotvault pull)? Set a password first β†’", where they set the password for dotvault login.

dev-c logs in from the CLI and pulls.

Terminal, recomposed. Under "Right after joining β€” writer on acme-shop, development only", dotvault whoami prints dev-c's email address and two orgs: Acme Shop (company, member) and Personal (personal, org_admin). dotvault pull acme-shop -E development wrote 2 keys. dotvault pull acme-shop -E production prints "βœ– PERMISSION_DENIED: You do not have permission for project 'acme-shop' env 'production' …" and echo $? prints 4. Under "After dev-c was added to Team backend β€” writer on acme-shop, all environments", the pulls of -E production and -E qa both wrote 2 keys.

dotvault whoami lists the newly joined Acme Shop (company, member) and dev-c's own Personal org. The pull of development, which was granted, works. The pull of production, which was not, stops with PERMISSION_DENIED and exit code 4. The lower half of the figure is from after dev-c joins a team in the next section.

5. Grant a team, and everyone in it gets the same access

Besides granting people one by one, you can grant a team. Create the team under Teams (in the demo, backend) and add org members to it by email with + Add member on the team page. On the free plan, an org can have one team. Creating a second one shows "Team limit reached for your plan (free): 1/1".

Grant the team on the project's Access page: choose Team under Grant to, pick a role and environments, and press Grant. With "All environments (including ones added later)", the grant also covers environments you add to the project later.

The Access page of acme-shop, cropped. At the top, the Access heading and "Members and teams granted directly to acme-shop." The Grant access panel has Grant to (Member or Team), Member, Role (writer β€” view and edit) and Environments (All environments (including ones added later) or Only selected environments). The list has four rows: Member dev-b, owner, All environments, "Owner (change it via "Transfer ownership" on the member page)"; Team backend, writer, All environments; Member dev-c, writer, development; Member dev-d, reader, development. Below the list: "If someone holds both a personal and a team grant with different roles, only the stronger role applies, with that grant's environment limits. With the same role, their environments are combined (a grant for all environments gives access to all)."

The note under the list explains what happens when someone has both a personal grant and a team grant. With different roles, the stronger role applies, along with that grant's environments. With the same role, the environments add up. dev-c has writer on development personally and writer on all environments through backend, so dev-c can read and write every environment. That is why the production and qa pulls in the lower half of the previous figure work.

6. Invite outside collaborators as guests

For someone outside the company, choose Guest as the Org role. In the demo, dev-d is a guest with reader on acme-shop, development only.

A guest sees only the projects granted to them. The sidebar has no Members, Teams, Service Tokens or Audit Log.

The Projects page as the guest dev-d. The sidebar shows Acme Shop under ORGANIZATION with Guest below it, and only Dashboard, Projects, Global Keys, Help and User Settings. The project list has one row, acme-shop, with the environments development, staging, production and qa, and 1–1 of 1 at the top right.

Acme Shop also has acme-batch, but dev-d's Projects page shows only acme-shop. Opening the Members page by URL shows "Guests cannot access this resource". The Keys page of acme-shop says "You have read-only access to this project. Editing actions are hidden (revealing a value is audit-logged)." and shows no editing buttons.

The CLI agrees. dev-d's dotvault project list shows only acme-shop. The development pull works, while the production pull and a write with dotvault set both stop with PERMISSION_DENIED (exit code 4).

7. Six steps when someone leaves

Now dev-c is leaving. The order:

Step What to do Why
1 Note which projects and environments they could read, on their member page Removing them deletes their grants and team memberships, so look first
2 Check what they did in the audit log Find the tokens they created and any access they changed
3 Transfer any project they own An owner cannot be removed until ownership moves
4 Press Remove from org Their pulls stop, and their tokens, grants and team memberships go
5 Confirm in the lists and the audit log Each of those changes can be checked afterwards
6 Replace the values they could read Whatever they already pulled stays on their laptop

Before this, dev-c created a service token named dev-c-laptop, the kind a CI job would use. Members can create tokens too, but only for projects and environments they have access to, with an expiry from 1 day to 1 year (90 days by default).

Step 1: Note what they could read

Press Details on dev-c's row in Members.

dev-c's member page, cropped. Projects: acme-shop, writer, ENV SCOPE Selected with only development checked (staging, production and qa are not), and a Remove button. TEAMS (READ-ONLY β€” EDIT FROM THE TEAMS SCREEN): backend with "acme-shop: writer (all envs)". ACCOUNT: a Member selector and a Remove from org button.

Projects shows the personal grants; TEAMS shows the access that comes through teams. Personally, dev-c had development only, but through backend dev-c could read every environment of acme-shop. Those are the values to replace in step 6. This page disappears with the removal, so note it first.

Step 2: Check what they did in the audit log

In Audit Log, put dev-c's email address in Actor (email / sub) and press Apply.

The audit log, cropped. Actor (email / sub) is dev-c's email address. Seven rows, from the top: service_token.created (Actor dev-c's email address), rbac.invitation.accepted (Actor 64782458-…, Change "+ accepted_by_sub: 64782458…" and "+ org_role: member"), rbac.invitation.accepted, service_token.created, rbac.invitation.accepted, service_token.created, service_token.created. Each Target is service_token/ followed by a Token ID, or invitation/ followed by an invitation ID.

You get the tokens dev-c created (service_token.created) and the invitations dev-c accepted (rbac.invitation.accepted). dev-c was invited more than once during the demo, hence three acceptance rows. The token rows also include three created the day before, already revoked.

The Actor of rbac.invitation.accepted is dev-c's user ID (64782458-…), not the email. Some actions record the actor by email and others by user ID, but filtering by email returns both.

If the person leaving was an admin, the audit log also shows who they added to teams and which projects they created or deleted. Here is the same filter for the admin, dev-b:

The audit log, cropped. Actor (email / sub) is dev-b's email address. From the top: project.deleted, project.archived and project.created (Target project/acme-tmp), team.updated (team/backend, Change "name: backend-qa β†’ backend"), team.updated (team/backend-qa, "name: backend β†’ backend-qa"), team.member.added (team/backend, "+" followed by dev-b's email address), team.member.removed (team/backend, "βˆ’" followed by dev-b's email address) and rbac.invitation.revoked (Change "status: invited β†’ revoked").

The Change column on the right shows what changed. The email address after "+" on team.member.added is the person added to the team; "name: backend β†’ backend-qa" on team.updated is the team's name before and after.

Step 3: Transfer any project they own

A project owner cannot be removed from the org as is. Move the ownership to someone else first.

Do it in the Projects panel of the member page. Press Transfer… on the owner row and pick the new owner from the org's members; a confirmation appears inside the row. Confirm transfer takes effect immediately.

The Projects panel of dev-b's member page, cropped. The acme-batch and acme-jobs rows show owner and Transfer…. On the acme-shop row, dev-c's email address is selected under owner, with "Transfer ownership of acme-shop to" followed by dev-c's email address and "?", and the Confirm transfer and Cancel buttons. ENV SCOPE on every row is "owner always has all envs".

In the demo, ownership of acme-shop had first gone from dev-b to dev-c (that is the screen above). Before removing dev-c, Transfer… on dev-c's page moved it back to dev-b. Each transfer is recorded in the audit log as rbac.assignment.owner_transferred.

A project created after the conversion to a Company org starts with no owner. Its Access page says "This project has no owner.", and an org_admin picks one with Designate owner.

Step 4: Press Remove from org

Remove from org is at the bottom of dev-c's page. It opens a confirmation:

The Remove from org confirmation: "Remove", dev-c's email address, "from Acme Shop?". Below it, an orange box reads "The 1 active service token(s) this person created will also be revoked:", then "dev-c-laptop b948c782", then "Any CI job or automatic token renewal (such as env.runtime-secret) that uses them will stop working." Cancel and Remove at the bottom right.

The confirmation names each service token dev-c created (dev-c-laptop) with the start of its Token ID; removing dev-c revokes them too. Any CI job using those tokens stops, so this is the moment to check that nothing important depends on them.

Here is dev-c's login and the dev-c-laptop token, pulling before and after Remove:

Terminal, recomposed. Under the two Before headings, dotvault pull acme-shop -E development wrote 2 keys both for dev-c (dotvault login) and for the dev-c-laptop service token (DOTVAULT_TOKEN). Under After Remove from org, the token's pull prints "βœ– AUTH_REQUIRED: invalid, expired or revoked service token" and echo $? prints 3; the login's pull prints "βœ– PERMISSION_DENIED: You do not have permission for project 'acme-shop' env 'development' …" and echo $? prints 4. The One minute later headings show the same two results.

After the removal, the token gets AUTH_REQUIRED (invalid, expired or revoked token) with exit code 3, and the login gets PERMISSION_DENIED with exit code 4. The token was tried about five seconds after the removal and the login about fifteen seconds after, and both were already cut off. A minute later, nothing had changed.

dotvault may keep using what it knows about who belongs to which org for up to 60 seconds, so a removal takes effect within a minute at most.

Step 5: Confirm in the lists and the audit log

dev-c's row is gone from Members. dev-c's row is also gone from the Access page of acme-shop, and the backend team now has only dev-b.

In Service Tokens, choose All (incl. revoked) to include revoked tokens.

The Service Tokens list, cropped. Columns: Name, Token ID, Role, Project, Envs, Created by, Created, Expires, Last used (hourly), Last-use IP, Status. The row: dev-c-laptop, b948c782263624dba9f21c04e2422c6e, Read only, acme-shop, development, dev-c's email address, the Created, Expires and Last used dates, Last-use IP blacked out, Status revoked.

dev-c-laptop is revoked. The Created by column shows who created each token.

In the audit log, the removal is three rows with the same timestamp. The Metadata column is at the right end of the table, so scroll the table right to read it (the bottom part of the figure).

The audit log, cropped (no Actor filter), shown in two stacked parts. Both parts show the same seven rows: the top part is the table as it opens (Timestamp, Actor, Action, Target and the left of Change), the bottom part is the table scrolled to its right end (Target, Change, Metadata). The top three rows share the same timestamp: member.removed (Target org_member/64782458-…, Change "βˆ’ role: member", Metadata revoked_token_ids:b948c782…), member.grants_removed (Actor dev-b's email address, Metadata kept_owner_targets:, reason:member_removed and removed:{"kind":"env_permission","projectEnv":"acme-shop:development"}, {"kind":"team_membership","teamId":"team_3mZFhAxahvkT"}) and service_token.revoked (Target service_token/b948c782…, Metadata name:dev-c-laptop, reason:member_removed and removed_sub:64782458-10c1…). Below them: two rbac.assignment.owner_transferred rows for project#acme-shop (Change "subject: 64782458… β†’ dev-b's email address" with Metadata self_granted:true, and "subject: dev-b's email address β†’ 64782458…"), service_token.created (Actor dev-c's email address, Metadata envs:development, expires_at and name:dev-c-laptop) and rbac.assignment.owner_transferred for project#acme-jobs (Change "subject: none β†’ dev-b's email address", Metadata self_granted:true).

Action What it records
member.removed dev-c was removed from the org. "βˆ’ role: member" in Change is the role dev-c had; revoked_token_ids in Metadata lists the tokens revoked with it
member.grants_removed dev-c's project grants (env_permission) and team memberships (team_membership) were deleted
service_token.revoked The token dev-c created, dev-c-laptop, was revoked

The Metadata records which grants were deleted, so even if you forgot step 1, you can read them here.

Step 6: Replace the values they could read

Removing dev-c from dotvault does nothing to the .env files dev-c has already pulled; they stay on dev-c's laptop. To make those values useless, the values themselves have to change.

In the demo, dev-b replaced the two values in development of acme-shop:

dotvault set STRIPE_SECRET_KEY=<new value> -p acme-shop -E development
dotvault set DATABASE_URL=<new value> -p acme-shop -E development

Each printed a line such as set STRIPE_SECRET_KEY (v3) in .env [acme-shop/development], the third change to that key. dev-c could read every environment through the team, so in real use you would replace the production, staging and qa values the same way.

For an API key issued by another service, such as a payment provider, issue a new key in that service's dashboard and disable the old one, then store the new value with set. For a database password, change it in the database first. Changing the value in dotvault alone leaves the old value on dev-c's laptop still working against that service.

Wrapping up

Hand .env files around, and the values on a leaver's laptop cannot be taken back. Invite people into a Company org in dotvault, grant access per project and per environment, and when someone leaves, note what they could read, check the audit log, transfer their projects, and press Remove from org. Their tokens, project grants and team memberships go with them, and in the demo both their token and their login were refused within fifteen seconds. What remains is replacing the values they could read.

dotvault stores the secrets in your .env and config files server-side and hands them to any authorised developer, CI job or AI agent with a single command (https://dotvault.io). It will be released free of charge when this series ends, and the steps in these articles are the steps you will use on day one.

If you find this useful, please like and follow.

Our products are listed at https://www.eln.ne.jp/products

EarthLink Network builds its own products to run the whole company on AI β€” each one born from a need along the way. See the full index of what we build:

β†’ The 18 products EarthLink Network builds in-house

For more about the company and each product, visit www.eln.ne.jp.

πŸ“° Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.