Skip to content
Teams8 min read

Removing someone from git-crypt, SOPS or Ansible Vault

None of the three can take back access someone already had. What each tool does when a person leaves, in what order, and why old commits stay readable.

Crunu teamPublished 8 min read

Example. Kiteline is a fictional company.
On this page

You can't fully remove someone from git-crypt, SOPS or Ansible Vault. git-crypt has no command to take a user out at all. SOPS and Ansible Vault let you change the keys, but only for the next version of each file: the person's old key still opens every earlier commit in the repository. In all three tools, the fix is the same two steps. Re-encrypt with a key the person never had, then reissue every secret they could read at the provider that issued it.

This post walks through what each tool actually does when someone leaves, the order that avoids a gap, and the part that no file-encryption tool can do for you. The wider process, from accounts to provider keys, is in the guide to what to rotate when a developer leaves.

Why removal is hard when secrets live in Git

All three tools encrypt files that sit in a repository. That design has real strengths: no server to run, secrets versioned next to the code that uses them, and reviews in the same pull request. It also means the repository is the distribution channel. Every clone, fork, CI cache and laptop backup holds a full copy of the encrypted history.

Removing a person therefore has two halves. The first is cryptographic: future versions of the file must be encrypted so that the person's key no longer works. The second is about what they already have. Anyone who could decrypt a file at commit 120 can still decrypt commit 120 after you rotate at commit 121, because Git keeps the old ciphertext and the old key still matches it.

Example. Kiteline is a fictional company.

Keep that in mind through the rest of the post. Rotating keys protects what you write next. It never protects what was already readable.

git-crypt: there is no remove command

The git-crypt README is direct about it: "git-crypt does not support revoking access to an encrypted repository which was previously granted." It explains that in GPG mode "there's no del-gpg-user command to complement add-gpg-user", and in symmetric mode "there's no support for rotating the key." The reason given is that it is "an inherently complex problem in the context of historical data" (git-crypt README).

