Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/Distributed.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ of the scheduler and instruct clients to configure their `scheduler_url` with th
appropriate `https://` address. The scheduler will verify the server's IP in this
configuration by inspecting the `X-Real-IP` header's value, if present. The webserver
used in this case should be configured to set this header to the appropriate value.
This verification is enabled by default and can be disabled by setting
`check_server_ip = false` in the scheduler config.

Securing communication with the server is performed automatically - HTTPS certificates
are generated dynamically on server startup and communicated to the scheduler during
Expand All @@ -296,6 +298,8 @@ Use the `--config` argument to pass the path to its configuration file to `sccac
# The socket address the scheduler will listen on. It's strongly recommended
# to listen on localhost and put a HTTPS server in front of it.
public_addr = "127.0.0.1:10600"
# Set to false to disable IP verification of build servers.
#check_server_ip = true

[client_auth]
type = "token"
Expand Down
2 changes: 2 additions & 0 deletions src/bin/sccache-dist/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ fn run(command: Command) -> Result<i32> {
public_addr,
client_auth,
server_auth,
check_server_ip,
}) => {
let check_client_auth: Box<dyn dist::http::ClientAuthCheck> = match client_auth {
scheduler_config::ClientAuth::Insecure => Box::new(token_check::EqCheck::new(
Expand Down Expand Up @@ -224,6 +225,7 @@ fn run(command: Command) -> Result<i32> {
scheduler,
check_client_auth,
check_server_auth,
check_server_ip,
);
http_scheduler.start()?;
unreachable!();
Expand Down
8 changes: 8 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,14 @@ pub mod scheduler {
pub public_addr: SocketAddr,
pub client_auth: ClientAuth,
pub server_auth: ServerAuth,
#[serde(default = "default_check_server_ip")]
/// Whether to verify the build server's IP address matches the address
/// in its auth token.
pub check_server_ip: bool,
}

fn default_check_server_ip() -> bool {
true
}

pub fn from_path(conf_path: &Path) -> Result<Option<Config>> {
Expand Down
101 changes: 81 additions & 20 deletions src/dist/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,33 @@ mod server {
split.next()
}

fn verify_server_ip(
server_id: &ServerId,
request: &rouille::Request,
) -> StdResult<(), rouille::Response> {
let origin_ip = if let Some(header_val) = request.header("X-Real-IP") {
trace!("X-Real-IP: {:?}", header_val);
match header_val.parse() {
Ok(ip) => ip,
Err(err) => {
warn!(
"X-Real-IP value {:?} could not be parsed: {:?}",
header_val, err
);
return Err(rouille::Response::empty_400());
}
}
} else {
request.remote_addr().ip()
};
if server_id.addr().ip() != origin_ip {
trace!("server ip: {:?}", server_id.addr().ip());
trace!("request ip: {:?}", origin_ip);
return Err(make_401("invalid_bearer_token_mismatched_address"));
}
Ok(())
}

/// Return `content` as a bincode-encoded `Response`.
pub fn bincode_response<T>(content: &T) -> rouille::Response
where
Expand Down Expand Up @@ -658,13 +685,60 @@ mod server {
assert!(ja2.verify_token(job_id2, &token2).is_err());
}

#[test]
fn test_verify_server_ip_match_remote_addr() {
let addr: SocketAddr = "127.0.0.1:12345".parse().unwrap();
let server_id = ServerId::new(addr);
let request = rouille::Request::fake_http("POST", "/", vec![], vec![]);
assert!(verify_server_ip(&server_id, &request).is_ok());
}

#[test]
fn test_verify_server_ip_mismatch_remote_addr() {
let server_id = ServerId::new("192.168.1.1:12345".parse().unwrap());
let request = rouille::Request::fake_http("POST", "/", vec![], vec![]);
let result = verify_server_ip(&server_id, &request);
assert!(result.is_err());
let resp = result.unwrap_err();
assert_eq!(resp.status_code, 401);
}

#[test]
fn test_verify_server_ip_match_x_real_ip() {
let server_id = ServerId::new("10.0.0.5:9000".parse().unwrap());
let request = rouille::Request::fake_http(
"POST",
"/",
vec![("X-Real-IP".into(), "10.0.0.5".into())],
vec![],
);
assert!(verify_server_ip(&server_id, &request).is_ok());
}

#[test]
fn test_verify_server_ip_mismatch_x_real_ip() {
let server_id = ServerId::new("10.0.0.5:9000".parse().unwrap());
let request = rouille::Request::fake_http(
"POST",
"/",
vec![("X-Real-IP".into(), "10.0.0.6".into())],
vec![],
);
let result = verify_server_ip(&server_id, &request);
assert!(result.is_err());
let resp = result.unwrap_err();
assert_eq!(resp.status_code, 401);
}

pub struct Scheduler<S> {
public_addr: SocketAddr,
handler: S,
// Is this client permitted to use the scheduler?
check_client_auth: Box<dyn ClientAuthCheck>,
// Do we believe the server is who they appear to be?
check_server_auth: ServerAuthCheck,
// Verify the server's IP matches the address in its auth token
check_server_ip: bool,
}

impl<S: dist::SchedulerIncoming + 'static> Scheduler<S> {
Expand All @@ -673,12 +747,14 @@ mod server {
handler: S,
check_client_auth: Box<dyn ClientAuthCheck>,
check_server_auth: ServerAuthCheck,
check_server_ip: bool,
) -> Self {
Self {
public_addr,
handler,
check_client_auth,
check_server_auth,
check_server_ip,
}
}

Expand All @@ -688,6 +764,7 @@ mod server {
handler,
check_client_auth,
check_server_auth,
check_server_ip,
} = self;
let requester = SchedulerRequester {
client: Mutex::new(new_reqwest_blocking_client()),
Expand All @@ -697,28 +774,12 @@ mod server {
($request:ident) => {{
match bearer_http_auth($request).and_then(&*check_server_auth) {
Some(server_id) => {
let origin_ip = if let Some(header_val) = $request.header("X-Real-IP") {
trace!("X-Real-IP: {:?}", header_val);
match header_val.parse() {
Ok(ip) => ip,
Err(err) => {
warn!(
"X-Real-IP value {:?} could not be parsed: {:?}",
header_val, err
);
return rouille::Response::empty_400();
}
if check_server_ip {
if let Err(resp) = verify_server_ip(&server_id, $request) {
return resp;
}
} else {
$request.remote_addr().ip()
};
if server_id.addr().ip() != origin_ip {
trace!("server ip: {:?}", server_id.addr().ip());
trace!("request ip: {:?}", $request.remote_addr().ip());
return make_401("invalid_bearer_token_mismatched_address");
} else {
server_id
}
server_id
}
None => return make_401("invalid_bearer_token"),
}
Expand Down
1 change: 1 addition & 0 deletions tests/harness/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ fn sccache_scheduler_cfg() -> sccache::config::scheduler::Config {
server_auth: sccache::config::scheduler::ServerAuth::Token {
token: DIST_SERVER_TOKEN.to_owned(),
},
check_server_ip: true,
}
}

Expand Down
Loading