1
1
use anyhow:: { Context , Result } ;
2
2
use bootc_utils:: CommandRunExt ;
3
+ use bootc_utils:: PathQuotedDisplay ;
3
4
use rustix:: fs:: Uid ;
4
5
use rustix:: process:: geteuid;
5
6
use rustix:: process:: getuid;
@@ -9,6 +10,7 @@ use std::collections::BTreeMap;
9
10
use std:: collections:: BTreeSet ;
10
11
use std:: fmt:: Display ;
11
12
use std:: fmt:: Formatter ;
13
+ use std:: os:: unix:: process:: CommandExt ;
12
14
use std:: process:: Command ;
13
15
use uzers:: os:: unix:: UserExt ;
14
16
@@ -83,7 +85,6 @@ impl Drop for UidChange {
83
85
pub ( crate ) struct UserKeys {
84
86
pub ( crate ) user : String ,
85
87
pub ( crate ) authorized_keys : String ,
86
- pub ( crate ) authorized_keys_path : String ,
87
88
}
88
89
89
90
impl UserKeys {
@@ -134,64 +135,104 @@ impl<'a> SshdConfig<'a> {
134
135
}
135
136
}
136
137
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
+
137
185
pub ( crate ) fn get_all_users_keys ( ) -> Result < Vec < UserKeys > > {
138
186
let loginctl_user_names = loginctl_users ( ) . context ( "enumerate users" ) ?;
139
187
140
188
let mut all_users_authorized_keys = Vec :: new ( ) ;
141
189
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 ( ) ) ?;
143
197
tracing:: debug!( "parsed sshd config: {:?}" , sshd_config) ;
144
198
145
199
for user_name in loginctl_user_names {
146
200
let user_info = uzers:: get_user_by_name ( user_name. as_str ( ) )
147
201
. context ( format ! ( "user {} not found" , user_name) ) ?;
148
202
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 ( ) ) ;
158
207
}
159
208
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
+
160
217
let user_name = user_info
161
218
. name ( )
162
219
. to_str ( )
163
220
. context ( "user name is not valid utf-8" ) ?;
164
221
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
-
178
222
if user_authorized_keys. trim ( ) . is_empty ( ) {
179
223
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
182
226
) ;
183
227
continue ;
184
228
}
185
229
186
230
let user_keys = UserKeys {
187
231
user : user_name. to_string ( ) ,
188
232
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 ( ) ,
193
233
} ;
194
234
235
+ tracing:: trace!( "Found user keys: {:?}" , user_keys) ;
195
236
tracing:: debug!(
196
237
"Found user {} with {} SSH authorized_keys" ,
197
238
user_keys. user,
0 commit comments