The feature request for removal has been open since 2015. In it, the maintainer explains that rotation means you "have to rotate git-crypt's internal symmetric key and re-encrypt all files", and that old key versions would still be needed to read old commits (git-crypt issue #47).

The workaround is to start over with a new key. Users in that issue have posted several scripts; they share the same outline:

  1. Decrypt the working copy with the current key and copy the protected files somewhere safe outside the repository.
  2. Remove the protected files and the .git-crypt directory, and commit.
  3. Run git-crypt init to create a new key, then git-crypt add-gpg-user for each person who should keep access.
  4. Put the files back, commit, and check with git-crypt status that they are encrypted before you push.
  5. Ask everyone who stays to pull and decrypt again with the new key.

Three caveats come with this. Branches that still carry the old key will conflict when merged, so do it when few branches are open. If you used a symmetric key exported with git-crypt export-key, every remaining person needs the new key file, delivered the same careful way as the first. And the history is untouched: one participant in the thread points out that the scripts change the key only for future commits.

git-crypt also leaves some things readable by design. The README notes that it "does not encrypt file names, commit messages, symlink targets, gitlinks, or other metadata." Removing a person changes nothing there: those parts were never encrypted, so treat them as readable by anyone with a clone.

SOPS: update the keys, then rotate the data key

SOPS encrypts each file with a data key, and wraps that data key for every recipient: an age or PGP key, or a key in a cloud KMS. Removing a recipient is supported, but there is a trap in it.

sops updatekeys reads your .sops.yaml and adds or removes recipients on the file. On its own, it does not change the data key. The SOPS documentation says to use updatekeys "if you want to add a key without rotating the data key", and recommends rotating when a key is removed, because the person may have obtained the data key already (SOPS key management).

For a compromised or departing recipient, the documented order is: remove the key from the key group in .sops.yaml, then run both commands for each encrypted file, then commit and push.

Shell
sops updatekeys secret.sops.yaml
sops rotate --in-place secret.sops.yaml

sops rotate "generates a new data encryption key and reencrypt all values with the new key." The documentation also warns that doing this in the wrong order can leave the old key with access in some cases.

Then comes the step that is easy to skip. SOPS is explicit that only after this is done "should you start rotating the actual passwords, API keys and other secrets". If you put new provider keys into the file before the data key changes, the old recipient can read the new values too.

If your recipients are cloud KMS keys rather than personal age or PGP keys, also check who can call decrypt on those KMS keys. Removing someone from the cloud account's IAM may be what actually ends their access to future versions.

Ansible Vault: rekey, file by file

Ansible Vault uses a password, or several passwords with vault IDs, rather than per-person keys. There is no list of people in the file. Anyone who knows the password can decrypt, so removing a person means changing the password and getting the new one to everyone who stays.

For whole encrypted files this is one command. The Ansible documentation calls password rotation for files "straightforward with the rekey command":

Shell
ansible-vault rekey --vault-id prod@old-password-file --new-vault-id prod@prompt group_vars/prod/vault.yml

Encrypted variables are harder. If you used ansible-vault encrypt_string to put single values inline in a YAML file, the same page says: "You cannot rekey encrypted variables" (Ansible: encrypting content). Each one has to be decrypted with the old password and encrypted again with ansible-vault encrypt_string under the new one. On a large inventory, that is the slow part of the job.

Vault IDs help limit the damage in the future. If development and production use different vault passwords, a developer who only ever had the development password does not force a production rekey. The comparison of SOPS and Ansible Vault covers how the two tools differ beyond offboarding.

What changes, and what doesn't

git-cryptSOPSAnsible Vault
Remove a personNot supported; start over with a new keyupdatekeys after editing .sops.yamlChange the shared password
Re-encrypt future versionsRe-initialize and re-add everyone elsesops rotate on every fileansible-vault rekey per file; inline variables one by one
Old commitsReadable with the old keyReadable with the old key or data keyReadable with the old password
Secrets inside the filesStill valid until reissuedStill valid until reissuedStill valid until reissued

You can rewrite Git history to drop old ciphertext, but it doesn't help here. Copies made before the rewrite still exist in clones, forks and backups, and the person you are removing may hold one. Treat everything in the old history as seen.

The step all three leave to you: reissue the values

Re-encryption only stops the person from reading what you write next. The database password, the Stripe key and the cloud access key inside those files are the same values they could read last week. To make their copy worthless, you have to create new values at each provider and switch your services over.

Example. Kiteline is a fictional company.

Do it in an order that avoids outages. For each value, issue a new one while the old one still works, deploy it, confirm nothing uses the old one, then revoke the old one. The key rotation guides by service cover the provider side for AWS, Stripe and others.

Then decide what to reissue. Here, file-based tools give you nothing to go on: none of the three records who decrypted which file, or when. The honest answer is usually "everything the person's key or password could open", which for a shared vault password means every value in every file that uses it.

A short checklist for file-encrypted repositories

The offboarding checklist tool builds a list like this for your own stack.

How it looks with a key per environment

Part of the difficulty above comes from the model: one key or one password protects many files, and nothing records reads. A vault built around per-environment keys changes some of this and leaves some of it in place.

Example. Kiteline is a fictional company.

In Crunu, each environment, such as payments-api / production, has its own key, sealed separately to each person's and machine's public key. When an owner or admin removes someone through Member removal, the server stops serving that person anything in the organization immediately, and the keys of every environment where they had a key are rotated in the admin's browser. A developer who only had development never held the production key, so production does not need re-encrypting on their account.

The wizard also shows an Exposure report: which versions of which environments the person received, when and from which addresses, built from the server's record of fetches. Next to it is a Reissue checklist of values they saw that are still valid, listed by file path and variable name, never by value.

The limit is the same as in the table above. Crunu does not reissue keys at AWS or Stripe, and rotation does not undo what the person already saw. It narrows the list to what they actually received instead of everything they could have opened. If you are weighing a move, see the pages for git-crypt, SOPS and Ansible Vault.

Sources