ssh: add an ssh-dev feature and keep host keys on /var for scarthgap - #276
Conversation
The device presents a new SSH host key on every boot. Every client's known_hosts breaks on every reboot, and `avocado container dev up` bootstraps the device over SSH, so a session cannot reconnect to a device that has rebooted - which is the normal case for a fleet. oe-core's read_only_rootfs_hook finds no pre-generated key in /etc/ssh and concludes the device cannot keep one, so it points sshd at sshd_config_readonly with keys under /var/run/ssh, on tmpfs. That inference is right for a stateless image and wrong for this one: the rootfs is read-only but /var is writable and persistent, which is a third case oe-core does not model. Its choice is between shipping a key in the image, which we will not do, and having no key survive at all. The shipped sshd_config already points HostKey at /var/lib/ssh, so only the override needs undoing. sshd_check_keys sources /etc/default/ssh as shell and its generate_key does mkdir -p on the key directory, so appending the persistent values is enough - keys are generated once on first boot and reused after. This runs from IMAGE_PREPROCESS_COMMAND rather than ROOTFS_POSTPROCESS_COMMAND, and that is the whole difficulty. A recipe-level append to the latter does not order after the entry rootfs-postcommands.bbclass contributes: measured here, read_only_rootfs_hook runs last of the three. Placed there this function ran before oe-core had created the file, its guard skipped, and the volatile values were written afterwards - a silent no-op that inspected as a working patch. Verified by reading the produced rootfs and sourcing the result rather than by reading the recipe. Confirmed on an FRDM-IMX93 running the wrynose BSP: the ed25519 host key fingerprint is unchanged across a reboot that changed /proc/sys/kernel/ random/boot_id, so the key is being reused rather than regenerated. Signed-off-by: Javier Tia <javier@peridio.com>
Getting a shell on a freshly flashed board took a bespoke sequence per
board: there was no way to ask for SSH access at build time, and the
image as built could not serve SSH at all even once openssh was present.
Two independent gaps, hence two pieces.
The host keys were unreachable. sshd resolves HostKey to ${sysconfdir}/ssh,
which on this image is the read-only rootfs, so sshd_check_keys cannot
create one: the unit fails, sshd starts keyless, and every client is
dropped at key exchange with "Connection reset by peer" - a symptom that
reads like a network fault rather than a missing file. Point HostKey at
/var/lib/ssh, which is writable and persistent, so the key survives a
reboot. A drop-in cannot do this: HostKey accumulates rather than
replaces, so the unreachable paths would remain in the list.
The bbappend branches on whether the recipe wrote those lines at all, and
that is what makes the same file correct on both openssh versions in the
layer. 10.3p1 writes them from OPENSSH_HOST_KEY_DIR, one per key type
enabled in PACKAGECONFIG, so rewriting in place preserves those gates.
9.6p1, which is what scarthgap carries, leaves them commented and
rewrites only sshd_config_readonly, so sshd runs on its compiled-in
defaults and a sed matches nothing - there the keys have to be named.
Either branch alone fails silently on the other version.
This is complementary to the SYSCONFDIR=/var/lib/ssh line the preceding
commit appends to /etc/default/ssh, not a duplicate of it. SYSCONFDIR
only decides where sshd_check_keys does its mkdir -p, while the key paths
come from `sshd -G` reading HostKey - which is why that append alone
still wrote keys to the read-only path. The gap has been invisible
because the sshd-dev extension ships an entire sshd_config of its own
with the right paths, so only an image carrying base-image openssh hits
it.
The feature itself is a kas overlay so any board can ask for it:
bakar build .../machine/<board>.yml:.../feature/ssh-dev.yml
Deliberately not an AVOCADO_FEATURE_GROUPS entry. A feature group
populates the feed - `networking` builds the openssh RPMs and stops
there, because the base image is minimal by design and gains capability
through extensions. That is correct for a product and useless for a
bench board, where the point is to have a shell before any extension has
been installed. ROOTFS_IMAGE_EXTRA_INSTALL is the seam the image recipe
already leaves for putting a package in the base rootfs.
Console and SSH need separate switches: AVOCADO_DEV_ROOT_LOGIN rewrites
`root:*:` to `root::` at package build, and oe-core's
allow-empty-password rewrites nullok_secure to nullok across pam.d at
rootfs assembly. Without the second, an empty password authenticates on
the console and is refused over SSH, because nullok_secure admits an
empty password only from a tty listed in /etc/securetty.
Verified on an FRDM-IMX93 against the wrynose BSP, where this same file
produced a reachable sshd and a host key that survived a reboot. Not
built or booted on scarthgap - the 9.6p1 branch above was derived by
reading that recipe's do_install, not by running it.
Signed-off-by: Javier Tia <javier@peridio.com>
0cfa25e to
5635ac1
Compare
This has been carried as an untracked file on the bench for the whole Jetson device-tree-overlay bring-up, because a stock image sets root's password field to `*` and /proc/device-tree - the only thing that proves an overlay reached the running kernel - is readable only from a shell on the board. An unshared file means the next person rediscovers both the toggle and the feed interaction below. ssh-dev.yml already sets the same toggle, so this is not the only way to get a login. It is the right one for a UART-attached board: ssh-dev appends sshd, keygen and sftp-server to the base rootfs so oe-core's image features have a config to edit, which on a board reached over a serial cable buys a larger image and a network service and nothing else. The PR bump ships commented out rather than active. Demoting the version-going-backwards check is not sufficient when the feed already holds a higher-PR avocado-users: dnf resolves to the older package, the image keeps `root:*`, and the build stays green - a failure indistinguishable from the feature not working. The value that fixes that depends on the reader's feed rather than on this file, so a fixed one would be correct once and wrong after. The debt marker names the condition that should replace it with a derived PR. Signed-off-by: Javier Tia <javier@peridio.com>
Both fragments need AVOCADO_DEV_ROOT_LOGIN, and two overlays setting the same variable independently drift - the second one to change is the one nobody notices. Include the fragment instead, the way containers.yml pulls its layer through virtualization.yml rather than pinning it twice. The include also closes a gap ssh-dev had on its own. Flipping the toggle re-signs avocado-users' do_install, so the PR service can hand back a revision below one already in the feed; packagedata then fails "version went backwards", and if the feed holds the older package at a higher revision dnf resolves to it and the image keeps root:*. ssh-dev flips the same toggle and carried none of that handling, so it was one stale feed away from a green build that cannot be logged into - over SSH or on the console. Including a fragment a config already pulls in directly is harmless: kas keys local_conf_header by section name, so the section resolves once rather than being emitted twice. Signed-off-by: Javier Tia <javier@peridio.com>
Any image built with the networking feature installs openssh-sshd, and
that sshd cannot start. It listens on 22, then every connection dies at
kex_exchange_identification, and journalctl -u sshd@ reports no entries
at all.
There is no sshd account. oe-core's openssh creates one through the
useradd class, declaring USERADD_PARAM:${PN}-sshd = "--system
--no-create-home --home-dir /var/run/sshd --shell /bin/false
--user-group sshd". This recipe then installs a fixed /etc/passwd and
/etc/group over the top, which discards it along with any other
useradd-created system user. OpenSSH refuses to run without its
privilege-separation account and exits before writing a banner or a log
line, so the unit looks healthy and the journal has nothing - which is
what makes this expensive to diagnose rather than merely broken.
Add the account, mirroring that USERADD_PARAM's home directory and shell
so the two do not drift. uid and gid 992 are free here and stay in the
system range.
Scoped to the account alone. #276 already carries the host-key work for
this branch - installing openssh-sshd and moving the keys onto /var - and
this is the piece it does not have. The two are independent: a host key
in a writable directory still gets no sshd without the account, and the
account alone still leaves the key on a read-only path, so neither
supersedes the other and the order they merge in does not matter.
The clobber itself is left alone and marked as debt. The real fix is to
stop shipping a whole passwd and pin only the uid/gid this distro cares
about, letting useradd own the rest - a change to how every Avocado image
gets its users, which wants its own verification rather than riding in
here.
Found on the wrynose line while diagnosing an sshd that reported
"Finished OpenSSH Key Generation" and still dropped every connection.
Adding the account there produced sshd:x:992:992 in the built rootfs and
a running server; scarthgap reaches the same failure by the same route,
since it ships the same avocado-users and installs openssh through
packagegroup-avocado-feature-networking.
|
Heads-up from taking this to hardware: the host-key work here is necessary but not sufficient on its own — sshd still will not start, because there is no
#304 adds the account against this branch. The two changes are independent and neither supersedes the other — a key in a writable directory still gets no sshd without the account, and the account alone still leaves the key on a read-only path — so merge order does not matter, but sshd will not come up until both have landed. Flagging it mainly so this PR does not read as not-working when validated on its own. |
Any image built with the networking feature installs openssh-sshd, and
that sshd cannot start. It listens on 22, then every connection dies at
kex_exchange_identification, and journalctl -u sshd@ reports no entries
at all.
There is no sshd account. oe-core's openssh creates one through the
useradd class, declaring USERADD_PARAM:${PN}-sshd = "--system
--no-create-home --home-dir /var/run/sshd --shell /bin/false
--user-group sshd". This recipe then installs a fixed /etc/passwd and
/etc/group over the top, which discards it along with any other
useradd-created system user. OpenSSH refuses to run without its
privilege-separation account and exits before writing a banner or a log
line, so the unit looks healthy and the journal has nothing - which is
what makes this expensive to diagnose rather than merely broken.
Add the account, mirroring that USERADD_PARAM's home directory and shell
so the two do not drift. uid and gid 992 are free here and stay in the
system range.
Scoped to the account alone. #276 already carries the host-key work for
this branch - installing openssh-sshd and moving the keys onto /var - and
this is the piece it does not have. The two are independent: a host key
in a writable directory still gets no sshd without the account, and the
account alone still leaves the key on a read-only path, so neither
supersedes the other and the order they merge in does not matter.
The clobber itself is left alone and marked as debt. The real fix is to
stop shipping a whole passwd and pin only the uid/gid this distro cares
about, letting useradd own the rest - a change to how every Avocado image
gets its users, which wants its own verification rather than riding in
here.
Found on the wrynose line while diagnosing an sshd that reported
"Finished OpenSSH Key Generation" and still dropped every connection.
Adding the account there produced sshd:x:992:992 in the built rootfs and
a running server; scarthgap reaches the same failure by the same route,
since it ships the same avocado-users and installs openssh through
packagegroup-avocado-feature-networking.
mobileoverlord
left a comment
There was a problem hiding this comment.
Approving. Verified beyond CI, which only parses the container-sdk config and never loads kas/feature/*:
- Resolved
machine/imx93-frdm.yml:feature/ssh-dev.ymlwithkas dumpon a merge of this branch onto current scarthgap. Bothfeature/dev-root-loginandfeature/ssh-devland in local_conf_header and meta-avocado-distro is in the layer set, so the include works. On the branch as-is it cannot resolve because its base still pins the orphaned oe-core 06b1c4f; that goes away on merge. - Traced the 9.6p1 path: the shipped sshd_config keeps
#HostKeycommented, so the bbappend takes the printf branch. read_only_rootfs_hook runs in do_rootfs and the IMAGE_PREPROCESS append runs in do_image afterwards, soSSHD_OPTS=is the last assignment for both sshd_check_keys (sourced) and sshd@.service (EnvironmentFile). /var is its own partition in fstab.
Non-blocking:
- This diverges from wrynose, which points sshd_config_readonly at /var/lib/ssh and leaves SSHD_OPTS alone. 9.6p1 has no OPENSSH_HOST_KEY_DIR_READONLY_CONFIG, but the same shape is one sed on sshd_config_readonly in the bbappend, with no image hook and no ordering argument. Worth doing if this file is touched again.
return 0inside ado_install:appendbody returns from the whole do_install, so a later append from another layer would be skipped in the no-sshd_config case. An if/else around the two branches avoids that.- The include precedent on scarthgap is kas/base.yml -> kas/vendor/oe.yml; containers.yml here does not include virtualization.yml.
- The feed's openssh-sshd RPM now ships HostKey lines pointing at /var/lib/ssh, so extensions using the packaged sshd_config on the read-only rootfs get working keygen too.
A freshly flashed board has no way in.
/etc/shadowshipsroot:*:, and evenonce openssh is installed the image cannot serve SSH at all, so getting a shell
has meant a bespoke sequence per board.
Two commits, two independent gaps.
Host keys were unreachable. sshd resolves
HostKeyto${sysconfdir}/ssh,which on this image is the read-only rootfs.
sshd_check_keyscannot create akey there, so the unit fails, sshd starts keyless, and every client is dropped
at key exchange with
Connection reset by peer- a symptom that reads like anetwork fault rather than a missing file. On top of that, oe-core's
read_only_rootfs_hooksees no pre-generated key in/etc/sshand points sshdat
sshd_config_readonlywith keys on tmpfs, so the device presents a new hostkey on every boot. Both halves are needed: clearing
SSHD_OPTSalone stillwrites keys to the read-only path, and repointing
HostKeyalone is ignoredwhile sshd is running the readonly config.
No build-time switch.
ssh-devis a kas overlay, so any board can ask:Deliberately not an
AVOCADO_FEATURE_GROUPSentry. A feature group populatesthe feed -
networkingbuilds the openssh RPMs and stops there, because thebase image is minimal by design and gains capability through extensions. That
is right for a product and wrong for a bench board, where the point is to have
a shell before any extension has been installed.
ROOTFS_IMAGE_EXTRA_INSTALLis the seam the image recipe already leaves for that.
Dev only - it accepts root with an empty password. The feature file says so at
the top.
Same files as the wrynose version
Byte-identical to #275. The bbappend branches on whether the recipe wrote the
HostKeylines at all, so one file is correct on both openssh versions in thelayer: 10.3p1 writes them from
OPENSSH_HOST_KEY_DIRone per key type enabledin
PACKAGECONFIG, so rewriting in place preserves those gates; 9.6p1, whichscarthgap carries, leaves them commented and rewrites only
sshd_config_readonly, so sshd runs on its compiled-in defaults and a sedmatches nothing. Either branch alone fails silently on the other version - the
sed is a no-op on 9.6p1, and an unconditional explicit list would resurrect a
key type
PACKAGECONFIGhad turned off on 10.3p1.Verification
Verified on an FRDM-IMX93 against the wrynose BSP: sshd reachable over the
network, and the ed25519 host key fingerprint unchanged across a reboot that
changed
boot_id, so the key is reused rather than regenerated.Not built or booted on scarthgap. The 9.6p1 branch above was derived by reading
that recipe's
do_install, not by running it, so the parse and the producedsshd_configare unconfirmed on this branch.Added 2026-08-15: a console-only sibling, and a dedupe
Two extra commits, folded in here rather than opened as their own PR since they
touch the same file and the same base.
kas/feature/dev-root-login.ymlis the console-only counterpart of thisfragment - the toggle and nothing else. It had been carried as an untracked file
on the bench through the whole Jetson device-tree-overlay bring-up, because a
stock image sets root's password field to
*and/proc/device-tree, the onlything that proves an overlay reached the running kernel, is readable only from a
shell on the board.
It is a separate fragment rather than a flag on this one because ssh-dev appends
openssh-sshd,openssh-keygenandopenssh-sftp-serverto the base rootfs sooe-core's image features have a config to edit. On a board reached over a serial
cable that is a larger image and a network service for no gain. Reach for
ssh-dev when you need a shell over the network; reach for dev-root-login when
you need a login prompt and nothing else.
ssh-dev.ymlnow includes it rather than settingAVOCADO_DEV_ROOT_LOGINagain, the way
containers.ymlpulls its layer throughvirtualization.yml.That dedupe closes a gap this PR had on its own, which is the part worth a
reviewer's attention. Flipping the toggle re-signs
avocado-users'do_install,so the PR service can hand back a revision below one already published;
packagedatathen failsversion went backwards, and if the feed holds theolder package at a higher revision, dnf resolves to it and the image keeps
root:*. The build stays green and the board cannot be logged into - over SSHor on the console - and the symptom is indistinguishable from the feature not
working. This PR flipped the same toggle and carried none of that handling.
The QA relaxation now inherited from the new fragment covers it. The
PRbumpthat is sometimes additionally needed ships commented out, with a
devtool-debt:marker: the right value depends on the reader's feed rather thanon the file, so a fixed one would be correct once and wrong after.
Not resolved through a real
kas dump- the include is YAML-valid and matchesthe
containers.ymlprecedent exactly, but it has not been run through kas onthis branch. Worth a reviewer's eye given every ssh-dev build now depends on it
resolving.
The equivalent change is not yet on the wrynose side (#275); it should follow
once the shape is agreed here.