262 lines
12 KiB
Markdown
262 lines
12 KiB
Markdown
# Links
|
|
|
|
Subscriptions, workspaces, vaults, scopes, repos, files and secrets can
|
|
be shared as a `lazysecrets://` link, in chat or a ticket. Whoever opens
|
|
it lands on that item, reading it with their own account and
|
|
permissions: the link names the item, never its value. Env files can
|
|
also be [shared encrypted](#sharing-env-files), in the link itself.
|
|
|
|
```
|
|
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`); in an env file, also the file as
|
|
an [encrypted link](#sharing-env-files) (`f`)
|
|
- 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`)
|
|
- repos and env files: also their env files as an
|
|
[encrypted link](#sharing-env-files) (`f`)
|
|
|
|
## Sharing env files
|
|
|
|
`.env` files usually live only on your machine. `S` → `f` shares them as
|
|
an encrypted link, on a repo (all its plain env files), an env file, or a
|
|
key in one (its file):
|
|
|
|
```
|
|
lazysecrets://share/AQ…
|
|
```
|
|
|
|
The files are encrypted with a secret's value, so whoever can read that
|
|
secret (in Key Vault, Databricks or a repo's SOPS file) can open the link,
|
|
and nobody else: who may open it is up to the vault's access, as for
|
|
everything else. Make a secret for it once, random:
|
|
|
|
```sh
|
|
az keyvault secret set --vault-name kv-team --name env-share --value "$(openssl rand -base64 32)"
|
|
```
|
|
|
|
and name it in the repo's `.lazysecrets.yaml` (committed: the whole team
|
|
uses it) or your config (for repos that don't name one), by its
|
|
`lazysecrets://` link, Key Vault URL or `@Microsoft.KeyVault(...)`
|
|
reference:
|
|
|
|
```yaml
|
|
sharing:
|
|
key: https://kv-team.vault.azure.net/secrets/env-share
|
|
```
|
|
|
|
With neither, sharing asks which secret, in [Go to](keys.md#go-to): it
|
|
lists only secrets, and `enter` picks one (pasting a reference picks it
|
|
too).
|
|
|
|
Anyone with a link can try to guess the key, so sharing refuses one that
|
|
isn't long and varied enough, saying how (never showing it). By default
|
|
that's 32 characters of two kinds; your config can ask for more, or less:
|
|
|
|
```yaml
|
|
sharing:
|
|
keyRules:
|
|
minLength: 32 # characters; 16 at least
|
|
minClasses: 2 # kinds of character: lowercase, uppercase, digits, symbols
|
|
```
|
|
|
|
Opening a link doesn't check them.
|
|
|
|
On the command line, `lazysecrets share [--key <secret>] <env file>...`
|
|
prints the link.
|
|
|
|
Opening a share link says which repo it's from, how many files and which
|
|
secret it's encrypted with, and decrypts only when asked: lazysecrets
|
|
reads the secret with your access, then shows where each file goes in
|
|
your clone of the repo (or asks for a folder, if it isn't here), which
|
|
ones would replace a file there and which ones git doesn't ignore. It
|
|
saves them all, only the new ones, or none. New files are readable by you
|
|
only.
|
|
|
|
A link holds the files' paths and contents encrypted ([age](https://age-encryption.org),
|
|
the secret's value as passphrase); the repo, the secret and the number of
|
|
files are in the clear. Links stay under 16 KB, which fits chat and
|
|
tickets.
|
|
|
|
A link stays readable as long as its secret is. On Azure it's pinned to
|
|
the secret's version, so adding a new version (rotating) doesn't break
|
|
old links, and disabling the old version, or taking away someone's
|
|
access, shuts them out; Databricks secrets have no versions, so changing
|
|
the value breaks every old link. Whoever has read the secret's value
|
|
before can decrypt links made with it without asking the vault, so rotate
|
|
it now and then: new links use the newest version.
|
|
|
|
## 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, ending on a secret's details as `enter` on it would. 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.
|
|
|
|
### Listing, for fzf and pickers
|
|
|
|
`lazysecrets list` prints every source, vault and secret, one per line,
|
|
tab-separated:
|
|
|
|
| Field | |
|
|
|---|---|
|
|
| kind | `source`, `vault`, `secret`, `key` or `certificate` |
|
|
| name | `platform-prod / kv-app-prod / db-password` |
|
|
| account | what it's listed through: an az account (`Azure:<user>/<tenant>`), `Databricks` or `Repos` |
|
|
| link | its `lazysecrets://` link |
|
|
|
|
It's what Go to searches, so fzf, or an editor's picker, can search it
|
|
instead, showing the names and acting on the links:
|
|
|
|
```sh
|
|
lazysecrets list | fzf --ansi --with-nth='2..-3' --accept-nth=-1 \
|
|
--bind 'ctrl-o:become(lazysecrets open --account {-2} {-1})' \
|
|
--bind 'ctrl-y:execute-silent(lazysecrets get --account {-2} {-1} | pbcopy)'
|
|
```
|
|
|
|
`--with-nth='2..-3'` shows and searches only the names; `{-1}` and
|
|
`--accept-nth=-1` are the link, `{-2}` the account. All but the name are
|
|
one word, so this works without `--delimiter`. To filter by kind
|
|
yourself: `awk -F'\t' '$1 == "secret"'`.
|
|
|
|
`enter` prints the link (`lazysecrets get $(…)`), `ctrl-o` opens it in
|
|
lazysecrets and `ctrl-y` copies the value (`pbcopy` on macOS; `wl-copy` or
|
|
`xclip` on Linux). A value copied this way isn't cleared from the
|
|
clipboard after `values.clipboardClear`, as one copied in lazysecrets is.
|
|
|
|
`--account` makes `open` and `get` use the account it was listed through,
|
|
when several of yours can see it: without, `open` asks which, and `get`
|
|
tries the one that last read it first. The link stays as it is, to copy
|
|
and share; the account isn't part of it.
|
|
|
|
What's [cached](config.md#cache) prints at once. Then, like Go to, it loads
|
|
what's missing or older than `--max-age` (2m), and what's new in it follows
|
|
as it arrives: fzf shows it while you type. `--cached` loads nothing from
|
|
Azure or Databricks, for an answer at once. Repos are always read, from
|
|
disk. What it loads is cached for the TUI too. It stops when the reader
|
|
goes, after a pick in fzf.
|
|
|
|
`--kind` lists only some kinds, comma-separated: `source`, `vault` (also
|
|
Databricks scopes and repo files), `secret`, `key`, `certificate`. Lists
|
|
only other kinds need aren't loaded: `--kind source,vault` asks for no
|
|
secrets. Keys and certificates load only when asked for this way; else,
|
|
like Go to, they're listed as far as you opened them in lazysecrets.
|
|
|
|
A vault or source it can't read is a line too, as it fails: its name,
|
|
then `✗` and why in red (`errorColor`), with its link, so opening it
|
|
shows more. They're in the list, not on stderr, which would draw over fzf.
|
|
`--errors=false` leaves them out.
|
|
|
|
The name is coloured from your [theme](config.md#theme), with the
|
|
provider's icon when [icons](config.md#theme) are on (`ui.icons`); fzf
|
|
shows the colours with `--ansi`. `--color never` leaves them out, for
|
|
scripts, and `--icons=false` the icons. The link is never coloured.
|
|
|
|
`--format json` prints an object per line instead, for editor plugins,
|
|
without colours or icons, and with `error` on a list it couldn't read:
|
|
|
|
```json
|
|
{"link":"lazysecrets://azure/kv-app-prod.vault.azure.net/secrets/db-password","kind":"secret","provider":"azure","path":["platform-prod","kv-app-prod","db-password"],"account":"Azure:alice@example.com/<tenant>","accountLabel":"alice"}
|
|
```
|
|
|
|
`accountLabel`, the account's short name, is there when several az
|
|
accounts are signed in. Values are never listed.
|
|
|
|
## 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.
|