From 1fd0154711eadcd77c36bf6fc55917be54949f6a Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Sun, 20 Oct 2019 12:59:02 -0400 Subject: drop root privileges during normal operation it should only be necessary for reading the tls key and binding to low-numbered ports --- Cargo.lock | 10 +++++ Cargo.toml | 1 + src/cmd/server.rs | 25 ++++++++++++ src/config.rs | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 18 +++++++++ src/main.rs | 1 + 6 files changed, 174 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 6df8fb4..be1d369 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1605,6 +1605,7 @@ dependencies = [ "tokio-tls 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "twoway 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1973,6 +1974,14 @@ dependencies = [ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "users" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "uuid" version = "0.7.4" @@ -2298,6 +2307,7 @@ dependencies = [ "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" +"checksum users 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c72f4267aea0c3ec6d07eaabea6ead7c5ddacfafc5e22bcf8d186706851fb4cf" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" diff --git a/Cargo.toml b/Cargo.toml index c696d13..1ab6844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ tokio-signal = "0.2" tokio-tls = "0.2" twoway = "0.2" url = "1" +users = "0.9" uuid = { version = "0.7", features = ["v4"] } [[bin]] diff --git a/src/cmd/server.rs b/src/cmd/server.rs index 6e98fe5..a6b8cff 100644 --- a/src/cmd/server.rs +++ b/src/cmd/server.rs @@ -35,6 +35,8 @@ impl crate::config::Config for Config { tls_identity_file, self.server.allowed_login_methods.clone(), self.oauth_configs.clone(), + self.server.uid, + self.server.gid, )? } else { create_server( @@ -43,6 +45,8 @@ impl crate::config::Config for Config { self.server.read_timeout, self.server.allowed_login_methods.clone(), self.oauth_configs.clone(), + self.server.uid, + self.server.gid, )? }; tokio::run(futures::future::lazy(move || { @@ -82,6 +86,8 @@ fn create_server( crate::protocol::AuthType, crate::oauth::Config, >, + uid: Option, + gid: Option, ) -> Result<( Box + Send>, Box + Send>, @@ -89,6 +95,7 @@ fn create_server( let (mut sock_w, sock_r) = tokio::sync::mpsc::channel(100); let listener = tokio::net::TcpListener::bind(&address) .context(crate::error::Bind { address })?; + drop_privs(uid, gid)?; let acceptor = listener .incoming() .context(crate::error::Acceptor) @@ -119,6 +126,8 @@ fn create_server_tls( crate::protocol::AuthType, crate::oauth::Config, >, + uid: Option, + gid: Option, ) -> Result<( Box + Send>, Box + Send>, @@ -126,6 +135,7 @@ fn create_server_tls( let (mut sock_w, sock_r) = tokio::sync::mpsc::channel(100); let listener = tokio::net::TcpListener::bind(&address) .context(crate::error::Bind { address })?; + drop_privs(uid, gid)?; let mut file = std::fs::File::open(tls_identity_file).context( crate::error::OpenFileSync { @@ -159,3 +169,18 @@ fn create_server_tls( ); Ok((Box::new(acceptor), Box::new(server))) } + +fn drop_privs( + uid: Option, + gid: Option, +) -> Result<()> { + if let Some(gid) = gid { + users::switch::set_effective_gid(gid) + .context(crate::error::SwitchGid)?; + } + if let Some(uid) = uid { + users::switch::set_effective_uid(uid) + .context(crate::error::SwitchUid)?; + } + Ok(()) +} diff --git a/src/config.rs b/src/config.rs index 3a22085..dcc44d7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -222,6 +222,12 @@ pub struct Server { )] pub allowed_login_methods: std::collections::HashSet, + + #[serde(deserialize_with = "uid", default)] + pub uid: Option, + + #[serde(deserialize_with = "gid", default)] + pub gid: Option, } impl Server { @@ -307,6 +313,8 @@ impl Default for Server { read_timeout: default_read_timeout(), tls_identity_file: None, allowed_login_methods: default_allowed_login_methods(), + uid: None, + gid: None, } } } @@ -410,6 +418,117 @@ fn default_allowed_login_methods( crate::protocol::AuthType::iter().collect() } +fn uid<'a, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: serde::de::Deserializer<'a>, +{ + struct StringOrInt; + + impl<'a> serde::de::Visitor<'a> for StringOrInt { + type Value = Option; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("string or int") + } + + fn visit_str( + self, + value: &str, + ) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Some( + users::get_user_by_name(value) + .context(crate::error::UnknownUser { name: value }) + .map_err(serde::de::Error::custom)? + .uid(), + )) + } + + fn visit_u32( + self, + value: u32, + ) -> std::result::Result + where + E: serde::de::Error, + { + if users::get_user_by_uid(value).is_none() { + return Err(serde::de::Error::custom(Error::UnknownUid { + uid: value, + })); + } + Ok(Some(value)) + } + } + + deserializer.deserialize_any(StringOrInt) +} + +fn gid<'a, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: serde::de::Deserializer<'a>, +{ + struct StringOrInt; + + impl<'a> serde::de::Visitor<'a> for StringOrInt { + type Value = Option; + + fn expecting( + &self, + formatter: &mut std::fmt::Formatter, + ) -> std::fmt::Result { + formatter.write_str("string or int") + } + + fn visit_none(self) -> std::result::Result + where + E: serde::de::Error, + { + Ok(None) + } + + fn visit_str( + self, + value: &str, + ) -> std::result::Result + where + E: serde::de::Error, + { + Ok(Some( + users::get_group_by_name(value) + .context(crate::error::UnknownGroup { name: value }) + .map_err(serde::de::Error::custom)? + .gid(), + )) + } + + fn visit_u32( + self, + value: u32, + ) -> std::result::Result + where + E: serde::de::Error, + { + if users::get_group_by_gid(value).is_none() { + return Err(serde::de::Error::custom(Error::UnknownGid { + gid: value, + })); + } + Ok(Some(value)) + } + } + + deserializer.deserialize_any(StringOrInt) +} + #[derive(serde::Deserialize, Debug)] pub struct Command { #[serde(default = "default_buffer_size")] diff --git a/src/error.rs b/src/error.rs index eeca1e8..a02b836 100644 --- a/src/error.rs +++ b/src/error.rs @@ -347,6 +347,12 @@ pub enum Error { #[snafu(display("failed to spawn process for `{}`: {}", cmd, source))] SpawnProcess { cmd: String, source: std::io::Error }, + #[snafu(display("failed to switch gid: {}", source))] + SwitchGid { source: std::io::Error }, + + #[snafu(display("failed to switch uid: {}", source))] + SwitchUid { source: std::io::Error }, + #[snafu(display( "failed to spawn a background thread to read terminal input: {}", source @@ -386,6 +392,18 @@ pub enum Error { #[snafu(display("unexpected message: {:?}", message))] UnexpectedMessage { message: crate::protocol::Message }, + #[snafu(display("failed to find group with gid {}", gid))] + UnknownGid { gid: users::gid_t }, + + #[snafu(display("failed to find group with group name {}", name))] + UnknownGroup { name: String }, + + #[snafu(display("failed to find user with uid {}", uid))] + UnknownUid { uid: users::uid_t }, + + #[snafu(display("failed to find user with username {}", name))] + UnknownUser { name: String }, + #[snafu(display("failed to write to event channel: {}", source))] WriteChannel { source: tokio::sync::mpsc::error::UnboundedSendError, diff --git a/src/main.rs b/src/main.rs index c088d6d..ed241d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ #![allow(clippy::similar_names)] #![allow(clippy::single_match)] #![allow(clippy::single_match_else)] +#![allow(clippy::too_many_arguments)] #![allow(clippy::type_complexity)] mod prelude; -- cgit v1.2.3-54-g00ecf