11use anyhow:: { Context , Result } ;
22use bootc_utils:: CommandRunExt ;
3+ use bootc_utils:: PathQuotedDisplay ;
34use rustix:: fs:: Uid ;
45use rustix:: process:: geteuid;
56use rustix:: process:: getuid;
@@ -9,6 +10,7 @@ use std::collections::BTreeMap;
910use std:: collections:: BTreeSet ;
1011use std:: fmt:: Display ;
1112use std:: fmt:: Formatter ;
13+ use std:: os:: unix:: process:: CommandExt ;
1214use std:: process:: Command ;
1315use uzers:: os:: unix:: UserExt ;
1416
@@ -83,7 +85,6 @@ impl Drop for UidChange {
8385pub ( crate ) struct UserKeys {
8486 pub ( crate ) user : String ,
8587 pub ( crate ) authorized_keys : String ,
86- pub ( crate ) authorized_keys_path : String ,
8788}
8889
8990impl UserKeys {
@@ -134,64 +135,104 @@ impl<'a> SshdConfig<'a> {
134135 }
135136}
136137
138+ fn get_keys_from_files ( user : & uzers:: User , keyfiles : & Vec < & str > ) -> Result < String > {
139+ let home_dir = user. home_dir ( ) ;
140+ let mut user_authorized_keys = String :: new ( ) ;
141+
142+ for keyfile in keyfiles {
143+ let user_authorized_keys_path = home_dir. join ( keyfile) ;
144+
145+ if !user_authorized_keys_path. exists ( ) {
146+ tracing:: debug!(
147+ "Skipping authorized key file {} for user {} because it doesn't exist" ,
148+ PathQuotedDisplay :: new( & user_authorized_keys_path) ,
149+ user. name( ) . to_string_lossy( )
150+ ) ;
151+ continue ;
152+ }
153+
154+ // Safety: The UID should be valid because we got it from uzers
155+ #[ allow( unsafe_code) ]
156+ let user_uid = unsafe { Uid :: from_raw ( user. uid ( ) ) } ;
157+
158+ // Change the effective uid for this scope, to avoid accidentally reading files we
159+ // shouldn't through symlinks
160+ let _uid_change = UidChange :: new ( user_uid) ?;
161+
162+ let key = std:: fs:: read_to_string ( & user_authorized_keys_path)
163+ . context ( "Failed to read user's authorized keys" ) ?;
164+ user_authorized_keys. push_str ( key. as_str ( ) ) ;
165+ user_authorized_keys. push ( '\n' ) ;
166+ }
167+
168+ Ok ( user_authorized_keys)
169+ }
170+
171+ fn get_keys_from_command ( command : & str , command_user : & str ) -> Result < String > {
172+ let user_config = uzers:: get_user_by_name ( command_user) . context ( format ! (
173+ "authorized_keys_command_user {} not found" ,
174+ command_user
175+ ) ) ?;
176+
177+ let mut cmd = Command :: new ( command) ;
178+ cmd. uid ( user_config. uid ( ) ) ;
179+ let output = cmd
180+ . run_get_string ( )
181+ . context ( format ! ( "running authorized_keys_command {}" , command) ) ?;
182+ Ok ( output)
183+ }
184+
137185pub ( crate ) fn get_all_users_keys ( ) -> Result < Vec < UserKeys > > {
138186 let loginctl_user_names = loginctl_users ( ) . context ( "enumerate users" ) ?;
139187
140188 let mut all_users_authorized_keys = Vec :: new ( ) ;
141189
142- let sshd_config = SshdConfig :: parse ( ) ?;
190+ let sshd_output = Command :: new ( "sshd" )
191+ . arg ( "-T" )
192+ . run_get_string ( )
193+ . context ( "running sshd -T" ) ?;
194+ tracing:: trace!( "sshd output:\n {}" , sshd_output) ;
195+
196+ let sshd_config = SshdConfig :: parse ( sshd_output. as_str ( ) ) ?;
143197 tracing:: debug!( "parsed sshd config: {:?}" , sshd_config) ;
144198
145199 for user_name in loginctl_user_names {
146200 let user_info = uzers:: get_user_by_name ( user_name. as_str ( ) )
147201 . context ( format ! ( "user {} not found" , user_name) ) ?;
148202
149- let home_dir = user_info. home_dir ( ) ;
150- let user_authorized_keys_path = home_dir. join ( ".ssh/authorized_keys" ) ;
151-
152- if !user_authorized_keys_path. exists ( ) {
153- tracing:: debug!(
154- "Skipping user {} because it doesn't have an SSH authorized_keys file" ,
155- user_info. name( ) . to_string_lossy( )
156- ) ;
157- continue ;
203+ let mut user_authorized_keys = String :: new ( ) ;
204+ if !sshd_config. authorized_keys_files . is_empty ( ) {
205+ let keys = get_keys_from_files ( & user_info, & sshd_config. authorized_keys_files ) ?;
206+ user_authorized_keys. push_str ( keys. as_str ( ) ) ;
158207 }
159208
209+ if sshd_config. authorized_keys_command != "none" {
210+ let keys = get_keys_from_command (
211+ & sshd_config. authorized_keys_command ,
212+ & sshd_config. authorized_keys_command_user ,
213+ ) ?;
214+ user_authorized_keys. push_str ( keys. as_str ( ) ) ;
215+ } ;
216+
160217 let user_name = user_info
161218 . name ( )
162219 . to_str ( )
163220 . context ( "user name is not valid utf-8" ) ?;
164221
165- let user_authorized_keys = {
166- // Safety: The UID should be valid because we got it from uzers
167- #[ allow( unsafe_code) ]
168- let user_uid = unsafe { Uid :: from_raw ( user_info. uid ( ) ) } ;
169-
170- // Change the effective uid for this scope, to avoid accidentally reading files we
171- // shouldn't through symlinks
172- let _uid_change = UidChange :: new ( user_uid) ?;
173-
174- std:: fs:: read_to_string ( & user_authorized_keys_path)
175- . context ( "Failed to read user's authorized keys" ) ?
176- } ;
177-
178222 if user_authorized_keys. trim ( ) . is_empty ( ) {
179223 tracing:: debug!(
180- "Skipping user {} because it has an empty SSH authorized_keys file " ,
181- user_info . name ( ) . to_string_lossy ( )
224+ "Skipping user {} because it has no SSH authorized_keys" ,
225+ user_name
182226 ) ;
183227 continue ;
184228 }
185229
186230 let user_keys = UserKeys {
187231 user : user_name. to_string ( ) ,
188232 authorized_keys : user_authorized_keys,
189- authorized_keys_path : user_authorized_keys_path
190- . to_str ( )
191- . context ( "user's authorized_keys path is not valid utf-8" ) ?
192- . to_string ( ) ,
193233 } ;
194234
235+ tracing:: trace!( "Found user keys: {:?}" , user_keys) ;
195236 tracing:: debug!(
196237 "Found user {} with {} SSH authorized_keys" ,
197238 user_keys. user,
0 commit comments