The gateway, tweed

You operate the welland gateway and want to know who can log in to tweed, as which account, with which key, and how its sshd is set. Logging in: Logging in.

tweed is not publicly addressable over IPv4. tweed.welland.mithis.com is split-horizon DNS (looked up 2026-09-29):

Resolver

A

AAAA

public (ns1/ns2.rollernet.us)

87.121.95.37, which is the upstream router, not tweed

2404:e80:a137:2100::1, 2404:e80:a137:9921::2 (tweed)

inside the site

10.99.21.2 (uplink), 10.21.0.1 (eth-local)

the same two

Which path reaches tweed’s sshd (checked 2026-09-29, after infra main 4de0b24 was deployed):

From

Use

Why

inside the site, or over the wg route

tweed.welland.mithis.com

resolves to 10.21.0.1 / 10.99.21.2, which reach tweed directly

the upstream router

10.99.21.2

the transit link

Ansible, from anywhere with IPv6

gw.welland.fpgas.online, IPv6 only

the inventory’s ansible_host; the name’s AAAA record is tweed, its A record is not

outside, IPv6

2404:e80:a137:2100::1

reaches tweed. 2404:e80:a137:9921::2 port 22 times out from outside, so use the address rather than the name, which also lists 9921::2

outside, IPv4 only

through the upstream router: -J to the site’s public IPv4 address, 87.121.95.37, then 10.99.21.2, if you have an account on the router

the public A record is the upstream router, not tweed (What a site needs from its upstream network)

tweed’s own firewall accepts SSH on every interface (roles/firewall).

Account

uid

sudo

authorized_keys

Managed by

ansible

1000

NOPASSWD (/etc/sudoers.d/ansible)

exactly the fpgas.online-ansible key, SHA256:D/6/i3vPET3EeQKtO0kv0y0YfukEVg7/ZIF7oW3U6yA (ED25519). Written exclusive, so a key added by hand is removed at the next converge

roles/automation_user, only where automation_user_manage: true (tweed and the CI VM). The Debian preseed creates the account on a fresh install

admin

1001

NOPASSWD (server_user_sudo: true)

exactly the keys published at https://github.com/mithro.keys and https://github.com/CarlFK.keys. Written exclusive, after every id has downloaded (see Where the keys come from)

roles/server_user (user_name: admin, server_user_ssh_import_ids = the operators’ ids). The account was videoteam until #141 renamed it in place (server_user_rename_from)

tim, carl

NOPASSWD (/etc/sudoers.d/<name>)

the GitHub keys of operators_accounts[].ssh_import_ids (gh:mithro, gh:CarlFK), added (not exclusive), minus ssh_public_keys_revoked

roles/operators

pi

none (the role deletes any /etc/sudoers.d/pi)

ssh_public_keys (static) plus the GitHub keys of ssh_imports (gh:CarlFK, gh:mithro), added (not exclusive), minus ssh_imports_revoked and ssh_public_keys_revoked

roles/jump

root

0

not managed by any role. Password locked (observed 2026-09-27)

sshd: PermitRootLogin prohibit-password

admin runs the site: gunicorn, daphne, uvicorn and the fleet consumer are User={{ user_name }} (roles/site). Its ~/.ssh/id_rsa keypair is the server-user key that every Pi trusts. It is generated by fixpi/tasks/userconf.yml (“Generate ssh keys for server user”, never overwritten) and read from fixpi_server_user_pubkey.

The pi jump account:

  • Its login shell is /bin/rbash with PATH locked to ~/bin, which holds only ssh and ssh-keyscan (jump_commands).

  • /etc/ssh/sshd_config.d/50-jump.conf does Match User pi → ForceCommand jump-shell (/usr/local/bin/jump-shell), which refuses every command except ssh and ssh-keyscan. It also sets AllowTcpForwarding yes, AllowAgentForwarding no, X11Forwarding no and PermitTunnel no.

  • It has its own keypair, /home/pi/.ssh/id_ed25519 (jump_ssh_key, #140). fixpi authorizes the public half for the Pis’ pi user, so ssh pi@10.21.S.P from inside the jump shell works without a password. ~/.ssh/config accepts new Pi host keys.

Retired accounts: piroot (the old chroot-shell provisioning login) is deleted by operators_retired_accounts, together with its sudoers file and /usr/local/bin/chroot-shell (#149). videoteam is now admin.

sshd

sshd_pubkey_only: true in tweed’s host_vars makes roles/sshd write /etc/ssh/sshd_config.d/00-pubkey-only.conf (#139):

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
PermitRootLogin prohibit-password

It sorts first, so it wins over 50-jump.conf and the main file. Before it writes the file, a lockout guard checks that the account Ansible is connected as, every operator and the jump account (sshd_pubkey_only_key_users) each have at least one key that ssh-keygen -l can parse. It does not check admin or root. If any account it checks has no such key, the converge fails. The connecting account is found with id -un with become switched off through the ansible_become variable, because tweed’s host_vars set ansible_become: true. It is then asserted to equal ansible_user (#162; before that fix the probe ran as root and the guard checked root’s keys). After writing, the role checks sshd -t and the effective sshd -T values for ansible, pi and root.

ps1.fpgas.online runs the same operators, jump and sshd roles. It does not run automation_user (automation_user_manage is false). Its sshd_pubkey_only is false, so sshd only makes sure the drop-in is absent, and its user_name is still videoteam.