## v20
+- We don't use the user's SSH public/private keypair anymore for
+ `mkosi ssh` but instead use a separate key pair which can be
+ generated by `mkosi genkey`. Users using `mkosi ssh` will have to run
+ `mkosi genkey` once to generate the necessary files to keep
+ `mkosi ssh` working.
- We don't automatically set `--offline=no` anymore when we detect the
`Subvolumes=` setting is used in a `systemd-repart` partition
definition file. Instead, use the new `RepartOffline` option to
kernel-core
mtools
openssh-clients
+ openssh-server
openssl
python3-cryptography
qemu-kvm-core
libtss2-dev
mtools
openssh-client
+ openssh-server
openssl
ovmf
pacman-package-manager
kernel-kvmsmall
mtools
openssh-clients
+ openssh-server
openssl
ovmf
pesign
fork_and_wait,
init_mount_namespace,
run,
+ run_openssl,
)
from mkosi.state import MkosiState
from mkosi.tree import copy_tree, install_tree, move_tree, rmtree
-from mkosi.types import _FILE, CompletedProcess, PathString
+from mkosi.types import PathString
from mkosi.util import (
INVOKING_USER,
chdir,
)
-def run_openssl(args: Sequence[PathString], stdout: _FILE = None) -> CompletedProcess:
- with tempfile.NamedTemporaryFile(prefix="mkosi-openssl.cnf") as config:
- return run(["openssl", *args], stdout=stdout, env=dict(OPENSSL_CONF=config.name))
-
-
def certificate_common_name(certificate: Path) -> str:
output = run_openssl([
"x509",
from mkosi.distributions import Distribution, detect_distribution
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SHELL, Style, die
from mkosi.pager import page
-from mkosi.run import run
+from mkosi.run import run, run_openssl
from mkosi.types import PathString, SupportsRead
from mkosi.util import INVOKING_USER, StrEnum, chdir, flatten, is_power_of_2
from mkosi.versioncomp import GenericVersion
tools_tree_packages: list[str]
runtime_trees: list[ConfigTree]
runtime_size: Optional[int]
+ ssh_key: Optional[Path]
+ ssh_certificate: Optional[Path]
# QEMU-specific options
qemu_gui: bool
parse=config_parse_bytes,
help="Grow disk images to the specified size before booting them",
),
+ MkosiConfigSetting(
+ dest="ssh_key",
+ metavar="PATH",
+ section="Host",
+ parse=config_make_path_parser(secret=True),
+ paths=("mkosi.key",),
+ help="Private key for use with mkosi ssh in PEM format",
+ ),
+ MkosiConfigSetting(
+ dest="ssh_certificate",
+ metavar="PATH",
+ section="Host",
+ parse=config_make_path_parser(),
+ paths=("mkosi.crt",),
+ help="Certificate for use with mkosi ssh in X509 format",
+ ),
MkosiConfigSetting(
dest="qemu_gui",
metavar="BOOL",
if "firstboot.locale" not in creds:
creds["firstboot.locale"] = "C.UTF-8"
- if (
- args.ssh and
- "ssh.authorized_keys.root" not in creds and
- "SSH_AUTH_SOCK" in os.environ and shutil.which("ssh-add")
- ):
- key = run(
- ["ssh-add", "-L"],
- stdout=subprocess.PIPE,
- env=os.environ,
- check=False,
- ).stdout.strip()
- if key:
- creds["ssh.authorized_keys.root"] = key
+ if "ssh.authorized_keys.root" not in creds:
+ if args.ssh_certificate:
+ pubkey = run_openssl(["x509", "-in", args.ssh_certificate, "-pubkey", "-noout"],
+ stdout=subprocess.PIPE).stdout.strip()
+ sshpubkey = run(["ssh-keygen", "-f", "/dev/stdin", "-i", "-m", "PKCS8"],
+ input=pubkey, stdout=subprocess.PIPE).stdout.strip()
+ creds["ssh.authorized_keys.root"] = sshpubkey
+ elif args.ssh:
+ die("Ssh= is enabled but no SSH certificate was found",
+ hint="Run 'mkosi genkey' to automatically create one")
return creds
Tools Tree Packages: {line_join_list(config.tools_tree_packages)}
Runtime Trees: {line_join_tree_list(config.runtime_trees)}
Runtime Size: {format_bytes_or_none(config.runtime_size)}
+ SSH Signing Key: {none_to_none(config.ssh_key)}
+ SSH Certificate: {none_to_none(config.ssh_certificate)}
QEMU GUI: {yes_no(config.qemu_gui)}
QEMU CPU Cores: {config.qemu_smp}
if config.qemu_vsock_cid == QemuVsockCID.auto:
die("Can't use ssh verb with QemuVSockCID=auto")
+ if not config.ssh_key:
+ die("SshKey= must be configured to use 'mkosi ssh'",
+ hint="Use 'mkosi genkey' to generate a new SSH key and certificate")
+
if config.qemu_vsock_cid == QemuVsockCID.hash:
cid = hash_to_vsock_cid(hash_output(config))
else:
cid = config.qemu_vsock_cid
- cmd = [
+ cmd: list[PathString] = [
"ssh",
+ "-i", config.ssh_key,
"-F", "none",
# Silence known hosts file errors/warnings.
"-o", "UserKnownHostsFile=/dev/null",
: When the image is built with the `Ssh=yes` option, this command
connects to a booted virtual machine (`qemu`) via SSH. Make sure to
- run `mkosi ssh` with the same config as `mkosi build` was run with so
- that it has the necessary information available to connect to the
- running virtual machine via SSH. Any arguments passed after the `ssh`
- verb are passed as arguments to the `ssh` invocation. To connect to a
- container, use `machinectl login` or `machinectl shell`.
+ run `mkosi ssh` with the same config as `mkosi build` so that it has
+ the necessary information available to connect to the running virtual
+ machine via SSH. Specifically, the SSH private key from the `SshKey=`
+ setting is used to connect to the virtual machine. Use `mkosi genkey`
+ to automatically generate a key and certificate that will be picked up
+ by mkosi. Any arguments passed after the `ssh` verb are passed as
+ arguments to the `ssh` invocation. To connect to a container, use
+ `machinectl login` or `machinectl shell`.
`journalctl`
option and running the image using `mkosi qemu`, the `mkosi ssh`
command can be used to connect to the container/VM via SSH. Note that
you still have to make sure openssh is installed in the image to make
- this option behave correctly. mkosi will automatically provision the
- user's public SSH key into the image using the
- `ssh.authorized_keys.root` credential if it can be retrieved from a
- running SSH agent. To access images booted using `mkosi boot`, use
- `machinectl`.
+ this option behave correctly. Run `mkosi genkey` to automatically
+ generate an X509 certificate and private key to be used by mkosi to
+ enable SSH access to any virtual machines via `mkosi ssh`. To access
+ images booted using `mkosi boot`, use `machinectl`.
### [Validation] Section
Additionally, the suffixes `K`, `M` and `G` can be used to specify a
size in kilobytes, megabytes and gigabytes respectively.
+`SshKey=`, `--ssh-key=`
+
+: Path to the X509 private key in PEM format to use to connect to a
+ virtual machine started with `mkosi qemu` and built with the `Ssh=`
+ option enabled via the `mkosi ssh` command. If not configured and
+ `mkosi.key` exists in the working directory, it will automatically be
+ used for this purpose. Run `mkosi genkey` to automatically generate
+ a key in `mkosi.key`.
+
+`SshCertificate=`, `--ssh-certificate=`
+
+: Path to the X509 certificate in PEM format to provision as the SSH
+ public key in virtual machines started with `mkosi qemu`. If not
+ configured and `mkosi.crt` exists in the working directory, it will
+ automatically be used for this purpose. Run `mkosi genkey` to
+ automatically generate a certificate in `mkosi.crt`.
+
## Specifiers
The current value of various settings can be accessed when parsing
import signal
import subprocess
import sys
+import tempfile
import threading
from collections.abc import Awaitable, Collection, Iterator, Mapping, Sequence
from pathlib import Path
raise self.exc.get_nowait()
except queue.Empty:
pass
+
+
+def run_openssl(args: Sequence[PathString], stdout: _FILE = None) -> CompletedProcess:
+ with tempfile.NamedTemporaryFile(prefix="mkosi-openssl.cnf") as config:
+ return run(["openssl", *args], stdout=stdout, env=dict(OPENSSL_CONF=config.name))
"SourceDateEpoch": 12345,
"SplitArtifacts": true,
"Ssh": false,
+ "SshCertificate": "/path/to/cert",
+ "SshKey": null,
"Timezone": null,
"ToolsTree": null,
"ToolsTreeDistribution": null,
source_date_epoch = 12345,
split_artifacts = True,
ssh = False,
+ ssh_certificate = Path("/path/to/cert"),
+ ssh_key = None,
timezone = None,
tools_tree = None,
tools_tree_distribution = None,