Inbound SSH to infra-services: humans (1Password) and agents (Cursor)¶
Use this runbook when you want both:
- You — interactive terminal, 1Password as SSH identity, normal ergonomics.
- Cursor / automation — non-interactive tools that must
ssh infra-serviceswithout prompts, without 1Password touch approvals, and without you typing a password.
Those are two different trust paths. Keep them separate keys and separate
Host aliases so OpenSSH never offers the wrong identity first.
Related: outbound Git identities on the VM (ansible-pull, git push) are
documented in ssh-keys-and-infra-services.md.
This document is inbound only (your workstation → someone@192.168.6.17).
Target end state¶
| Who | Command | Key material | Agent |
|---|---|---|---|
| You | ssh infra-services |
1Password-managed key (.pub path in SSH config) |
1Password / OpenSSH agent (IdentityAgent in your global config) |
| Cursor / scripts | ssh infra-services-cursor |
File-backed key pair on disk (e.g. ~/.ssh/cursor-infra) |
None (IdentityAgent none on that host stanza) |
Host: infra-services is 192.168.6.17, user someone (see inventory / workspace rules).
Prerequisites on your workstation¶
- OpenSSH client available in the same environment Cursor uses (Windows OpenSSH is fine).
~/.ssh/config(Windows:C:\Users\<you>\.ssh\config) includes:- A
Host infra-servicesblock:HostName,User someone, 1PasswordIdentityFile(often a~/.ssh/1Password/*.pubpath),IdentitiesOnly yes. - A
Host infra-services-cursorblock: sameHostName/User,IdentityFilepointing at a normal private key file on disk,IdentityAgent none,IdentitiesOnly yes. - Global
Host *behavior you already use:IdentitiesOnly yesplusInclude ~/.ssh/1Password/configandIdentityAgentpointing at the Windows OpenSSH agent pipe is OK — theinfra-services-cursorstanza overrides the agent for that alias only.
Adjust paths to match your machine; the names infra-services /
infra-services-cursor are the important part for muscle memory and for agents.
Path A — Your normal way (terminal + 1Password)¶
Goal¶
ssh infra-services opens a shell using the key 1Password injects via the
agent / IdentityFile wiring in ~/.ssh/1Password/config.
Steps¶
- Confirm 1Password is running and SSH agent integration is enabled for this vault/item (per 1Password docs for your platform).
- Confirm
Match Host 192.168.6.17 User someone(or equivalent) exists in~/.ssh/1Password/configand matches theIdentityFilereferenced byHost infra-services. - Test from a normal terminal (outside Cursor if you are debugging):
- If this fails, fix 1Password/agent first — do not move on to Path B until it passes.
Daily use¶
Path B — Cursor / automation (no human in the loop)¶
Goal¶
From Cursor’s non-interactive shell (or CI later), this must succeed with no password and no 1Password UI:
Steps¶
- Key pair dedicated to automation (example name
cursor-infra): - Private key on disk:
%USERPROFILE%\.ssh\cursor-infra(no extension). - Public key:
cursor-infra.pubalongside it. -
Generate if missing:
-
SSH config —
Host infra-services-cursormust include at minimum:
Host infra-services-cursor
HostName 192.168.6.17
User someone
IdentityFile ~/.ssh/cursor-infra
IdentitiesOnly yes
IdentityAgent none
-
Authorize the public key on the server for
someone(see next section). -
Verify from the same environment Cursor uses (important: paths differ between WSL vs Windows OpenSSH):
Why not use 1Password for Path B?¶
Cursor’s integrated terminal often cannot complete 1Password prompts or
see the same agent state as your interactive login. A file key plus
IdentityAgent none on infra-services-cursor removes that dependency.
Installing the automation key on infra-services¶
The public half must be in /home/someone/.ssh/authorized_keys on
infra-services.
Ansible note: the common role manages authorized_keys from GitHub with
exclusive: false by default (common_ssh_authorized_keys_exclusive: false),
so adding another line for cursor-infra will not wipe GitHub keys — but
do not set exclusive: true unless you intend to replace keys wholesale.
Pick one approach:
Option 1 — One-time append (fastest)¶
While logged in the human way (ssh infra-services):
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo 'PASTE_CONTENT_OF_cursor-infra.pub_ONE_LINE' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Option 2 — Pipe from your workstation¶
From a machine that already has cursor-infra.pub:
type %USERPROFILE%\.ssh\cursor-infra.pub | ssh infra-services "mkdir -p .ssh && chmod 700 .ssh && cat >> .ssh/authorized_keys && chmod 600 .ssh/authorized_keys"
(PowerShell: Get-Content ... | ssh ... equivalent.)
Option 3 — Publish the pubkey via GitHub¶
If the automation pubkey is attached to the GitHub user listed in
common_github_ssh_users, the next successful ansible-pull / common role
run will deploy it like your other operator keys. This is optional and has
broader blast radius than a dedicated per-host key.
Verification matrix (when you return)¶
Run these in order; all should succeed before you declare “done”.
| # | Where | Command | Expect |
|---|---|---|---|
| 1 | Your terminal | ssh -o BatchMode=yes infra-services 'echo human-ok' |
human-ok |
| 2 | Cursor terminal | ssh -o BatchMode=yes infra-services-cursor 'echo agent-ok' |
agent-ok |
| 3 | Cursor terminal | ssh -o ConnectTimeout=10 infra-services-cursor 'hostname' |
infra-services (or VM hostname) |
If 1 fails: 1Password / agent / Match Host — not a server problem.
If 2 fails but 1 works: missing cursor-infra private key in the
path Cursor uses, wrong IdentityFile, or pubkey not in
authorized_keys.
Troubleshooting¶
Permission denied (publickey) only from Cursor¶
- Confirm
Test-Path $env:USERPROFILE\.ssh\cursor-infra(private) is true in PowerShell inside Cursor, not only in WSL or another profile. - Confirm
IdentityAgent noneis set oninfra-services-cursorso OpenSSH does not skip your file key while chasing an empty agent.
Hangs instead of failing¶
- Remove interactive prompts: use
BatchMode=yesin tests. - Add
-o ConnectTimeout=10so stuck TCP does not block forever.
Too many authentication failures¶
- Keep
IdentitiesOnly yeson both stanzas. - Do not use bare
ssh someone@192.168.6.17for automation — useinfra-services-cursorso only the intended key is offered.
Policy: what agents should use¶
For any automated command in this repo’s context (Cursor agent, scripts), use:
Reserve ssh infra-services for you.
Optional: document for Cursor rules¶
If you want the agent to default correctly in this workspace, add a short rule:
“For SSH to infra-services from tools, use Host infra-services-cursor, not
the raw IP.” (Optional — this runbook is the source of truth.)