1. Home
  2. Blog
  3. Part 3: SSH Access, User Accounts, and Secrets Management

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

  • salt is random data

  • hash is 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 extrausers class

  • 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

This article is part of a series on building embedded Linux systems with Yocto and KAS. Questions or feedback? Open an issue on GitHub.