Set up credentials on a host
How to give Legato’s agents an SSH key and API keys on a new machine, so that agents use them without holding them, and you approve their use from the web UI, the TUI or your phone. Do the steps in order. What each part does is in How credentials reach agents, and every setting is in the configuration reference.
| What | Where it lives | Agents get |
|---|---|---|
| An SSH key | 1Password (or Legato’s own store) | Their own SSH agent socket; Legato signs each login you allow |
An API key with a proxied profile (elevenlabs, api-key) |
Legato’s own store or 1Password | A placeholder key and Legato’s proxy address |
Any other secret (profile secret) |
Legato’s own store or 1Password | The value, through legato credential exec, after you allow it |
1. Legato
Section titled “1. Legato”Build or install Legato as usual (task build), and run its TUI or legato serve on the host. That process serves the credential channels (the proxy and the SSH agent sockets) for every Legato on the machine.
Approvals reach you in the web UI, the TUI (!), Attention, and on your phone through ntfy (notifications.ntfy in the configuration). Set web.public_url so the phone’s link opens this host.
2. 1Password (for keys kept in 1Password)
Section titled “2. 1Password (for keys kept in 1Password)”Skip this part for keys you keep in Legato’s own store (step 4).
-
The
opcommand is optional. Legato reads 1Password with 1Password’s SDK, which is built in. You needoponly to make the service account from a terminal (step 3) or to read a public key (§ SSH keys), and Legato uses it only withsecrets.onepassword.backend: op. Any install works: Homebrew (brew install --cask 1password-cli), or the zip fromhttps://cache.agilebits.com/dist/1P/op2/pkg/v<version>/op_linux_amd64_v<version>.zip. Legato uses a service account, so the 1Password app does not need to be installed, and the app’s CLI integration (which needs anopowned by theonepassword-cligroup with the setgid bit) is not needed. -
Make a vault for Legato (here
Legato). A service account cannot read your Personal or Private vault. Put in it only the items agents may use. -
Make a service account with read access to that vault and nothing else: on 1password.com, Developer › Service Accounts › New, or
op service-account create Legato --vault Legato:read_items(that command needsop signinas yourself first). Save the token (ops_…) in 1Password: it is shown once. -
Save the token in a private file. In zsh:
Terminal window (umask 077; read -rs "t?token: " && printf %s "$t" > ~/.config/legato/op-token; echo)In bash,
read -rs -p "token: " t. The file must not be readable by other users; Legato refuses it otherwise. -
Check it (prints names and a public key, never a secret):
Terminal window OP_SERVICE_ACCOUNT_TOKEN="$(cat ~/.config/legato/op-token)" op item list --vault Legato
Never export OP_SERVICE_ACCOUNT_TOKEN in a shell profile, and do not write token: ${OP_SERVICE_ACCOUNT_TOKEN} in the configuration. Legato empties that variable in agent sessions, but token_file keeps it out of every environment in the first place.
3. SSH keys
Section titled “3. SSH keys”-
The keys. In 1Password: New Item › SSH Key in the
Legatovault (generate one, or import one without a passphrase). With the vault as a source (step 6), every SSH Key item in it is a credential, named after its title, and a key you add or delete is seen within about two minutes, with no restart. Or keep a key in Legato’s own store,legato secret add homelab-key --type keypair --from <private key file>, and add it tocredentials:by itself. -
Let it in. Add its public key to
~/.ssh/authorized_keyson each machine agents may log in to. From 1Password:op item get <item> --vault Legato --fields "public key". -
Know the hosts. Legato names a host by the key the server proves it holds, looked up in
~/.ssh/known_hosts(credential_channels.ssh_agent.known_hosts). Connect to each host once yourself, so it is listed. A host that is not listed is namedkey:SHA256:…in approvals and rules. -
Make ssh use Legato’s agent in agent sessions.
IdentityAgentin~/.ssh/configwins overSSH_AUTH_SOCK, so a line such asHost */IdentityAgent ~/.1password/agent.socksends every agent’s login past Legato. Keep it for your own shells only:# 1Password's agent everywhere except in Legato's agent sessions, which# have LEGATO_EXECUTION_ID and use Legato's own agent ($SSH_AUTH_SOCK).Match exec "test -z \"$LEGATO_EXECUTION_ID\""IdentityAgent ~/.1password/agent.sockPut it where the
Host *block was, before other blocks. Legato’s own git keeps using 1Password. When ansshcredential is configured, Legato logs a warning at start ifsshstill names another agent for every host.
4. API keys
Section titled “4. API keys”Store a key in Legato’s own store (legato secret add elevenlabs-key, the value on stdin), or keep it in 1Password and name it op://Legato/<item>/credential.
A proxied key works only with a tool that takes a base address. The elevenlabs profile gives agents ELEVENLABS_API_KEY (a placeholder) and ELEVENLABS_BASE_URL. libretto reads only the first today, so keep its key on the secret profile until libretto reads ELEVENLABS_BASE_URL.
5. GitHub
Section titled “5. GitHub”Agents read your repositories, push to legato/… branches, open pull requests and write issues through a GitHub App of your own, never with your login. Legato makes a token for each use, narrowed to one repository: read tokens last an hour, and a write token is made for one push or one pull request and revoked after it. The agent never holds a write token: its pushes go through Legato’s proxy, which reads which branches each push changes.
| Use | Default rule |
|---|---|
Fetch, pull, gh reads, API reads, of a listed repository |
no prompt |
Push to legato/**, or a pull request from such a branch |
asked once a session, per branch |
| Push to any other branch, or a pull request from it | asked each time |
Write issues (gh issue create, comment, close, …) |
asked once a session |
Read GitHub Actions (gh run list, view, watch, download, gh workflow view) |
no prompt |
Act on GitHub Actions (gh run rerun, cancel, delete, gh workflow run, enable, disable, gh cache delete) |
asked once a session |
| Push to the default branch, push a tag, delete a branch | refused |
-
Create the App. On GitHub: Settings › Developer settings › GitHub Apps › New GitHub App.
- Name: anything (
Legato for <you>). Homepage URL: anything. Webhook: untick Active. - Repository permissions: Contents read and write, Pull requests read and write, Metadata read, Issues read and write if agents are to write issues, and Actions read (or read and write, to let agents re-run, cancel and dispatch workflows) if agents are to see workflow runs. If you add a permission to an App that is already installed, accept the new permission on the installation (Settings › Applications › Installed GitHub Apps), or GitHub refuses the tokens that need it.
- Where can this GitHub App be installed: Only on this account. Create it.
- Name: anything (
-
Note its client ID (
Iv23…, on the App’s page). -
Generate a private key (the App’s page, Private keys). Store it in Legato and delete the download:
Terminal window legato secret add github-app --type keypair --from ~/Downloads/<app>.private-key.pemOr keep it in 1Password and name it as
op://Legato/<item>/<field>. A vault source gives SSH keys and secrets, not GitHub Apps, so the App key is listed incredentials:by itself. -
Install the App (its page, Install App): your account, Only select repositories, and pick the ones agents may use.
-
The configuration (step 6 below), with the same repositories:
credentials:- name: githubsecret: legato://github-appprofile: githubsettings:client_id: Iv23liAbCdEfrepositories: [cpave3/legato, cpave3/chimera]A repository that is not listed is refused, whatever the App is installed on.
owner/*lists every repository of an owner (repositories: [cpave3/*]): the App’s installation then decides which of them agents reach, and a repository added in the web UI (Settings › Agents › Repositories) can be cloned without changing this file. -
A backstop (optional). In each repository, Settings › Rules › Rulesets › New branch ruleset: target every branch except
legato/**, restrict creations, updates and deletions, and add your own role to the bypass list. GitHub then refuses the App outsidelegato/**even if Legato had a bug. Rulesets on private repositories need a paid plan.
Agents’ gh reads with Legato’s read tokens and never with your own gh login. If you want agents to use your login for queries (gh api, searches across repositories), turn that off:
credential_channels: gh: isolate_login: falseIn an agent session, git fetch and git pull get read tokens from Legato’s credential helper, and git push (to https://github.com/… or git@github.com:…) goes to Legato’s proxy. Agents are told to work on legato/<task ID>-<a few words>, to ask for that one branch before the first push (or, when they can build stacks with Staccato, for all of the task’s branches at once, so a stack is one approval), and to open pull requests with the API address Legato gives them ($LEGATO_GITHUB_API_URL), not with gh, which only reads.
PR tracking keeps its own read access (github.auth); this changes nothing there.
The reviewer App (for review passes)
Section titled “The reviewer App (for review passes)”A worker that runs review passes posts its reviews as a second GitHub App, so its approval of the author App’s pull requests counts.
-
Create another GitHub App, for example
legato-reviewer, with Pull requests: Read and write, Contents: Read and write, Checks: Read, Commit statuses: Read, Actions: Read and Metadata: Read. GitHub counts an App’s approval only when it can write the repository’s contents. Add Administration: Read to let Legato read which checks branch protection requires; without it, Legato countsreview_passes.required_checks. No webhook. -
Install it on the same repositories, and keep its private key as you did the first App’s.
-
Add it with the
github-reviewerprofile, and name it inreview_passes.github_credential:credentials:- name: reviewer-appsecret: op://Agents/legato-reviewer/private keyprofile: github-reviewertype: keypairsettings:client_id: Iv23liReViEwErrepositories: [cpave3/legato]
No agent is given this credential: Legato posts the reviews itself. See Review passes for the whole setup. Contents write would let the App push, so you may leave it off the bypass list of the ruleset above, as the first App is.
6. The configuration
Section titled “6. The configuration”In ~/.config/legato/config.yaml:
secrets: onepassword: token_file: ~/.config/legato/op-token # backend: op # read with the op command instead of the built-in SDK
# Every SSH key of the vault, and every API credential, password and# login as a secret named after the item's title.credential_sources: - vault: op://Legato policy: - { operation: ssh.login, approve: window, window: 30m } # offers: for 30 minutes, once, deny - { operation: ssh.forwarded-login, approve: deny }
credentials: - name: elevenlabs secret: legato://elevenlabs-key profile: secret policy: - { operation: read, approve: auto, limit: { requests: 200/day } } - { operation: read, approve: window, window: 30m }policy replaces the profile’s rules; leave it out to use them (the ssh profile asks once an hour for each host). Each item takes the rules that name an operation of its profile (ssh.login for a key, read for a secret) or none; with none, its profile’s own, so the rules above leave secrets asked once per agent session. profile: ssh or profile: secret gives only the keys or only the secrets. Other items (notes, documents, cards) are not credentials. A key that needs rules of its own can still be listed in credentials:: an entry there wins over a vault item of the same name: list a vault’s API key there to send it through Legato’s proxy (api-key) instead. Restart Legato after a change to the configuration: it reads it at start. Changes in the vault need no restart.
ssh offers an agent’s keys one after another, and a server allows only a few tries (MaxAuthTries, 6 by default). With more than about five keys in the vault, a login can fail with “Too many authentication failures” before the right key is offered. For such a host, name its key in ~/.ssh/config (IdentityFile of the public key file, and IdentitiesOnly yes).
7. Check it
Section titled “7. Check it”legato credential list # every credential, its profile and storelegato secret list # Legato's own store, and where its master key livesThen, in an agent’s terminal (spawn a shell agent from the web UI or the TUI):
ssh-add -l # lists only the credentials' keysssh -v <host> true # the approval appears; "Will attempt key: legato_default … agent"Approve the request, and the login goes through. For GitHub, in the same terminal:
git ls-remote https://github.com/<owner>/<repo> # a read, no promptgit push origin HEAD:refs/heads/legato/check-1 # an approval appears; push again after itlegato audit shows each request, grant and use; legato audit verify checks the log’s chain.
Agents are told about each credential and how to use it, in their Legato guidance: run ssh as usual, wait for an approval, and never get in another way.
In a sandboxed Chimera session (--sandbox), ssh fails before it connects with Bad owner or permissions on /etc/ssh/ssh_config.d/…: in the sandbox, root’s files appear to belong to nobody (ls -ln shows 65534), and ssh refuses a configuration file it cannot trust. Run ssh there in host mode, as with Legato’s own commands.
What this does not stop
Section titled “What this does not stop”An agent that runs outside the sandbox runs as you. It can read ~/.config/legato/op-token, the configuration, ~/.ssh, and ~/.1password/agent.sock, and it can push with your own gh login or SSH key by undoing the session’s git settings. These steps make the approved way the one agents use and are told to use, and they record every use; they do not make the other ways impossible. Turn on the sandbox (agents.sandbox.enabled: true) to hide those files from agents.
