1- //! SSH credential injection for bootc VMs
2- //!
3- //! Injects SSH public keys into VMs via systemd credentials using either SMBIOS
4- //! firmware variables (preferred) or kernel command-line arguments. Creates systemd
5- //! tmpfiles.d configuration to set up SSH access during VM boot.
1+ //! Systemd credential injection for bootc VMs
62//!
3+ //! Provides functions for injecting configuration into VMs via systemd credentials
4+ //! using SMBIOS firmware variables (preferred) or kernel command-line arguments.
5+ //! Supports SSH keys, mount units, environment configuration, and AF_VSOCK setup.
76
87use color_eyre:: Result ;
98
10- /// Generate SMBIOS credential string for root SSH access
11- ///
12- /// Creates a systemd credential for QEMU's SMBIOS interface. Preferred method
13- /// as it keeps credentials out of kernel command line and boot logs.
14- ///
15- /// Returns a string for use with `qemu -smbios type=11,value="..."`
16- pub fn smbios_cred_for_root_ssh ( pubkey : & str ) -> Result < String > {
17- let k = key_to_root_tmpfiles_d ( pubkey) ;
18- let encoded = data_encoding:: BASE64 . encode ( k. as_bytes ( ) ) ;
19- let r = format ! ( "io.systemd.credential.binary:tmpfiles.extra={encoded}" ) ;
20- Ok ( r)
21- }
22-
23- /// Generate kernel command-line argument for root SSH access
24- ///
25- /// Creates a systemd credential for kernel command-line delivery. Less secure
26- /// than SMBIOS method as credentials are visible in /proc/cmdline and boot logs.
27- ///
28- /// Returns a string for use in kernel boot parameters.
29- pub fn karg_for_root_ssh ( pubkey : & str ) -> Result < String > {
30- let k = key_to_root_tmpfiles_d ( pubkey) ;
31- let encoded = data_encoding:: BASE64 . encode ( k. as_bytes ( ) ) ;
32- let r = format ! ( "systemd.set_credential_binary=tmpfiles.extra:{encoded}" ) ;
33- Ok ( r)
34- }
35-
36- /// Convert SSH public key to systemd tmpfiles.d configuration
37- ///
38- /// Generates configuration to create `/root/.ssh` directory (0750) and
39- /// `/root/.ssh/authorized_keys` file (700) with the Base64-encoded SSH key.
40- /// Uses `f+~` to append to existing authorized_keys files.
41- pub fn key_to_root_tmpfiles_d ( pubkey : & str ) -> String {
42- let buf = data_encoding:: BASE64 . encode ( pubkey. as_bytes ( ) ) ;
43- format ! ( "d /root/.ssh 0750 - - -\n f+~ /root/.ssh/authorized_keys 700 - - - {buf}\n " )
44- }
45-
46- /// Generate SMBIOS credentials for STORAGE_OPTS configuration
47- ///
48- /// Creates a systemd unit that conditionally appends STORAGE_OPTS to /etc/environment
49- /// (for PAM sessions including SSH), plus a dropin to ensure it runs.
50- ///
51- /// Returns a vector with:
52- /// 1. The unit itself (systemd.extra-unit)
53- /// 2. A dropin for sysinit.target to pull in the unit
54- pub fn smbios_creds_for_storage_opts ( ) -> Result < Vec < String > > {
55- // Create systemd unit that conditionally appends to /etc/environment
56- let unit_content = r#"[Unit]
57- Description=Setup STORAGE_OPTS for bcvk
58- DefaultDependencies=no
59- Before=systemd-user-sessions.service
60-
61- [Service]
62- Type=oneshot
63- ExecStart=/bin/sh -c 'grep -q STORAGE_OPTS /etc/environment || echo STORAGE_OPTS=additionalimagestore=/run/host-container-storage >> /etc/environment'
64- RemainAfterExit=yes
65- "# ;
66- let encoded_unit = data_encoding:: BASE64 . encode ( unit_content. as_bytes ( ) ) ;
67- let unit_cred = format ! (
68- "io.systemd.credential.binary:systemd.extra-unit.bcvk-storage-opts.service={encoded_unit}"
69- ) ;
70-
71- // Create dropin for sysinit.target to pull in our unit
72- let dropin_content = "[Unit]\n Wants=bcvk-storage-opts.service\n " ;
73- let encoded_dropin = data_encoding:: BASE64 . encode ( dropin_content. as_bytes ( ) ) ;
74- let dropin_cred = format ! (
75- "io.systemd.credential.binary:systemd.unit-dropin.sysinit.target~bcvk-storage={encoded_dropin}"
76- ) ;
77-
78- Ok ( vec ! [ unit_cred, dropin_cred] )
79- }
80-
81- /// Generate tmpfiles.d lines for STORAGE_OPTS in systemd contexts
82- ///
83- /// Configures STORAGE_OPTS for:
84- /// - /etc/environment.d/: systemd user manager and user services
85- /// - /etc/systemd/system.conf.d/: system-level systemd services
86- pub fn storage_opts_tmpfiles_d_lines ( ) -> String {
87- concat ! (
88- "f /etc/environment.d/90-bcvk-storage.conf 0644 root root - STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n " ,
89- "d /etc/systemd/system.conf.d 0755 root root -\n " ,
90- "f /etc/systemd/system.conf.d/90-bcvk-storage.conf 0644 root root - [Manager]\\ nDefaultEnvironment=STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n "
91- ) . to_string ( )
92- }
93-
94- /// Generate SMBIOS credential string for AF_VSOCK systemd notification socket
95- ///
96- /// Creates a systemd credential that configures systemd to send notifications
97- /// via AF_VSOCK instead of the default Unix socket. This enables host-guest
98- /// communication for debugging VM boot sequences.
99- ///
100- /// Returns a string for use with `qemu -smbios type=11,value="..."`
101- pub fn smbios_cred_for_vsock_notify ( host_cid : u32 , port : u32 ) -> String {
102- format ! (
103- "io.systemd.credential:vmm.notify_socket=vsock-stream:{}:{}" ,
104- host_cid, port
105- )
106- }
107-
1089/// Convert a guest mount path to a systemd unit name
10910///
11011/// Systemd requires mount unit names to match the mount path with:
@@ -174,6 +75,7 @@ pub fn generate_mount_unit(virtiofs_tag: &str, guest_path: &str, readonly: bool)
17475/// 2. A dropin for local-fs.target that wants this mount unit
17576///
17677/// Returns a vector of SMBIOS credential strings
78+ #[ allow( dead_code) ]
17779pub fn smbios_creds_for_mount_unit (
17880 virtiofs_tag : & str ,
17981 guest_path : & str ,
@@ -199,6 +101,105 @@ pub fn smbios_creds_for_mount_unit(
199101 Ok ( vec ! [ mount_cred, dropin_cred] )
200102}
201103
104+ /// Generate SMBIOS credential string for AF_VSOCK systemd notification socket
105+ ///
106+ /// Creates a systemd credential that configures systemd to send notifications
107+ /// via AF_VSOCK instead of the default Unix socket. This enables host-guest
108+ /// communication for debugging VM boot sequences.
109+ ///
110+ /// Returns a string for use with `qemu -smbios type=11,value="..."`
111+ pub fn smbios_cred_for_vsock_notify ( host_cid : u32 , port : u32 ) -> String {
112+ format ! (
113+ "io.systemd.credential:vmm.notify_socket=vsock-stream:{}:{}" ,
114+ host_cid, port
115+ )
116+ }
117+
118+ /// Generate SMBIOS credentials for STORAGE_OPTS configuration
119+ ///
120+ /// Creates a systemd unit that conditionally appends STORAGE_OPTS to /etc/environment
121+ /// (for PAM sessions including SSH), plus a dropin to ensure it runs.
122+ ///
123+ /// Returns a vector with:
124+ /// 1. The unit itself (systemd.extra-unit)
125+ /// 2. A dropin for sysinit.target to pull in the unit
126+ pub fn smbios_creds_for_storage_opts ( ) -> Result < Vec < String > > {
127+ // Create systemd unit that conditionally appends to /etc/environment
128+ let unit_content = r#"[Unit]
129+ Description=Setup STORAGE_OPTS for bcvk
130+ DefaultDependencies=no
131+ Before=systemd-user-sessions.service
132+
133+ [Service]
134+ Type=oneshot
135+ ExecStart=/bin/sh -c 'grep -q STORAGE_OPTS /etc/environment || echo STORAGE_OPTS=additionalimagestore=/run/host-container-storage >> /etc/environment'
136+ RemainAfterExit=yes
137+ "# ;
138+ let encoded_unit = data_encoding:: BASE64 . encode ( unit_content. as_bytes ( ) ) ;
139+ let unit_cred = format ! (
140+ "io.systemd.credential.binary:systemd.extra-unit.bcvk-storage-opts.service={encoded_unit}"
141+ ) ;
142+
143+ // Create dropin for sysinit.target to pull in our unit
144+ let dropin_content = "[Unit]\n Wants=bcvk-storage-opts.service\n " ;
145+ let encoded_dropin = data_encoding:: BASE64 . encode ( dropin_content. as_bytes ( ) ) ;
146+ let dropin_cred = format ! (
147+ "io.systemd.credential.binary:systemd.unit-dropin.sysinit.target~bcvk-storage={encoded_dropin}"
148+ ) ;
149+
150+ Ok ( vec ! [ unit_cred, dropin_cred] )
151+ }
152+
153+ /// Generate tmpfiles.d lines for STORAGE_OPTS in systemd contexts
154+ ///
155+ /// Configures STORAGE_OPTS for:
156+ /// - /etc/environment.d/: systemd user manager and user services
157+ /// - /etc/systemd/system.conf.d/: system-level systemd services
158+ pub fn storage_opts_tmpfiles_d_lines ( ) -> String {
159+ concat ! (
160+ "f /etc/environment.d/90-bcvk-storage.conf 0644 root root - STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n " ,
161+ "d /etc/systemd/system.conf.d 0755 root root -\n " ,
162+ "f /etc/systemd/system.conf.d/90-bcvk-storage.conf 0644 root root - [Manager]\\ nDefaultEnvironment=STORAGE_OPTS=additionalimagestore=/run/host-container-storage\n "
163+ ) . to_string ( )
164+ }
165+
166+ /// Generate SMBIOS credential string for root SSH access
167+ ///
168+ /// Creates a systemd credential for QEMU's SMBIOS interface. Preferred method
169+ /// as it keeps credentials out of kernel command line and boot logs.
170+ ///
171+ /// Returns a string for use with `qemu -smbios type=11,value="..."`
172+ pub fn smbios_cred_for_root_ssh ( pubkey : & str ) -> Result < String > {
173+ let k = key_to_root_tmpfiles_d ( pubkey) ;
174+ let encoded = data_encoding:: BASE64 . encode ( k. as_bytes ( ) ) ;
175+ let r = format ! ( "io.systemd.credential.binary:tmpfiles.extra={encoded}" ) ;
176+ Ok ( r)
177+ }
178+
179+ /// Generate kernel command-line argument for root SSH access
180+ ///
181+ /// Creates a systemd credential for kernel command-line delivery. Less secure
182+ /// than SMBIOS method as credentials are visible in /proc/cmdline and boot logs.
183+ ///
184+ /// Returns a string for use in kernel boot parameters.
185+ #[ allow( dead_code) ]
186+ pub fn karg_for_root_ssh ( pubkey : & str ) -> Result < String > {
187+ let k = key_to_root_tmpfiles_d ( pubkey) ;
188+ let encoded = data_encoding:: BASE64 . encode ( k. as_bytes ( ) ) ;
189+ let r = format ! ( "systemd.set_credential_binary=tmpfiles.extra:{encoded}" ) ;
190+ Ok ( r)
191+ }
192+
193+ /// Convert SSH public key to systemd tmpfiles.d configuration
194+ ///
195+ /// Generates configuration to create `/root/.ssh` directory (0750) and
196+ /// `/root/.ssh/authorized_keys` file (700) with the Base64-encoded SSH key.
197+ /// Uses `f+~` to append to existing authorized_keys files.
198+ pub fn key_to_root_tmpfiles_d ( pubkey : & str ) -> String {
199+ let buf = data_encoding:: BASE64 . encode ( pubkey. as_bytes ( ) ) ;
200+ format ! ( "d /root/.ssh 0750 - - -\n f+~ /root/.ssh/authorized_keys 700 - - - {buf}\n " )
201+ }
202+
202203#[ cfg( test) ]
203204mod tests {
204205 use data_encoding:: BASE64 ;
0 commit comments