use std::net::SocketAddr;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct SshConfig {
pub bind: SocketAddr,
pub server_id: String,
pub inactivity_timeout: Option<Duration>,
pub exec_grace: Option<Duration>,
pub auth_rejection_time: Duration,
pub max_auth_attempts: usize,
pub keepalive_interval: Option<Duration>,
pub max_concurrent_transfers: Option<usize>,
pub allow_anonymous: bool,
pub max_transfers_per_principal: Option<usize>,
pub max_anonymous_transfers: Option<usize>,
#[cfg(feature = "admit-share")]
pub admit: edda_admit::Settings,
pub per_principal_limit_is_hard: bool,
pub memory_budget_bytes: Option<u64>,
pub admission_wait: Option<Duration>,
pub keyed_admission_wait: Option<Duration>,
pub session_end_grace: Duration,
pub session_end_grace_crowded: Duration,
}
pub const DEFAULT_PORT: u16 = 2222;
pub const DEFAULT_MAX_CONCURRENT_TRANSFERS: usize = 256;
pub const DEFAULT_MAX_TRANSFERS_PER_PRINCIPAL: usize = 32;
impl Default for SshConfig {
fn default() -> Self {
SshConfig {
bind: SocketAddr::from(([0, 0, 0, 0], DEFAULT_PORT)),
server_id: format!("SSH-2.0-gunnar_{}", env!("CARGO_PKG_VERSION")),
inactivity_timeout: Some(Duration::from_secs(600)),
exec_grace: Some(Duration::from_secs(60)),
auth_rejection_time: Duration::from_secs(1),
max_auth_attempts: 6,
keepalive_interval: Some(Duration::from_secs(15)),
allow_anonymous: true,
max_concurrent_transfers: Some(DEFAULT_MAX_CONCURRENT_TRANSFERS),
max_transfers_per_principal: Some(DEFAULT_MAX_TRANSFERS_PER_PRINCIPAL),
max_anonymous_transfers: Some(crate::admission::DEFAULT_MAX_ANONYMOUS_TRANSFERS),
per_principal_limit_is_hard: false,
#[cfg(feature = "admit-share")]
admit: edda_admit::Settings::default(),
memory_budget_bytes: Some(crate::governor::DEFAULT_MEMORY_BUDGET_BYTES),
admission_wait: Some(crate::governor::DEFAULT_ADMISSION_WAIT),
keyed_admission_wait: Some(crate::governor::DEFAULT_KEYED_ADMISSION_WAIT),
session_end_grace: crate::channel::SESSION_END_GRACE,
session_end_grace_crowded: crate::channel::CROWDED_SESSION_END_GRACE,
}
}
}
impl SshConfig {
pub fn ephemeral_loopback() -> Self {
SshConfig {
bind: SocketAddr::from(([127, 0, 0, 1], 0)),
inactivity_timeout: Some(Duration::from_secs(20)),
exec_grace: Some(Duration::from_secs(10)),
auth_rejection_time: Duration::from_millis(50),
admission_wait: Some(Duration::from_millis(100)),
keyed_admission_wait: None,
..SshConfig::default()
}
}
pub fn without_anonymous_access(mut self) -> Self {
self.allow_anonymous = false;
self
}
}
pub const ANONYMOUS_SSH_USER: &str = "anonymous";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_admission_limits_are_a_decision_and_are_ordered() {
let config = SshConfig::default();
let server_wide = config
.max_concurrent_transfers
.expect("the appliance default must HAVE a limit; the defect was that it had none");
let per_principal = config
.max_transfers_per_principal
.expect("the per-principal default must exist too");
assert!(
per_principal < server_wide,
"a per-principal limit of {per_principal} against a server-wide {server_wide} means \
one principal can take the whole server, which is the half of the defect the global \
limit does not answer"
);
}
#[test]
fn the_test_constructor_does_not_quietly_change_the_limits() {
let ephemeral = SshConfig::ephemeral_loopback();
let default = SshConfig::default();
assert_eq!(
ephemeral.max_concurrent_transfers,
default.max_concurrent_transfers
);
assert_eq!(
ephemeral.max_transfers_per_principal,
default.max_transfers_per_principal
);
assert_eq!(
ephemeral.allow_anonymous, default.allow_anonymous,
"the test constructor must not quietly open or close the anonymous path, or every \
integration test would be exercising a configuration no appliance has"
);
}
#[test]
fn anonymous_read_is_on_by_default_and_can_be_switched_off() {
assert!(
SshConfig::default().allow_anonymous,
"a public repository must be readable without a key by default"
);
assert!(
!SshConfig::default()
.without_anonymous_access()
.allow_anonymous
);
}
}