Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
45fd80b418
130 files changed
+26739
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 232 KiB |
@@ -0,0 +1,78 @@
|
||||
# Azure Key Vault
|
||||
|
||||
lazysecrets reads Azure with your az CLI login: run `az login` once, and
|
||||
it reuses it.
|
||||
|
||||
## Accounts
|
||||
|
||||
Every account signed in to az gets its own group in the Sources pane
|
||||
(`az login` again with another account to add one), and each account's
|
||||
vaults are read with that account. A subscription several accounts can see
|
||||
is listed under each, since their permissions can differ.
|
||||
|
||||
A service principal is used instead when `AZURE_CLIENT_ID`,
|
||||
`AZURE_TENANT_ID` and a secret or certificate are set in the environment.
|
||||
|
||||
On an account's group row, the fourth pane shows who it signs in as: name,
|
||||
user, tenant, object ID, whether the session used MFA, directory roles
|
||||
(built-in ones by name) and its subscriptions, read from az's account list
|
||||
and the token lazysecrets already has: no requests of their own. `y` on
|
||||
the row copies the object ID, the one access policies and role
|
||||
assignments ask for; `l` goes into the pane, where `j`/`k` pick any
|
||||
field, `/` filters them (`/tenant`) and `y` copies the selected one.
|
||||
|
||||
If an account's az login has expired, the status line says so once, and the
|
||||
Details pane on that account's rows says it until you sign in: press `L`
|
||||
there to run `az login` for it without leaving lazysecrets, and everything
|
||||
reloads.
|
||||
|
||||
## Permissions
|
||||
|
||||
Vaults are discovered with Resource Graph, so you need read access to the
|
||||
vault resources (e.g. *Reader*). Key Vault data-plane access is separate
|
||||
(Azure RBAC or access policies). With RBAC, typically:
|
||||
|
||||
| To | Role |
|
||||
|---|---|
|
||||
| list secrets, keys and certificates | *Key Vault Reader* |
|
||||
| read secret values | *Key Vault Secrets User* |
|
||||
| read certificate details | *Key Vault Certificate User* |
|
||||
|
||||
A missing permission is shown in the pane, shortened to what was denied
|
||||
(the action, or the access policy permission), with what to check: a
|
||||
role or group to activate in PIM, access from the vault's owner, or, when
|
||||
its firewall refused, the network you're on.
|
||||
|
||||
## Vaults you can't discover
|
||||
|
||||
A vault you can read but not see through Resource Graph can be pinned in
|
||||
the [config](config.md); it's listed under "Pinned vaults" and read with
|
||||
az's default account:
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
azure:
|
||||
vaults:
|
||||
- https://kv-foo.vault.azure.net/
|
||||
hideSubscriptions: [platform-mgmt] # by name or ID
|
||||
```
|
||||
|
||||
## What's shown
|
||||
|
||||
Secrets, keys and certificates, each on their own tab of the Secrets pane
|
||||
(`[`/`]`). `space` reveals a secret's value, or the public material of a
|
||||
key (PEM public key) or certificate; private key material is never
|
||||
fetched. `v` lists an item's versions. Items expiring within
|
||||
`expiryWarningDays` (30 by default) are marked `⚠ 12d`, expired ones `✗`.
|
||||
|
||||
The status pane (`0`) lists them across every vault loaded, opened or
|
||||
cached, soonest first, and its line counts them (`⚠ 3 expiring`); `enter`
|
||||
on one goes to it. That makes no requests of its own: expiry dates come
|
||||
with the lists. It says how many vaults it covers; `ctrl+a` in search
|
||||
loads the rest.
|
||||
|
||||
On a vault, the right pane shows its settings: resource group, location,
|
||||
SKU, whether access is by Azure roles or access policies, soft delete and
|
||||
purge protection, which networks can reach it, private endpoints and tags.
|
||||
They come with the list of vaults, so they cost no extra requests and are
|
||||
cached with it.
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
# Config
|
||||
|
||||
Optional, at `~/.config/lazysecrets/config.yaml` (or
|
||||
`$XDG_CONFIG_HOME/lazysecrets/config.yaml`, or `--config`). Flags override
|
||||
it. Unknown keys are an error, so typos don't go unnoticed.
|
||||
|
||||
```yaml
|
||||
icons: true # Nerd Font icons (v3+); also --icons=false or LAZYSECRETS_ICONS=0
|
||||
clipboardClear: 30s # clear copied values from the clipboard after this long; 0s disables
|
||||
revealTimeout: 60s # hide a revealed value after this long; 0s keeps it shown
|
||||
hideOnBlur: true # hide a revealed value when the terminal loses focus (tmux: focus-events on)
|
||||
expiryWarningDays: 30 # mark secrets, keys and certificates expiring this soon (⚠ 12d); expired ones get ✗
|
||||
timeout: 30s # per backend request
|
||||
loadDelay: 150ms # how long the cursor rests on a row before it loads; 0s loads at once
|
||||
sourcesView: grouped # or flat; ` toggles it
|
||||
collapseGroups: true # start with the Sources pane's groups folded; enter or space unfolds one
|
||||
updateCheck: true # look for a newer release once a day (U and lazysecrets update work either way)
|
||||
history: true # count uses of items for Recent (R)
|
||||
cache: true # keep what was loaded (never values) for the next start
|
||||
providers:
|
||||
azure: # applies to every az account
|
||||
enabled: true # default
|
||||
hideSubscriptions: [platform-mgmt] # by name or ID
|
||||
vaults: # listed under "Pinned vaults", for vaults
|
||||
- https://kv-foo.vault.azure.net/ # you can read but not discover
|
||||
databricks:
|
||||
enabled: true
|
||||
hideProfiles: [test]
|
||||
repos:
|
||||
enabled: true
|
||||
roots: [~/git]
|
||||
paths: [~/work/infra]
|
||||
include: ["secrets/**"]
|
||||
exclude: ["**/testdata/**"]
|
||||
```
|
||||
|
||||
More, each on its own page: [repos](repos.md) (`providers.repos`), the
|
||||
[link handler](links.md#from-the-browser-and-chat) (`linkHandler`),
|
||||
[keybindings](keys.md#rebinding-keys) (`keybinding`) and
|
||||
[custom commands](custom-commands.md) (`customCommands`).
|
||||
|
||||
## Clipboard
|
||||
|
||||
The clipboard is the system clipboard locally and OSC52 over SSH (your
|
||||
terminal must allow OSC52 writes; in tmux, `set -g set-clipboard on`).
|
||||
|
||||
## Theme
|
||||
|
||||
Colours follow your terminal's 16-colour palette by default. A `theme:`
|
||||
section overrides roles with hex colours, palette numbers (0-255) or names,
|
||||
plus attributes (`bold`, `italic`, `underline`, `reverse`, `faint`). Role
|
||||
names follow lazygit's `gui.theme` where they mean the same thing:
|
||||
|
||||
```yaml
|
||||
theme:
|
||||
activeBorderColor: ["#ff966c", bold] # focused pane
|
||||
inactiveBorderColor: ["#589ed7"]
|
||||
selectedLineBgColor: ["#2d3f76"] # cursor row background (default: palette 8)
|
||||
inactiveViewSelectedLineBgColor: ["#2f334d"] # in the other panes (default: palette 8, not bold)
|
||||
defaultFgColor: ["#c8d3f5"]
|
||||
optionsTextColor: ["#82aaff"] # keys and labels
|
||||
searchMatchColor: ["#ffc777", bold]
|
||||
errorColor: ["#c53b53"] # ✗ failures
|
||||
noticeColor: ["#c3e88d"]
|
||||
successColor: ["#c3e88d", bold] # the ✓ before confirmations
|
||||
warningColor: ["#ffc777"] # ⚠ what can't be done here
|
||||
mutedColor: ["#636da6"]
|
||||
favouriteColor: ["#ffc777"]
|
||||
valueColor: ["#ffc777"]
|
||||
azureColor: ["#82aaff"]
|
||||
databricksColor: ["#ff757f"]
|
||||
repoColor: ["#c099ff"]
|
||||
```
|
||||
|
||||
Icons need a [Nerd Font](https://www.nerdfonts.com) (v3+). If they render
|
||||
as boxes, turn them off with `icons: false`.
|
||||
|
||||
## Layering
|
||||
|
||||
Several config files can be layered, later ones winning (theme roles and
|
||||
keybindings merge one by one), with a comma-separated `--config` or
|
||||
`LAZYSECRETS_CONFIG_FILE`, like lazygit's `LG_CONFIG_FILE`. An editor
|
||||
plugin can so add a generated theme file to your own config, the way
|
||||
snacks.nvim themes lazygit from Neovim's highlight groups:
|
||||
|
||||
```sh
|
||||
LAZYSECRETS_CONFIG_FILE=~/.config/lazysecrets/config.yaml,~/.cache/nvim/lazysecrets-theme.yml lazysecrets
|
||||
```
|
||||
|
||||
## State
|
||||
|
||||
Stars, notes, usage and the layout are kept in
|
||||
`~/.local/state/lazysecrets/state.json` (`$XDG_STATE_HOME`), readable only
|
||||
by you: one record per source, vault or secret, keyed by its
|
||||
`lazysecrets://` link, never with values. A star belongs to the account you
|
||||
starred the item under (a vault two of your accounts can see can be starred
|
||||
for one); a note describes the item, so it shows under every account. It's
|
||||
JSON so an editor plugin can read it with a built-in parser; several
|
||||
lazysecrets windows can save to it without losing each other's changes.
|
||||
|
||||
Revealing, copying, opening in the browser, sharing and jumping to an item
|
||||
count as using it: its record keeps when it was last used and how often,
|
||||
for Recent (`R`). Records kept only for usage are dropped after 90 days
|
||||
unused, and beyond the 100 most recent. `history: false` stops counting.
|
||||
|
||||
It also keeps where you left lazysecrets in each folder (the selected
|
||||
source, vault or secret), and goes back there the next time it starts in
|
||||
that folder, unless it was started on a link.
|
||||
|
||||
## Cache
|
||||
|
||||
What lazysecrets has loaded is kept in
|
||||
`~/.cache/lazysecrets/cache.json` (`$XDG_CACHE_HOME`), readable only by
|
||||
you: the Sources lists, vaults and scopes, and secrets' names and metadata
|
||||
(tags, dates, references), never values. On the next start they show at
|
||||
once, and anything older than two minutes reloads in the background when
|
||||
you go to it, or when Go to searches it. While it does, the pane's
|
||||
bottom border says how old what it shows is (`cached 3h ago`). Revealing
|
||||
and copying always fetch the value live.
|
||||
|
||||
Lists not loaded for 30 days are dropped, and so is everything from an
|
||||
account you're no longer signed in to. Repos aren't cached: their files
|
||||
are read from disk anyway. Deleting the file is always safe, and
|
||||
`cache: false` stops using it.
|
||||
|
||||
## Updates
|
||||
|
||||
Once a day (`updateCheck`), in the background, lazysecrets asks the
|
||||
release server for the latest release; within the day it reuses the
|
||||
answer from the state file. Offline, or with the server down, it says
|
||||
nothing. A newer release shows in the footer, `v0.1.1 ↑ v0.2.0`; `U` (or
|
||||
`lazysecrets update --check`) shows its changelog.
|
||||
|
||||
Updating (`u` there, or `lazysecrets update`) downloads the release for
|
||||
your OS and CPU, checks it against the release's `checksums.txt`, makes
|
||||
sure it runs, and only then replaces the binary, atomically: a
|
||||
lazysecrets that's running keeps working, and the next start is the new
|
||||
version. A binary a package manager owns (Homebrew, Nix, a distro
|
||||
package) is left to it. Development builds (`dev-…`) aren't offered
|
||||
releases.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Custom commands
|
||||
|
||||
Run your own shell commands on the selected item, like lazygit's custom
|
||||
commands: by their key in the pane they're for, or from the commands menu
|
||||
(`x`, also listed under `?`).
|
||||
|
||||
```yaml
|
||||
customCommands:
|
||||
- key: K
|
||||
context: secrets # sources | vaults | secrets | global
|
||||
description: Create Kubernetes secret
|
||||
command: kubectl create secret generic {{.Name}} -n {{.Prompt.ns}} --from-file=value=/dev/stdin
|
||||
value: stdin # the value on the command's input; or env:NAME
|
||||
prompts:
|
||||
- {name: ns, title: Namespace, default: default}
|
||||
output: popup # quiet (default) | popup | terminal
|
||||
- key: E
|
||||
context: secrets
|
||||
description: Add to .env
|
||||
command: printf '%s=%s\n' {{.Name}} "$(cat)" >> .env
|
||||
value: stdin
|
||||
- context: vaults
|
||||
description: Show in az
|
||||
command: az keyvault show --name {{.Vault}}
|
||||
output: terminal
|
||||
```
|
||||
|
||||
## Fields
|
||||
|
||||
`.Kind`, `.Account`, `.Source`, `.SourceID` (subscription ID or Databricks
|
||||
profile), `.Vault`, `.VaultURL`, `.Name`, `.Type`, `.Version` (from the
|
||||
versions view), `.Link`, `.Reference`, `.URL` (web page), `.Note`,
|
||||
`.Expires`, and `.Prompt.<name>`. They're shell-quoted when filled in, so a
|
||||
note can't break or inject into the command; `{{raw .Note}}` opts out.
|
||||
|
||||
## The value
|
||||
|
||||
The secret value is never a field, so it can't end up on a command line
|
||||
(where `ps` and shell history see it). `value: stdin` hands it to the
|
||||
command's input, `value: env:NAME` puts it in that environment variable for
|
||||
the command alone; without `value` it isn't fetched. Commands that get the
|
||||
value ask first, showing the command (`confirm: false` skips that).
|
||||
|
||||
## Running
|
||||
|
||||
Output: `quiet` runs in the background with ✓ or ✗ in the footer, `popup`
|
||||
shows what it printed, `terminal` hands it the terminal and waits for
|
||||
enter. Commands run with `$SHELL -c` in the directory lazysecrets started
|
||||
in, and come only from your config: a link can never run one. Their keys
|
||||
are checked against the [keybindings](keys.md#rebinding-keys) at startup.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Databricks
|
||||
|
||||
Workspaces come from the profiles in `~/.databrickscfg` (or
|
||||
`$DATABRICKS_CONFIG_FILE`); account-console profiles are skipped. Each
|
||||
workspace is a source, its secret scopes are the vaults.
|
||||
|
||||
Credentials are resolved the same way as the Databricks CLI, so
|
||||
`auth_type = azure-cli` uses your az login and OAuth profiles use
|
||||
`databricks auth login`. When a login expires, `L` runs the right one for
|
||||
that profile.
|
||||
|
||||
Secrets in Azure Key Vault-backed scopes are read through Databricks; if
|
||||
Databricks refuses, lazysecrets reads the backing vault directly with your
|
||||
az login (which then needs the [Key Vault roles](azure.md#permissions)).
|
||||
|
||||
When Databricks refuses, the pane says what to check: access to the
|
||||
workspace and READ on the scope, or, when the workspace's IP access list
|
||||
refused, the network you're on.
|
||||
|
||||
Hide profiles you don't want in the [config](config.md):
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
databricks:
|
||||
hideProfiles: [test]
|
||||
```
|
||||
@@ -0,0 +1,63 @@
|
||||
# Development
|
||||
|
||||
```sh
|
||||
go run ./cmd/lazysecrets
|
||||
go run ./cmd/lazysecrets --demo # built-in sample data, no Azure needed
|
||||
go test ./...
|
||||
go test ./internal/ui/app -run TestGolden -update # after intentional UI changes
|
||||
```
|
||||
|
||||
A stripped binary is about 21 MB instead of 30 MB:
|
||||
|
||||
```sh
|
||||
go build -trimpath -ldflags "-s -w" -o lazysecrets ./cmd/lazysecrets
|
||||
```
|
||||
|
||||
Development builds show `dev-<commit>` as their version (`-dirty` with
|
||||
uncommitted changes); `go install …@v0.2.0` shows the tag, releases the
|
||||
version they were built for.
|
||||
|
||||
## Pull requests
|
||||
|
||||
Pull requests into `main` are squash-merged, so each is one commit titled
|
||||
like the PR. The title is a [conventional commit](https://www.conventionalcommits.org)
|
||||
subject, checked by `pr-title.yml`:
|
||||
|
||||
| Type | For | In the changelog |
|
||||
|---|---|---|
|
||||
| `feat` | a new feature | Added |
|
||||
| `perf` | faster or smaller, same behaviour | Changed |
|
||||
| `fix` | a bug fix | Fixed |
|
||||
| `refactor`, `docs`, `test`, `ci`, `chore`, `build`, `style`, `revert` | everything users don't need to hear about | no |
|
||||
|
||||
`feat!:` marks a breaking change, `feat(scope):` names a part. Brackets
|
||||
and a missing space work too: `[feat] x`, `[feat]: x` and `feat:x` all
|
||||
become `feat: x` in the changelog. `[skip changelog]` in the description
|
||||
leaves a pull request out.
|
||||
|
||||
CI (`ci.yml`) runs gofmt, vet, staticcheck, the tests and a build for each
|
||||
platform on pull requests into `main`. `main` is protected: a pull request
|
||||
merges only when it's up to date with `main` (Gitea's "Update branch"
|
||||
button, or a rebase, then CI again), so what lands on `main` is exactly
|
||||
what its pull request tested, and CI doesn't run again there.
|
||||
|
||||
## Releasing
|
||||
|
||||
Merge as many pull requests as you like, then release `main`:
|
||||
|
||||
```sh
|
||||
scripts/release.sh --dry-run v0.2.0 # the notes the release would have
|
||||
scripts/release.sh v0.2.0 # CHANGELOG.md, commit, tag, push
|
||||
```
|
||||
|
||||
It refuses unless you're on a clean `main` that matches `origin/main`.
|
||||
[git-cliff](https://git-cliff.org) (`cliff.toml`) writes `CHANGELOG.md`
|
||||
from the pull request titles. The tag runs the release workflow
|
||||
(`.gitea/workflows/release.yml`): it tests, then
|
||||
[GoReleaser](https://goreleaser.com) (`.goreleaser.yaml`) builds the
|
||||
archives and publishes a Gitea release with them and the release's
|
||||
changelog section. `goreleaser release --snapshot --clean` builds the same
|
||||
archives locally, into `dist/`, without publishing.
|
||||
|
||||
A suffix (`v0.2.0-rc.1`) makes a pre-release, which leaves `CHANGELOG.md`
|
||||
alone.
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
# Keys
|
||||
|
||||
`?` lists every key with what it does, the focused pane's first; type to
|
||||
filter, `enter` runs one. The help bar at the bottom shows the most useful
|
||||
keys for where you are. Every key can be [rebound](#rebinding-keys).
|
||||
|
||||
The rule: a letter in the lists, ctrl+ that letter where you type a filter
|
||||
(`f` stars, `ctrl+f` stars while filtering).
|
||||
|
||||
## Moving around
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `j`/`k`, `↑`/`↓` | move |
|
||||
| `g`/`G` | top / bottom |
|
||||
| `tab`, `shift+tab`/`h` | next / previous pane |
|
||||
| `l` | the details of what's selected, on the right (`h` goes back) |
|
||||
| `1`–`4` | jump to pane |
|
||||
| `enter` / `esc`, `⌫` | drill in / back (also clears a filter or leaves the versions view) |
|
||||
| `[` / `]` | previous / next tab of the focused pane: Sources and Favourites in Sources; Secrets, Keys, Certs in Secrets |
|
||||
| `/` | filter the focused list by name or note ([how it matches](#searching)); `enter` keeps it, `esc` clears it. While typing: `ctrl+j`/`ctrl+k` move, `ctrl+y` copies, `ctrl+r` reveals, `ctrl+f` stars |
|
||||
| `` ` `` | switch the Sources pane between grouped by provider and a flat list; `enter`/`space` on a provider folds it |
|
||||
| `r` | refresh the focused pane |
|
||||
| `q`, `ctrl+c` | quit |
|
||||
|
||||
## Searching
|
||||
|
||||
`/` in a pane, Go to, `?` and other popups with a text field all search
|
||||
the same way, with [fzf](https://github.com/junegunn/fzf)'s scoring (the
|
||||
same as snacks.nvim's picker): letters at the start of a word or in one
|
||||
run count far more than letters scattered through a name, matches in an
|
||||
item's own name more than in its path or note, and equally good matches
|
||||
put the shorter name first.
|
||||
|
||||
| Query | Finds |
|
||||
|---|---|
|
||||
| `dbpw` | fuzzy: `db-password` |
|
||||
| `pass prod` | both terms, in any order |
|
||||
| `smtp \| key` | either |
|
||||
| `'prod` | exactly `prod`, not fuzzy |
|
||||
| `^kv-` / `-dev$` | names starting / ending with it |
|
||||
| `!test` | without `test` |
|
||||
|
||||
Lowercase ignores case; typing an uppercase letter makes that term
|
||||
match case exactly.
|
||||
|
||||
## Secrets
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `space` | reveal / hide the selected value (public material for keys and certificates) |
|
||||
| `y` | copy the value without revealing it; cleared from the clipboard after 30s. On an Azure account's row: its object ID; in its pane, the selected field |
|
||||
| `Y` | copy the reference (`@Microsoft.KeyVault(...)`, key/certificate URL, `{{secrets/scope/key}}`) |
|
||||
| `v` | versions of the selected item (Azure); `space`/`y`/`Y` then act on that version; `esc`, `⌫` or `v` to go back |
|
||||
| `o` | open the selected item in the browser (over SSH: copy the link) |
|
||||
| `e` | open the selected SOPS or `.env` file, or a secret's file at its line ([how](repos.md#opening-files)) |
|
||||
| `s` / `S` | copy a [link](links.md) to the selected item / share menu |
|
||||
| `ctrl+o` | open a link or reference (or just paste one) |
|
||||
| `x` | [custom commands](custom-commands.md) |
|
||||
| `L` | sign in again (shown when a login has expired) |
|
||||
| `U` | a newer lazysecrets: its changelog, and update in place (restart to use it); or look for one |
|
||||
|
||||
A revealed value is shown as it reads best: JSON indented, and PEM
|
||||
certificates as their subject, issuer, dates, names and thumbprint, with
|
||||
any other PEM block (a private key) named but not shown. `y` still copies
|
||||
it exactly as stored.
|
||||
|
||||
A revealed value hides again after `revealTimeout` (60s) and when the
|
||||
terminal loses focus (`hideOnBlur`); see [config](config.md).
|
||||
|
||||
## Stars and notes
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `f` | star the selected source, vault or secret: it gets a ★ and sorts first in its list |
|
||||
| `n` | write a short note on it, shown dimmed on its row and in the detail pane |
|
||||
|
||||
A star belongs to the account you starred the item under; a note shows
|
||||
under every account. Both are kept in the [state file](config.md#state).
|
||||
|
||||
## Go to
|
||||
|
||||
`ctrl+s` opens Go to on its Search tab, `F` on Favourites (everything you
|
||||
starred) and `R` on Recent (what you used last). `enter` goes to the
|
||||
selected item.
|
||||
|
||||
Search has the scope on the left (All; all of Azure, Databricks or repos;
|
||||
or one source; with `○ ◐ ●` for how much of it is loaded) and what's in
|
||||
it on the right. All repos searches every repo without asking Azure or
|
||||
Databricks for anything. It opens on the
|
||||
source you're in. Typing a search loads what it covers: everything on
|
||||
All, else the source picked; results appear as lists arrive. That's once
|
||||
each time Go to opens, and only what isn't loaded yet; `ctrl+a` loads
|
||||
again, lists that failed included.
|
||||
|
||||
Like a pane, Go to is either browsing or typing a filter:
|
||||
|
||||
| Browsing | Typing | Action |
|
||||
|---|---|---|
|
||||
| `j`/`k` | `ctrl+j`/`ctrl+k` | move |
|
||||
| `h`/`l`, `tab` | `ctrl+h`/`ctrl+l`, `tab` | scope list / results |
|
||||
| `[` / `]` | | previous / next tab |
|
||||
| `f` | `ctrl+f` | star |
|
||||
| `r` | `ctrl+r` | load the selected item, or the scope when nothing is selected |
|
||||
| `a` | `ctrl+a` | load everything in the scope (again) |
|
||||
| `/` | `esc` | start typing / clear it and go back to browsing |
|
||||
| `esc`, `q` | | close |
|
||||
|
||||
Favourites and Recent come from the state file, so they work before
|
||||
anything loads.
|
||||
|
||||
## Layout
|
||||
|
||||
The pane on the right describes what's selected in the pane you're in: the
|
||||
account or subscription in Sources, the vault in Vaults, the secret in
|
||||
Secrets. Going into it (`l`, or `4`) keeps it on that; there `j`/`k`,
|
||||
`/` and `y` work on its rows, and `h` goes back. A value you reveal stays in view whichever pane you're in, until
|
||||
it's hidden.
|
||||
|
||||
On top, the status pane (`0`) shows the version, a newer release and how
|
||||
your accounts are doing. Focused, the right pane lists more under the
|
||||
logo (when there's room): the version and updates (`U`), each account
|
||||
(`L` on one that needs it signs in), what's expiring (`enter` goes to
|
||||
it), this session's errors, newest first (what a pane showed, or the
|
||||
footer flashed before you moved on; not kept after quitting), the config,
|
||||
state and cache files (`e` opens one in your editor), and links to the
|
||||
docs, source, issues, releases and changelog (`o` opens one; on the
|
||||
version or update, its release), then the changelog of every release up
|
||||
to yours, built in so it needs no network: `/` filters it (`/fixed`,
|
||||
`/search`). `y` copies any row.
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `ctrl+←`/`ctrl+→` or `<`/`>` | move the divider between the left panes and the detail pane |
|
||||
| `ctrl+↓`/`ctrl+↑` or `}`/`{` | make the focused left pane taller / shorter |
|
||||
| `+` / `_` | zoom the focused pane: normal → half → full screen, and back |
|
||||
| `=` | reset the layout (divider, heights, zoom) |
|
||||
|
||||
The divider and pane heights are remembered (zoom isn't). macOS uses
|
||||
ctrl+arrows for Mission Control and switching desktops by default, hence
|
||||
the `<` `>` `{` `}` alternatives.
|
||||
|
||||
## Rebinding keys
|
||||
|
||||
Every action can be rebound in the [config](config.md), per context:
|
||||
|
||||
```yaml
|
||||
keybinding:
|
||||
lists: # the panes, when not typing
|
||||
star: [x] # replaces f
|
||||
versions: [] # unbound
|
||||
filter: # typing a filter in a pane
|
||||
star: [ctrl+x]
|
||||
popup: # popups with a text field: help, notes, open link, Go to while typing
|
||||
down: [ctrl+j, down]
|
||||
menu: # popups without a text field
|
||||
search: # Go to's own actions while typing (on top of popup)
|
||||
goto: # Go to while browsing
|
||||
```
|
||||
|
||||
An action you set gets exactly the keys you list, replacing its defaults;
|
||||
`[]` unbinds it. Actions you don't set keep their defaults, also across
|
||||
layered config files. `lazysecrets keybindings` prints every action with
|
||||
its keys (your config applied), ready to copy and edit. A key bound to two
|
||||
actions that apply together is an error at startup, naming both. The help
|
||||
bar, `?` and every hint show the keys you bound. Fixed: the letters inside
|
||||
menus (they're part of each entry), and `ctrl+c`, which always quits.
|
||||
|
||||
Without the kitty keyboard protocol (or tmux `extended-keys`), some
|
||||
combinations never reach a terminal app, such as `ctrl+shift+x`, `ctrl+1`
|
||||
or `ctrl+enter`.
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
# Links
|
||||
|
||||
Every subscription, workspace, vault, scope, repo, file and secret has a
|
||||
`lazysecrets://` link. Share one in chat or a ticket, and whoever opens it
|
||||
lands on that item, reading it with their own account and permissions:
|
||||
links carry names, never values.
|
||||
|
||||
```
|
||||
lazysecrets://azure/kv-app-prod.vault.azure.net vault
|
||||
lazysecrets://azure/kv-app-prod.vault.azure.net/keys vault, Keys tab
|
||||
lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password secret
|
||||
lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password/91d3e0 one version
|
||||
lazysecrets://databricks/adb-1111111111111111.11.azuredatabricks.net/app/storage-key
|
||||
lazysecrets://repo/git.example.com%2Fteam%2Finfra/secrets%2Fapp.yaml/db.password
|
||||
```
|
||||
|
||||
## Sharing
|
||||
|
||||
`s` copies a link to the selected item (from the versions view, pinned to
|
||||
that version). `S` opens the share menu:
|
||||
|
||||
- secrets: the link (`o`), the plain Key Vault URL (`u`), the config
|
||||
reference (`y`) or the web page (`b`)
|
||||
- vaults and scopes: a link (`o`), a link that opens on the current tab
|
||||
(`t`), the plain vault URL (`u`) or the web page (`b`)
|
||||
- subscriptions and workspaces: a link (`o`) or the web page (`b`)
|
||||
|
||||
## Opening
|
||||
|
||||
- Paste it into lazysecrets, or press `ctrl+o`, which reads the clipboard
|
||||
and fills in any reference it finds there. Key Vault URLs,
|
||||
`@Microsoft.KeyVault(...)` and `{{secrets/scope/key}}` work too.
|
||||
- `lazysecrets <link>` (or `lazysecrets open <link>`) in a terminal.
|
||||
- Click it anywhere, once the [link handler](#from-the-browser-and-chat) is
|
||||
set up.
|
||||
|
||||
If you copy a `lazysecrets://` link elsewhere and switch back, lazysecrets
|
||||
offers to open it, once per link. It looks at the clipboard at startup,
|
||||
when the terminal regains focus (in tmux: `set -g focus-events on`) and,
|
||||
for terminals that don't report focus, on a key press at most every 3
|
||||
seconds. It keeps nothing but `lazysecrets://` links, doesn't offer a link
|
||||
to what's already selected (like one you just copied with `s`), and can't
|
||||
read the clipboard over SSH.
|
||||
|
||||
lazysecrets finds the vault in your accounts (one Resource Graph query per
|
||||
account) and jumps to it, asking which account to use when several can see
|
||||
it. Links say what, not what to do: opening a link to a secret asks whether
|
||||
to just go to it, also copy or show its value, or (`y`) copy its value
|
||||
without going there.
|
||||
|
||||
## In scripts
|
||||
|
||||
`lazysecrets get <link>` prints the value, for scripts and pipes:
|
||||
|
||||
```sh
|
||||
export DB_PASSWORD=$(lazysecrets get lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password)
|
||||
lazysecrets get lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password | kubectl create secret generic db --from-file=password=/dev/stdin
|
||||
```
|
||||
|
||||
It reads the value directly, with your az accounts (the one that last read
|
||||
it first), the Databricks profile for the workspace, or sops in your clone
|
||||
of the repo; a version in the link reads that version. The value ends with
|
||||
a newline only on a terminal.
|
||||
|
||||
## From the browser and chat
|
||||
|
||||
```sh
|
||||
lazysecrets handler install # run it in the terminal links should open in
|
||||
lazysecrets handler uninstall
|
||||
```
|
||||
|
||||
Use an installed binary (`go run`'s is temporary). lazysecrets offers to do
|
||||
this at startup when links don't open it (or open a lazysecrets that no
|
||||
longer exists): set it up, not now, or don't ask again, which is
|
||||
remembered in the state file. `linkHandler.prompt: false` in the config
|
||||
turns the question off too.
|
||||
|
||||
`handler install` registers `lazysecrets://` with the system: clicking a
|
||||
link then opens a new window of the terminal you ran it in, with
|
||||
lazysecrets on that link. It recognises kitty, Ghostty, WezTerm, iTerm2,
|
||||
Terminal.app, Alacritty, GNOME Terminal, Konsole and foot, also inside
|
||||
tmux; `--terminal NAME` picks another. It records your `PATH` and config
|
||||
locations, since apps start without your shell's environment (and so
|
||||
without `az`): run it again after moving lazysecrets or changing those.
|
||||
|
||||
- macOS: a small background app, `~/Applications/Lazysecrets Links.app`
|
||||
- Linux: a desktop entry claiming `x-scheme-handler/lazysecrets`, set as
|
||||
the default with `xdg-mime` (desktops following freedesktop.org)
|
||||
- Windows: not yet
|
||||
|
||||
The config can override the terminal, or run any other:
|
||||
|
||||
```yaml
|
||||
linkHandler:
|
||||
terminal: ghostty # instead of the one install detected
|
||||
command: [foot, --title, secrets, "{cmd}"] # any terminal; {cmd} is the lazysecrets command
|
||||
reuse: true # hand links to a running lazysecrets first
|
||||
prompt: true # offer to set up the handler at startup
|
||||
```
|
||||
|
||||
### Where a clicked link opens
|
||||
|
||||
- **In a terminal** (a link in a terminal app): a lazysecrets that
|
||||
terminal shows (or whose tmux session one of its tmux clients can switch
|
||||
to) gets it, never one elsewhere; otherwise a new window opens.
|
||||
- **Anywhere else** (Slack, a browser): the lazysecrets you used last (key
|
||||
press or focus), preferring one already on screen; a tmux client is
|
||||
switched to its session if needed, its terminal comes to the front, and
|
||||
a hidden dropdown terminal (kitty's quick access terminal) is shown.
|
||||
With none running, a new window opens.
|
||||
|
||||
The link is then offered as if pasted. `linkHandler.reuse: false` always
|
||||
opens a new window.
|
||||
|
||||
Running instances listen on a socket in `$XDG_RUNTIME_DIR/lazysecrets`, or
|
||||
`run/` in the state directory, readable only by you. A link from a web page
|
||||
can only make lazysecrets go somewhere: it asks before copying or showing
|
||||
anything.
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
# SOPS and .env files in git repos
|
||||
|
||||
lazysecrets shows git repositories under **Repos**: their
|
||||
[SOPS](https://github.com/getsops/sops)-encrypted files (YAML, JSON,
|
||||
`.env`, INI) and plain env files as vaults, and the keys in them
|
||||
(`db.password`, `list[0]`) as secrets.
|
||||
|
||||
## Which repos
|
||||
|
||||
**Repos** lists every repo with secret files under the `roots` (`~/git`
|
||||
by default; one or two levels down, `repo` or `org/repo`), any listed in
|
||||
`paths`, and any you starred something in (the repo, a file or a key),
|
||||
wherever it is. They're checked in the background.
|
||||
|
||||
Started inside a repo, that repo gets a row of its own at the top,
|
||||
`Repo · <name>`, and lazysecrets opens on it, with the files under the
|
||||
directory you started in first. In the flat list (`` ` ``) it sorts first, marked
|
||||
`· here`.
|
||||
|
||||
A repo without secret files isn't listed. `--dir <path>` starts somewhere
|
||||
else, for example in one project of a monorepo.
|
||||
|
||||
Repos load themselves as they're found, files and keys, since they're
|
||||
local and cheap: [Go to](keys.md#go-to) searches them without loading
|
||||
anything by hand.
|
||||
|
||||
## Which files
|
||||
|
||||
**Env files**: `.env`, `.env.local`, `prod.env` and the like, also when
|
||||
git-ignored (as they usually are), in the folders git tracks files in;
|
||||
not `.env.example`, `.sample`, `.template` and the like. They're read
|
||||
directly; a file with SOPS metadata is always treated as SOPS, whatever
|
||||
its name.
|
||||
|
||||
Folders of dependencies and caches are never looked in, tracked or not:
|
||||
`node_modules`, `vendor`, `.venv`, `venv`, `__pycache__`, `.terraform`,
|
||||
`.gradle`, `.yarn`, `.next` and the like.
|
||||
|
||||
**SOPS files**: those tracked by git, then the first of
|
||||
|
||||
1. `include`/`exclude` in the repo's own `.lazysecrets.yaml` (committed,
|
||||
so everyone sees the same files),
|
||||
2. `include`/`exclude` in the config,
|
||||
3. where the repo's `.sops.yaml` `creation_rules` say SOPS files go,
|
||||
4. the whole repo.
|
||||
|
||||
`include`/`exclude` apply to env files too.
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
repos:
|
||||
roots: [~/git] # folders of repos (the default; [] turns it off)
|
||||
paths: [~/work/infra] # single repos elsewhere
|
||||
include: ["secrets/**", "**/*.sops.yaml"] # globs from the repo root; ** for any depth
|
||||
exclude: ["**/testdata/**"]
|
||||
```
|
||||
|
||||
## Decrypting
|
||||
|
||||
Listing never decrypts: SOPS encrypts values only, so keys and metadata
|
||||
(encryption, last modified) are read straight from the files. Revealing,
|
||||
copying or `lazysecrets get` decrypts one value with your `sops` binary, so
|
||||
whatever sops uses works (age keys, Azure Key Vault through az, AWS/GCP
|
||||
KMS, PGP). When sops can't decrypt, its whole error message is shown.
|
||||
|
||||
On a file, the right pane shows how it's encrypted and the keys it's
|
||||
encrypted for (public: age recipients, Key Vault keys, KMS ARNs, PGP
|
||||
fingerprints), when it was last encrypted, and what git knows of it:
|
||||
tracked, ignored or neither, and its last commit. A plain `.env` file that
|
||||
git tracks, or that isn't ignored, is marked in red: its values are in the
|
||||
repo's history, or easily could be.
|
||||
|
||||
## Opening files
|
||||
|
||||
`e` opens the selected file, or on a secret, its file at that key's line:
|
||||
|
||||
- **SOPS files** open with `sops edit`, which decrypts them into your
|
||||
editor (`$SOPS_EDITOR` or `$EDITOR`) and encrypts them again when you
|
||||
save. lazysecrets comes back after, with the file's keys reloaded.
|
||||
- **Other files**, when lazysecrets runs inside nvim (its plugin, or
|
||||
`:terminal`): lazysecrets quits and the file opens in that nvim, in the
|
||||
window you came from.
|
||||
- Otherwise in `$VISUAL` or `$EDITOR` in the terminal, coming back after;
|
||||
without either, in the system's default app for the file.
|
||||
|
||||
Editors that take a line (vim, nvim, nano, emacs, micro, helix, VS Code,
|
||||
Zed, Sublime and others) start on the key. With `sops edit` that works for
|
||||
editors taking `+N` (vim, nvim, nano, emacs, micro and others), and needs
|
||||
sops 3.8 or later.
|
||||
|
||||
## Links
|
||||
|
||||
Links name the repo by its git remote, so they open in a colleague's clone
|
||||
wherever it is:
|
||||
|
||||
```
|
||||
lazysecrets://repo/git.example.com%2Fteam%2Finfra/secrets%2Fapp.yaml/db.password
|
||||
```
|
||||
|
||||
See [links](links.md).
|
||||
Reference in new issue
Block a user