@@ -54,8 +54,23 @@ pub const ROOT_CERTS: &[&[u8]] = &[
5454/// Maximum certificate chain length.
5555const MAX_CERT_CHAIN_LEN : usize = 10 ;
5656
57+ /// Maximum number of job signing certificates.
58+ /// The ideal number of certs is 1, so this need not be large.
59+ const MAX_CERTS : usize = 100 ;
60+
61+ /// Maximum number of identities.
62+ const MAX_IDENTITIES : usize = 1_000 ;
63+
64+ /// Maximum number of jobs in a session. Should be set to a large
65+ /// but reasonable value; big enough to not be annoying, but small
66+ /// enough to avoid filling up memory.
67+ const MAX_JOBS : usize = 10_000 ;
68+
5769/// Maximum number of outstanding authentication nonces.
58- const MAX_OUTSTANDING_NONCES : NonZeroUsize = NonZeroUsize :: new ( 1000 ) . unwrap ( ) ;
70+ /// We do not really expect more than one simultaneous user,
71+ /// nor do we expect hostile (DoS) requests, so a small value
72+ /// here is adequate.
73+ const MAX_OUTSTANDING_NONCES : NonZeroUsize = NonZeroUsize :: new ( 100 ) . unwrap ( ) ;
5974
6075/// Output files or ranges larger than this will not be served all at once.
6176const OUTPUT_THRESHOLD : u64 = ByteSize :: mb ( 128 ) . as_u64 ( ) ;
@@ -126,6 +141,9 @@ impl JobManager {
126141
127142 fn import_cert_inner ( & self , cert : Certificate , root_allowed : bool ) -> Result < KeyId , JobError > {
128143 let mut certs = self . certs . lock ( ) . unwrap ( ) ;
144+ if certs. len ( ) == MAX_CERTS {
145+ return Err ( JobError :: TooManyCerts ( MAX_CERTS ) ) ;
146+ }
129147
130148 // Verify the certificate signature.
131149 let signature = Signature :: try_from ( & cert) ?;
@@ -235,12 +253,14 @@ impl JobManager {
235253 Some (
236254 identity @ Identity {
237255 key_id : identity_key_id,
256+ nonce : n,
238257 cnonce : c,
239258 signature : s,
240259 time_revoked : None ,
241260 ..
242261 } ,
243262 ) if try_authn ! ( identity. is_still_valid( & now) , "credentials expired" )
263+ && try_authn ! ( n == nonce, "invalid nonce" )
244264 && try_authn ! ( c == cnonce, "invalid cnonce" )
245265 && try_authn ! ( s == signature, "invalid signature" ) =>
246266 {
@@ -258,6 +278,9 @@ impl JobManager {
258278 "invalid public key"
259279 ) =>
260280 {
281+ if identities. len ( ) == MAX_IDENTITIES {
282+ return Err ( JobError :: TooManyIdentities ( MAX_IDENTITIES ) ) ;
283+ }
261284 let public_key = public_key. expect ( "checked in guard" ) ;
262285 let response = credentials. clone ( ) . into_challenge_response ( ) ;
263286 let verified = try_authn ! ( response. verify_with_ssh_public_key( & public_key) ) ?;
@@ -330,9 +353,15 @@ impl JobManager {
330353 session_id : & SessionId ,
331354 ) -> Result < ( ) , JobError > {
332355 // Anyone is allowed to stop a session.
333- if let Some ( session) = { self . session . lock ( ) . unwrap ( ) . take ( ) }
334- && session. session_id ( ) == session_id
335- {
356+ let session = {
357+ let mut session_guard = self . session . lock ( ) . unwrap ( ) ;
358+ if session_guard. as_ref ( ) . map ( |s| s. session_id ( ) ) == Some ( session_id) {
359+ session_guard. take ( )
360+ } else {
361+ None
362+ }
363+ } ;
364+ if let Some ( session) = session {
336365 self . session_stop_inner ( session) . await
337366 } else {
338367 Err ( JobError :: SessionNotFound ( session_id. to_owned ( ) ) )
@@ -366,8 +395,14 @@ impl JobManager {
366395 let wait = params. wait ;
367396 let started = {
368397 let job_id = job. job_id ( ) . to_owned ( ) ;
369- if self . jobs . lock ( ) . unwrap ( ) . contains_key ( & job_id) {
370- return Err ( JobError :: InvalidJobId ( job_id) ) ;
398+ {
399+ let jobs = self . jobs . lock ( ) . unwrap ( ) ;
400+ if jobs. contains_key ( & job_id) {
401+ return Err ( JobError :: InvalidJobId ( job_id) ) ;
402+ }
403+ if jobs. len ( ) == MAX_JOBS {
404+ return Err ( JobError :: TooManyJobs ( MAX_JOBS ) ) ;
405+ }
371406 }
372407
373408 let cert_key_id = job. key_id ( ) . to_owned ( ) ;
@@ -410,20 +445,21 @@ impl JobManager {
410445 if wait { Ok ( Some ( rx. await ?) ) } else { Ok ( None ) }
411446 }
412447
448+ /// For jobs in the current session, only the owner is allowed access.
449+ /// For jobs from previous sessions, anyone may access them; otherwise,
450+ /// they would be orphaned.
413451 fn check_job_owner ( & self , authn : & Identity , job_id : & JobId ) -> Result < ( ) , JobError > {
414- let Some ( ref session) = * self . session . lock ( ) . unwrap ( ) else {
415- return Err ( JobError :: NoSession ) ;
416- } ;
417452 let Some ( job_session_id) = self . job_status ( authn, job_id) ?. session_id ( ) else {
418453 return Err ( JobError :: JobNotFound ( job_id. to_owned ( ) ) ) ;
419454 } ;
420- if job_session_id != * session. session_id ( ) {
421- return Err ( JobError :: JobNotFound ( job_id. to_owned ( ) ) ) ;
422- }
423- if session. key_id ( ) != Some ( & authn. key_id ) {
424- return Err ( JobError :: SessionWrongIdentity ) ;
455+ if let Some ( session) = self . session . lock ( ) . unwrap ( ) . as_ref ( )
456+ && job_session_id == * session. session_id ( )
457+ && session. key_id ( ) != Some ( & authn. key_id )
458+ {
459+ Err ( JobError :: SessionWrongIdentity )
460+ } else {
461+ Ok ( ( ) )
425462 }
426- Ok ( ( ) )
427463 }
428464
429465 pub async fn job_start_interactive_session (
@@ -458,12 +494,12 @@ impl JobManager {
458494
459495 pub async fn job_output (
460496 & self ,
461- _authn : & Identity ,
497+ authn : & Identity ,
462498 job_id : & JobId ,
463499 stream : JobOutputStream ,
464500 range : Option < Range > ,
465501 ) -> Result < Vec < u8 > , JobError > {
466- // Anyone is allowed to read job output.
502+ self . check_job_owner ( authn , job_id ) ? ;
467503 get_job_output ( & self . output_dir , job_id, stream, range)
468504 }
469505
0 commit comments