@@ -9,6 +9,7 @@ use std::collections::BTreeMap;
9
9
use std:: collections:: BTreeSet ;
10
10
use std:: fmt:: Display ;
11
11
use std:: fmt:: Formatter ;
12
+ use std:: os:: unix:: process:: CommandExt ;
12
13
use std:: process:: Command ;
13
14
use uzers:: os:: unix:: UserExt ;
14
15
@@ -83,7 +84,6 @@ impl Drop for UidChange {
83
84
pub ( crate ) struct UserKeys {
84
85
pub ( crate ) user : String ,
85
86
pub ( crate ) authorized_keys : String ,
86
- pub ( crate ) authorized_keys_path : String ,
87
87
}
88
88
89
89
impl UserKeys {
@@ -134,64 +134,103 @@ impl<'a> SshdConfig<'a> {
134
134
}
135
135
}
136
136
137
+ fn get_keys_from_files ( user : & uzers:: User , keyfiles : & Vec < & str > ) -> Result < String > {
138
+ let home_dir = user. home_dir ( ) ;
139
+ let mut user_authorized_keys = String :: new ( ) ;
140
+
141
+ for keyfile in keyfiles {
142
+ let user_authorized_keys_path = home_dir. join ( keyfile) ;
143
+
144
+ if !user_authorized_keys_path. exists ( ) {
145
+ tracing:: debug!(
146
+ "Skipping authorized key file {} for user {} because it doesn't exist" ,
147
+ user_authorized_keys_path. to_string_lossy( ) ,
148
+ user. name( ) . to_string_lossy( )
149
+ ) ;
150
+ continue ;
151
+ }
152
+
153
+ // Safety: The UID should be valid because we got it from uzers
154
+ #[ allow( unsafe_code) ]
155
+ let user_uid = unsafe { Uid :: from_raw ( user. uid ( ) ) } ;
156
+
157
+ // Change the effective uid for this scope, to avoid accidentally reading files we
158
+ // shouldn't through symlinks
159
+ let _uid_change = UidChange :: new ( user_uid) ?;
160
+
161
+ let key = std:: fs:: read_to_string ( & user_authorized_keys_path)
162
+ . context ( "Failed to read user's authorized keys" ) ?;
163
+ user_authorized_keys. push_str ( key. as_str ( ) ) ;
164
+ }
165
+
166
+ Ok ( user_authorized_keys)
167
+ }
168
+
169
+ fn get_keys_from_command ( command : & str , command_user : & str ) -> Result < String > {
170
+ let user_config = uzers:: get_user_by_name ( command_user) . context ( format ! (
171
+ "authorized_keys_command_user {} not found" ,
172
+ command_user
173
+ ) ) ?;
174
+
175
+ let mut cmd = Command :: new ( command) ;
176
+ cmd. uid ( user_config. uid ( ) ) ;
177
+ let output = cmd
178
+ . run_get_string ( )
179
+ . context ( format ! ( "running authorized_keys_command {}" , command) ) ?;
180
+ Ok ( output)
181
+ }
182
+
137
183
pub ( crate ) fn get_all_users_keys ( ) -> Result < Vec < UserKeys > > {
138
184
let loginctl_user_names = loginctl_users ( ) . context ( "enumerate users" ) ?;
139
185
140
186
let mut all_users_authorized_keys = Vec :: new ( ) ;
141
187
142
- let sshd_config = SshdConfig :: parse ( ) ?;
188
+ let sshd_output = Command :: new ( "sshd" )
189
+ . arg ( "-T" )
190
+ . run_get_string ( )
191
+ . context ( "running sshd -T" ) ?;
192
+ tracing:: trace!( "sshd output:\n {}" , sshd_output) ;
193
+
194
+ let sshd_config = SshdConfig :: parse ( sshd_output. as_str ( ) ) ?;
143
195
tracing:: debug!( "parsed sshd config: {:?}" , sshd_config) ;
144
196
145
197
for user_name in loginctl_user_names {
146
198
let user_info = uzers:: get_user_by_name ( user_name. as_str ( ) )
147
199
. context ( format ! ( "user {} not found" , user_name) ) ?;
148
200
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 ;
201
+ let mut user_authorized_keys = String :: new ( ) ;
202
+ if !sshd_config. authorized_keys_files . is_empty ( ) {
203
+ let keys = get_keys_from_files ( & user_info, & sshd_config. authorized_keys_files ) ?;
204
+ user_authorized_keys. push_str ( keys. as_str ( ) ) ;
158
205
}
159
206
207
+ if sshd_config. authorized_keys_command != "none" {
208
+ let keys = get_keys_from_command (
209
+ & sshd_config. authorized_keys_command ,
210
+ & sshd_config. authorized_keys_command_user ,
211
+ ) ?;
212
+ user_authorized_keys. push_str ( keys. as_str ( ) ) ;
213
+ } ;
214
+
160
215
let user_name = user_info
161
216
. name ( )
162
217
. to_str ( )
163
218
. context ( "user name is not valid utf-8" ) ?;
164
219
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
220
if user_authorized_keys. trim ( ) . is_empty ( ) {
179
221
tracing:: debug!(
180
- "Skipping user {} because it has an empty SSH authorized_keys file " ,
181
- user_info . name ( ) . to_string_lossy ( )
222
+ "Skipping user {} because it has no SSH authorized_keys" ,
223
+ user_name
182
224
) ;
183
225
continue ;
184
226
}
185
227
186
228
let user_keys = UserKeys {
187
229
user : user_name. to_string ( ) ,
188
230
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
231
} ;
194
232
233
+ tracing:: trace!( "Found user keys: {:?}" , user_keys) ;
195
234
tracing:: debug!(
196
235
"Found user {} with {} SSH authorized_keys" ,
197
236
user_keys. user,
0 commit comments