Skip to content

djgoku/sops

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sops: Edit SOPS-encrypted files transparently in Emacs

SOPS (Secret OPerationS) is a tool for managing secrets — encrypting values inside YAML/JSON/ENV/INI/binary files while leaving keys readable. This package lets you open a SOPS-encrypted file with find-file and edit it like any other buffer; encryption happens transparently on save.

Installation

(use-package sops
  :ensure (:type git :host github :repo "djgoku/sops")
  :init
  (global-sops-mode 1))

That’s it. No commands to bind, no special keystrokes.

Requires sops >= 3.9.0 (>= 3.13.0 recommended for inline YAML comment preservation).

Usage

  1. Open a sops-encrypted file with M-x find-file (or C-x C-f). The buffer shows decrypted contents and the sops lighter appears in the modeline.
  2. Edit normally.
  3. Save with C-x C-s. The file on disk stays encrypted.

To create a new encrypted file: M-x sops-find-file /path/to/new.enc.yaml. The buffer is seeded with a SOPS example (mirroring upstream sops <path>); edit it and save. Requires a reachable .sops.yaml (in the path’s directory or any ancestor) with a creation_rules entry matching the path.

If the path already exists, sops-find-file behaves identically to find-file – the global mode decrypts sops files transparently.

Configuration

VariableDefaultPurpose
sops-executable"sops"Path to sops binary
sops-prefilter-regex(see below)Files matching trigger filestatus check
sops-extra-decrypt-argsnilExtra args inserted between decrypt and the trailing file path
sops-extra-encrypt-argsnilExtra args appended after --filename-override FILE in encrypt
sops-input-type-overridesnilAlist of (REGEX . TYPE) for files where extension doesn’t tell sops the type

The default sops-prefilter-regex is:

"\\.\\(ya?ml\\|json\\|env\\|ini\\|txt\\)\\'"

It matches .yaml, .yml, .json, .env, .ini, and .txt file extensions. Override with your own regex if your encrypted files use a different naming scheme.

Hooks

  • sops-before-decrypt-hook — run before each decrypt; use to set AWS_PROFILE, age key paths, etc.
  • sops-before-encrypt-hook — run before each encrypt; same use.
  • sops-mode-hook — standard minor-mode hook.

Example (set AWS_PROFILE based on the file’s KMS ARN):

(defun my/sops-setup-aws-profile ()
  (when-let* ((file buffer-file-name)
              ((file-readable-p file))
              (contents (with-temp-buffer
                          (insert-file-contents file)
                          (buffer-string)))
              ((string-match "arn:aws:kms.*?:\\([[:digit:]]+\\):" contents)))
    (pcase (match-string-no-properties 1 contents)
      ("111111111111" (setenv "AWS_PROFILE" "dev"))
      ("222222222222" (setenv "AWS_PROFILE" "stage")))))

(add-hook 'sops-before-decrypt-hook #'my/sops-setup-aws-profile)
(add-hook 'sops-before-encrypt-hook #'my/sops-setup-aws-profile)

For non-interactive age PIN/passphrase, set SOPS_AGE_KEY_CMD in sops-before-decrypt-hook.

Migration from v0.1.X

v0.2 is a clean break. If your v0.1.X config used:

:bind (("C-c C-c" . sops-save-file)
       ("C-c C-k" . sops-cancel)
       ("C-c C-d" . sops-edit-file))

Remove all of the above. find-file and save-buffer now do the right thing automatically.

If you used sops-before-encrypt-decrypt-hook, split it into sops-before-decrypt-hook and sops-before-encrypt-hook. The context the hooks run in also changed and affects how you write the body:

  • v0.1.X combined hook ran in two different buffers — the user’s original buffer for decrypt (where buffer-file-name pointed at the encrypted file) and the *sops-mode-process*-/path temp buffer for encrypt (where buffer-file-name was nil and the path lived in the internal sops--original-buffer-file-name local).
  • v0.2 has no temp buffer. Both hooks run in the same buffer that visits the encrypted file, and buffer-file-name is that file’s path in both cases. Read buffer-file-name directly; the sops--original-* locals are gone.

If your v0.1.X hook handled the encrypt path by reading sops--original-buffer-file-name (or branched on whether buffer-file-name was nil), replace it with a plain buffer-file-name read — the encrypt-side branch is no longer needed.

See NEWS.org for the full break list.

Read-only on encrypted files

If sops fails to decrypt a file (auth issue, missing key, etc.), the buffer shows the encrypted contents in read-only-mode. The *sops-error: <file>* buffer shows what went wrong. Fix the auth issue, then M-x revert-buffer to retry.

Notes

Remote files

v0.2 does not work over TRAMP — the local sops binary cannot read remote paths. sops-mode skips remote files automatically.

Plaintext in git diff / magit-status

Some upstream sops repos ship a .gitattributes line like *.yaml diff=sopsdiffer plus a matching [diff "sopsdiffer"] block in .git/config that runs sops -d on each file before diffing. That makes git diff, git log -p, and magit-status show plaintext for sops-encrypted YAML files. This is independent of sops-mode.

If you want plaintext diffs for your own real-secret repos, you must opt into the textconv yourself. Be deliberate: anything that reads the diff (the GitHub web UI if anyone copies the config there, tig, IDE diff viewers, CI logs) will see plaintext. sops-mode protects your buffer and on-disk file; the textconv affects diff rendering and is a separate decision.

Future work

  • future work: Interactive prompt handling for age PIN, yubikey, passphrase

License

GPL-3.0-or-later.

About

Edit SOPS-encrypted files transparently in Emacs

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors