Part 3: SSH Access, User Accounts, and Secrets Management
In Part 2, we added QEMU support and created a custom layer. The system boots and runs our code, but we still can't access it remotely and there's no authentication. This part adds SSH access, proper user accounts, and a secrets management strategy for production builds.
Adding SSH Access
Yocto provides two SSH server options:
-
Dropbear - Minimal SSH server, ideal for resource-constrained systems (~110KB)
-
OpenSSH - Full-featured SSH implementation, larger footprint (~1MB+)
For embedded systems, Dropbear is usually sufficient. It doesn't include an SFTP server, but we can add openssh-sftp-server (~200KB) alongside it for file transfers.
Configuring SSH in KAS
Update kas/include/yoseli-apps.yml:
The ssh-server-dropbear feature adds the Dropbear SSH server and generates host keys on first boot. Adding openssh-sftp-server enables SFTP file transfers.
If you prefer OpenSSH instead (includes SFTP by default):
EXTRA_IMAGE_FEATURES += "ssh-server-openssh"
Testing SSH in QEMU
Build and launch QEMU:
$> kas build kas/project_qemu.yml
$> kas shell kas/project_qemu.yml -c "runqemu nographic slirp"
The slirp option sets up user-mode networking with port forwarding: host port 2222 forwards to QEMU port 22. From another terminal:
$> ssh -p 2222 root@127.0.0.1
With debug-tweaks enabled, root login works without a password:
__ __ _ _
\ \ / /__ ___ ___| (_)
\ V / _ \/ __|/ _ \ | |
| | (_) \__ \ __/ | |
|_|\___/|___/\___|_|_|
Custom Embedded Linux - Built with Yocto & KAS
https://www.yoseli.org
root@qemuarm64:~# hello
Hello from Yoseli!
This is your first custom Yocto recipe.
$> sftp -P 2222 root@127.0.0.1
sftp> ls /etc
Creating User Accounts
For production systems, you need proper user accounts with passwords. Yocto's extrausers class handles this at image build time.
The extrausers Class
The extrausers class runs standard Linux user management commands (usermod, groupadd) during image creation. This happens in a fakeroot environment,
so the changes are baked into the rootfs.
Generating Password Hashes
Never put plaintext passwords in recipes. Generate hashes with mkpasswd:
$> mkpasswd -m sha-512
Password:
$6$randomsalt$longhashstring...
You can also pass the password directly (useful for scripts, but be careful with shell history):
$> mkpasswd -m sha-512 mypassword
$6$6PFHTmY0Hru3$3XXHxJMpBHhFkdzIEtcM8txP6rTtSlRmjdx7.asGc6P54peUL8WPxU03V3s4Zl.6x5KxxbRmDz23QOI5gfvet/
The output looks like $6$salt$hash where:
-
$6$indicates SHA-512 -
saltis random data -
hashis the actual password hash
Adding Users via bbappend
Create meta-yoseli-apps/recipes-core/images/core-image-minimal.bbappend:
# Add user accounts to the image
inherit extrausers
# Password hash - override via secrets.yml or environment variable
# Default password is "yoseli" (for development only!)
# Generate your own with: mkpasswd -m sha-512 yourpassword
YOSELI_PASSWORD ?= "\$6\$16Xfrd9uDJdj4SKx\$8R2G/GZuIAh6K1pKMIk9j6ZtbiNXUam3vo9M8n/5Ljyy5M2B9KLNIWZ9x8rkCOvoeCcbnMntaNhDD2jXnsX1I1"
EXTRA_USERS_PARAMS = "\
useradd -m -s /bin/sh -G wheel yoseli; \
usermod -p '${YOSELI_PASSWORD}' yoseli; \
usermod -p '${YOSELI_PASSWORD}' root; \
"
Key points:
-
`?=` - Default assignment, can be overridden by secrets.yml or environment as we will see later
-
`\$` - Dollar signs must be escaped in BitBake
-
`-m` - Create home directory
-
`-s /bin/sh` - Set login shell
-
`-G wheel` - Add to wheel group (for sudo, if installed)
-
`usermod -p` - Set password for existing users (like root)
Testing User Login
$> kas build kas/project_qemu.yml
$> kas shell kas/project_qemu.yml -c "runqemu nographic slirp"
From another terminal:
$> ssh -p 2222 yoseli@127.0.0.1
yoseli@127.0.0.1's password: ****** # password is "yoseli"
__ __ _ _
\ \ / /__ ___ ___| (_)
\ V / _ \/ __|/ _ \ | |
| | (_) \__ \ __/ | |
|_|\___/|___/\___|_|_|
Custom Embedded Linux - Built with Yocto & KAS
https://www.yoseli.org
qemuarm64:~$ whoami
yoseli
qemuarm64:~$ id
uid=1000(yoseli) gid=1000(yoseli) groups=80(wheel),1000(yoseli)
Secrets Management
The example above has a problem: the password hash is committed to git. Anyone with access to the repository can see it. For production, we need a better approach.
The Problem with Hardcoded Secrets
Secrets in source control are a security risk:
-
Password hashes can be cracked offline
-
API tokens give unauthorized access
-
SSH keys compromise the entire system
-
Add your own issue there :-)
Even in private repositories, secrets should be separated from code.
Strategy 1: Local Secrets File
Create a secrets file that stays on the build machine:
Let's create `kas/include/secrets.yml`:
header:
version: 18
local_conf_header:
secrets: |
YOSELI_PASSWORD = "\$6\$salt\$hash"
# Add other secrets here
# MENDER_TENANT_TOKEN = "..."
# WIFI_PASSWORD = "..."
Add kas/include/secrets.yml to .gitignore. Then, update your bbappend to use the variable:
inherit extrausers
EXTRA_USERS_PARAMS = "\
useradd -m -s /bin/sh -G wheel yoseli; \
usermod -p '${YOSELI_PASSWORD}' yoseli; \
usermod -p '${YOSELI_PASSWORD}' root; \
Include it in your KAS config:
header:
version: 18
includes:
- include/common.yml
- include/qemuarm64.yml
- include/yoseli-apps.yml
- include/secrets.yml
machine: qemuarm64
You can create a template for others, like kas/include/secrets.yml.example:
header:
version: 18
# Copy this file to secrets.yml and fill in your values
# DO NOT commit secrets.yml to git
local_conf_header:
secrets: |
# Generate with: mkpasswd -m sha-512 yourpassword
YOSELI_PASSWORD = "\$6\$YOUR_SALT\$YOUR_HASH"
Strategy 2: Environment Variables
For CI/CD pipelines, use environment variables. Create kas/include/secrets-env.yml:
header:
version: 18
env:
YOSELI_PASSWORD:
local_conf_header:
secrets: |
YOSELI_PASSWORD = "${YOSELI_PASSWORD}"
Then, in your CI pipeline:
$> export YOSELI_PASSWORD='\$6\$salt\$hash'
$> kas build kas/project_qemu.yml
It obviously can be used for local dev too ;-).
Strategy 3: Separate Development and Production
Sometimes, you want the easy way while developing. In this case, use different KAS configs for development and production.
You will then have two files: kas/project_qemu.yml (development), and kas/project_qemu_prod.yml (production):
header:
version: 18
includes:
- include/common.yml
- include/qemuarm64.yml
- include/yoseli-apps.yml
machine: qemuarm64
local_conf_header:
debug: |
EXTRA_IMAGE_FEATURES += "debug-tweaks"
header:
version: 18
env:
YOSELI_PASSWORD:
local_conf_header:
secrets: |
YOSELI_PASSWORD = "${YOSELI_PASSWORD}"
The development config uses debug-tweaks for easy testing. The production config requires the secrets file and enforces password authentication.
Removing debug-tweaks
The debug-tweaks feature enables:
-
Empty root password
-
Post-install script logging
-
Debug packages
For production builds, remove it entirely. Without debug-tweaks, users must authenticate with proper passwords.
Complete Layer Structure
After Part 3, the structure shoud look like:
meta-yoseli-apps/
├── conf/
│ └── layer.conf
├── COPYING.MIT
├── recipes-core/
│ ├── base-files/
│ │ ├── base-files_%.bbappend
│ │ └── files/
│ │ └── poky/
│ │ └── motd
│ └── images/
│ └── core-image-minimal.bbappend
└── recipes-example/
└── hello/
├── hello_1.0.bb
└── files/
└── hello.c
Essential Commands
We have a few commands to remember:
# Build the image
$> kas build kas/project_qemu.yml
# Launch QEMU with networking
$> kas shell kas/project_qemu.yml -c "runqemu nographic slirp"
# SSH to QEMU (from another terminal)
$> ssh -p 2222 root@127.0.0.1
$> ssh -p 2222 yoseli@127.0.0.1
# SFTP to QEMU
$> sftp -P 2222 root@127.0.0.1
# Generate password hash
$> mkpasswd -m sha-512
What We Built
In this part, we:
-
Added SSH access with Dropbear and SFTP support
-
Created user accounts using the
extrausersclass -
Implemented password-based authentication
-
Established a secrets management strategy
-
Separated development and production configurations
The full code is available in the yocto-blog-posts repository on the part3-ssh-users-secrets branch.
Next Steps
The system now has secure remote access. Part 4 will cover A/B updates with Mender in QEMU:
-
Why A/B updates matter for embedded systems
-
Setting up meta-mender
-
Testing the update flow in QEMU
-
Automatic rollback on failure