{client,server}: send and require a header that contains the server public key
This is a stop-gap CSRF protection mechanism from unsophisticated attacks. It's to be considered a temporary solution until a more complete one can be implemented, but it should be sufficient in most cases for the time being. See https://github.com/tonarino/innernet/issues/38 for further discussion.pull/48/head
parent
bcd68df772
commit
a87d56cfc9
|
@ -1132,6 +1132,7 @@ dependencies = [
|
||||||
"shared",
|
"shared",
|
||||||
"socket2",
|
"socket2",
|
||||||
"structopt",
|
"structopt",
|
||||||
|
"subtle",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -21,7 +21,7 @@ mod util;
|
||||||
|
|
||||||
use data_store::DataStore;
|
use data_store::DataStore;
|
||||||
use shared::{wg, Error};
|
use shared::{wg, Error};
|
||||||
use util::{http_delete, http_get, http_post, http_put, human_duration, human_size};
|
use util::{human_duration, human_size, Api};
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(name = "innernet", about)]
|
#[structopt(name = "innernet", about)]
|
||||||
|
@ -216,8 +216,8 @@ fn install(invite: &Path, hosts_file: Option<PathBuf>) -> Result<(), Error> {
|
||||||
"[*]".dimmed(),
|
"[*]".dimmed(),
|
||||||
&config.server.internal_endpoint
|
&config.server.internal_endpoint
|
||||||
);
|
);
|
||||||
http_post(
|
Api::new(&config.server).http_form(
|
||||||
&config.server.internal_endpoint,
|
"POST",
|
||||||
"/user/redeem",
|
"/user/redeem",
|
||||||
RedeemContents {
|
RedeemContents {
|
||||||
public_key: keypair.public.to_base64(),
|
public_key: keypair.public.to_base64(),
|
||||||
|
@ -334,7 +334,7 @@ fn fetch(
|
||||||
|
|
||||||
println!("{} fetching state from server.", "[*]".dimmed());
|
println!("{} fetching state from server.", "[*]".dimmed());
|
||||||
let mut store = DataStore::open_or_create(&interface)?;
|
let mut store = DataStore::open_or_create(&interface)?;
|
||||||
let State { peers, cidrs } = http_get(&config.server.internal_endpoint, "/user/state")?;
|
let State { peers, cidrs } = Api::new(&config.server).http("GET", "/user/state")?;
|
||||||
|
|
||||||
let device_info = DeviceInfo::get_by_name(&interface)?;
|
let device_info = DeviceInfo::get_by_name(&interface)?;
|
||||||
let interface_public_key = device_info
|
let interface_public_key = device_info
|
||||||
|
@ -419,12 +419,13 @@ fn fetch(
|
||||||
fn add_cidr(interface: &InterfaceName) -> Result<(), Error> {
|
fn add_cidr(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||||
println!("Fetching CIDRs");
|
println!("Fetching CIDRs");
|
||||||
let cidrs: Vec<Cidr> = http_get(&server.internal_endpoint, "/admin/cidrs")?;
|
let api = Api::new(&server);
|
||||||
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||||
|
|
||||||
let cidr_request = prompts::add_cidr(&cidrs)?;
|
let cidr_request = prompts::add_cidr(&cidrs)?;
|
||||||
|
|
||||||
println!("Creating CIDR...");
|
println!("Creating CIDR...");
|
||||||
let cidr: Cidr = http_post(&server.internal_endpoint, "/admin/cidrs", cidr_request)?;
|
let cidr: Cidr = api.http_form("POST", "/admin/cidrs", cidr_request)?;
|
||||||
|
|
||||||
printdoc!(
|
printdoc!(
|
||||||
"
|
"
|
||||||
|
@ -443,15 +444,17 @@ fn add_cidr(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
|
|
||||||
fn add_peer(interface: &InterfaceName) -> Result<(), Error> {
|
fn add_peer(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||||
|
let api = Api::new(&server);
|
||||||
|
|
||||||
println!("Fetching CIDRs");
|
println!("Fetching CIDRs");
|
||||||
let cidrs: Vec<Cidr> = http_get(&server.internal_endpoint, "/admin/cidrs")?;
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||||
println!("Fetching peers");
|
println!("Fetching peers");
|
||||||
let peers: Vec<Peer> = http_get(&server.internal_endpoint, "/admin/peers")?;
|
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
||||||
let cidr_tree = CidrTree::new(&cidrs[..]);
|
let cidr_tree = CidrTree::new(&cidrs[..]);
|
||||||
|
|
||||||
if let Some((peer_request, keypair)) = prompts::add_peer(&peers, &cidr_tree)? {
|
if let Some((peer_request, keypair)) = prompts::add_peer(&peers, &cidr_tree)? {
|
||||||
println!("Creating peer...");
|
println!("Creating peer...");
|
||||||
let peer: Peer = http_post(&server.internal_endpoint, "/admin/peers", peer_request)?;
|
let peer: Peer = api.http_form("POST", "/admin/peers", peer_request)?;
|
||||||
let server_peer = peers.iter().find(|p| p.id == 1).unwrap();
|
let server_peer = peers.iter().find(|p| p.id == 1).unwrap();
|
||||||
prompts::save_peer_invitation(
|
prompts::save_peer_invitation(
|
||||||
interface,
|
interface,
|
||||||
|
@ -470,17 +473,15 @@ fn add_peer(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
|
|
||||||
fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(), Error> {
|
fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(), Error> {
|
||||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||||
|
let api = Api::new(&server);
|
||||||
|
|
||||||
println!("Fetching peers.");
|
println!("Fetching peers.");
|
||||||
let peers: Vec<Peer> = http_get(&server.internal_endpoint, "/admin/peers")?;
|
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
|
||||||
|
|
||||||
if let Some(peer) = prompts::enable_or_disable_peer(&peers[..], enable)? {
|
if let Some(peer) = prompts::enable_or_disable_peer(&peers[..], enable)? {
|
||||||
let Peer { id, mut contents } = peer;
|
let Peer { id, mut contents } = peer;
|
||||||
contents.is_disabled = !enable;
|
contents.is_disabled = !enable;
|
||||||
http_put(
|
api.http_form("PUT", &format!("/admin/peers/{}", id), contents)?;
|
||||||
&server.internal_endpoint,
|
|
||||||
&format!("/admin/peers/{}", id),
|
|
||||||
contents,
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
println!("exited without disabling peer.");
|
println!("exited without disabling peer.");
|
||||||
}
|
}
|
||||||
|
@ -490,13 +491,14 @@ fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(),
|
||||||
|
|
||||||
fn add_association(interface: &InterfaceName) -> Result<(), Error> {
|
fn add_association(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||||
|
let api = Api::new(&server);
|
||||||
|
|
||||||
println!("Fetching CIDRs");
|
println!("Fetching CIDRs");
|
||||||
let cidrs: Vec<Cidr> = http_get(&server.internal_endpoint, "/admin/cidrs")?;
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||||
|
|
||||||
if let Some((cidr1, cidr2)) = prompts::add_association(&cidrs[..])? {
|
if let Some((cidr1, cidr2)) = prompts::add_association(&cidrs[..])? {
|
||||||
http_post(
|
api.http_form(
|
||||||
&server.internal_endpoint,
|
"POST",
|
||||||
"/admin/associations",
|
"/admin/associations",
|
||||||
AssociationContents {
|
AssociationContents {
|
||||||
cidr_id_1: cidr1.id,
|
cidr_id_1: cidr1.id,
|
||||||
|
@ -512,18 +514,15 @@ fn add_association(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
|
|
||||||
fn delete_association(interface: &InterfaceName) -> Result<(), Error> {
|
fn delete_association(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||||
|
let api = Api::new(&server);
|
||||||
|
|
||||||
println!("Fetching CIDRs");
|
println!("Fetching CIDRs");
|
||||||
let cidrs: Vec<Cidr> = http_get(&server.internal_endpoint, "/admin/cidrs")?;
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||||
println!("Fetching associations");
|
println!("Fetching associations");
|
||||||
let associations: Vec<Association> =
|
let associations: Vec<Association> = api.http("GET", "/admin/associations")?;
|
||||||
http_get(&server.internal_endpoint, "/admin/associations")?;
|
|
||||||
|
|
||||||
if let Some(association) = prompts::delete_association(&associations[..], &cidrs[..])? {
|
if let Some(association) = prompts::delete_association(&associations[..], &cidrs[..])? {
|
||||||
http_delete(
|
api.http("DELETE", &format!("/admin/associations/{}", association.id))?;
|
||||||
&server.internal_endpoint,
|
|
||||||
&format!("/admin/associations/{}", association.id),
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
println!("exited without adding association.");
|
println!("exited without adding association.");
|
||||||
}
|
}
|
||||||
|
@ -533,11 +532,12 @@ fn delete_association(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
|
|
||||||
fn list_associations(interface: &InterfaceName) -> Result<(), Error> {
|
fn list_associations(interface: &InterfaceName) -> Result<(), Error> {
|
||||||
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
|
||||||
|
let api = Api::new(&server);
|
||||||
|
|
||||||
println!("Fetching CIDRs");
|
println!("Fetching CIDRs");
|
||||||
let cidrs: Vec<Cidr> = http_get(&server.internal_endpoint, "/admin/cidrs")?;
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||||
println!("Fetching associations");
|
println!("Fetching associations");
|
||||||
let associations: Vec<Association> =
|
let associations: Vec<Association> = api.http("GET", "/admin/associations")?;
|
||||||
http_get(&server.internal_endpoint, "/admin/associations")?;
|
|
||||||
|
|
||||||
for association in associations {
|
for association in associations {
|
||||||
println!(
|
println!(
|
||||||
|
@ -590,8 +590,8 @@ fn override_endpoint(interface: &InterfaceName, unset: bool) -> Result<(), Error
|
||||||
|
|
||||||
if let Some(endpoint) = prompts::override_endpoint(unset)? {
|
if let Some(endpoint) = prompts::override_endpoint(unset)? {
|
||||||
println!("Updating endpoint.");
|
println!("Updating endpoint.");
|
||||||
http_put(
|
Api::new(&config.server).http_form(
|
||||||
&config.server.internal_endpoint,
|
"PUT",
|
||||||
"/user/endpoint",
|
"/user/endpoint",
|
||||||
EndpointContents::from(endpoint),
|
EndpointContents::from(endpoint),
|
||||||
)?;
|
)?;
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{ClientError, Error};
|
use crate::{ClientError, Error};
|
||||||
use colored::*;
|
use colored::*;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{net::SocketAddr, time::Duration};
|
use shared::{interface_config::ServerInfo, INNERNET_PUBKEY_HEADER};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub fn human_duration(duration: Duration) -> String {
|
pub fn human_duration(duration: Duration) -> String {
|
||||||
match duration.as_secs() {
|
match duration.as_secs() {
|
||||||
|
@ -46,34 +47,48 @@ pub fn human_size(bytes: u64) -> String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn http_get<T: DeserializeOwned>(server: &SocketAddr, endpoint: &str) -> Result<T, Error> {
|
pub struct Api<'a> {
|
||||||
let response = ureq::get(&format!("http://{}/v1{}", server, endpoint)).call()?;
|
server: &'a ServerInfo,
|
||||||
process_response(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn http_delete(server: &SocketAddr, endpoint: &str) -> Result<(), Error> {
|
impl<'a> Api<'a> {
|
||||||
ureq::get(&format!("http://{}/v1{}", server, endpoint)).call()?;
|
pub fn new(server: &'a ServerInfo) -> Self {
|
||||||
Ok(())
|
Self { server }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn http_post<S: Serialize, D: DeserializeOwned>(
|
pub fn http<T: DeserializeOwned>(&self, verb: &str, endpoint: &str) -> Result<T, Error> {
|
||||||
server: &SocketAddr,
|
self.request::<(), _>(verb, endpoint, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn http_form<S: Serialize, T: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
verb: &str,
|
||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
form: S,
|
form: S,
|
||||||
) -> Result<D, Error> {
|
) -> Result<T, Error> {
|
||||||
let response = ureq::post(&format!("http://{}/v1{}", server, endpoint))
|
self.request(verb, endpoint, Some(form))
|
||||||
.send_json(serde_json::to_value(form)?)?;
|
|
||||||
process_response(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn http_put<S: Serialize>(server: &SocketAddr, endpoint: &str, form: S) -> Result<(), Error> {
|
fn request<S: Serialize, T: DeserializeOwned>(
|
||||||
ureq::put(&format!("http://{}/v1{}", server, endpoint))
|
&self,
|
||||||
.send_json(serde_json::to_value(form)?)?;
|
verb: &str,
|
||||||
Ok(())
|
endpoint: &str,
|
||||||
}
|
form: Option<S>,
|
||||||
|
) -> Result<T, Error> {
|
||||||
|
let request = ureq::request(
|
||||||
|
verb,
|
||||||
|
&format!("http://{}/v1{}", self.server.internal_endpoint, endpoint),
|
||||||
|
)
|
||||||
|
.set(INNERNET_PUBKEY_HEADER, &self.server.public_key);
|
||||||
|
|
||||||
|
let response = if let Some(form) = form {
|
||||||
|
request.send_json(serde_json::to_value(form)?)?
|
||||||
|
} else {
|
||||||
|
request.call()?
|
||||||
|
};
|
||||||
|
|
||||||
fn process_response<T: DeserializeOwned>(response: ureq::Response) -> Result<T, Error> {
|
|
||||||
let mut response = response.into_string()?;
|
let mut response = response.into_string()?;
|
||||||
|
// A little trick for serde to parse an empty response as `()`.
|
||||||
if response.is_empty() {
|
if response.is_empty() {
|
||||||
response = "null".into();
|
response = "null".into();
|
||||||
}
|
}
|
||||||
|
@ -84,3 +99,4 @@ fn process_response<T: DeserializeOwned>(response: ureq::Response) -> Result<T,
|
||||||
))
|
))
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ rusqlite = "0.24"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
shared = { path = "../shared" }
|
shared = { path = "../shared" }
|
||||||
|
subtle = "2"
|
||||||
structopt = "0.3"
|
structopt = "0.3"
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
|
@ -104,7 +104,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -132,7 +133,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -145,7 +147,8 @@ mod tests {
|
||||||
cidr: test::EXPERIMENTAL_SUBCIDR.parse()?,
|
cidr: test::EXPERIMENTAL_SUBCIDR.parse()?,
|
||||||
parent: Some(cidr_res.id),
|
parent: Some(cidr_res.id),
|
||||||
};
|
};
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -166,7 +169,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::USER1_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::USER1_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -186,7 +190,8 @@ mod tests {
|
||||||
parent: Some(test::ROOT_CIDR_ID),
|
parent: Some(test::ROOT_CIDR_ID),
|
||||||
};
|
};
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -200,7 +205,8 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -220,7 +226,8 @@ mod tests {
|
||||||
parent: Some(test::ROOT_CIDR_ID),
|
parent: Some(test::ROOT_CIDR_ID),
|
||||||
};
|
};
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/cidrs")
|
.path("/v1/admin/cidrs")
|
||||||
.body(serde_json::to_string(&contents)?)
|
.body(serde_json::to_string(&contents)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -253,7 +260,8 @@ mod tests {
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
|
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -261,7 +269,8 @@ mod tests {
|
||||||
// Should fail because child CIDR exists.
|
// Should fail because child CIDR exists.
|
||||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/cidrs/{}", experimental_subcidr.id))
|
.path(&format!("/v1/admin/cidrs/{}", experimental_subcidr.id))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -269,7 +278,8 @@ mod tests {
|
||||||
// Deleting child "leaf" CIDR should fail because peer exists inside it.
|
// Deleting child "leaf" CIDR should fail because peer exists inside it.
|
||||||
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||||
|
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -304,7 +314,8 @@ mod tests {
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
|
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
|
|
@ -147,7 +147,8 @@ mod tests {
|
||||||
let peer = test::developer_peer_contents("developer3", "10.80.64.4")?;
|
let peer = test::developer_peer_contents("developer3", "10.80.64.4")?;
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -172,7 +173,8 @@ mod tests {
|
||||||
let peer = test::developer_peer_contents("devel oper", "10.80.64.4")?;
|
let peer = test::developer_peer_contents("devel oper", "10.80.64.4")?;
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -192,7 +194,8 @@ mod tests {
|
||||||
let peer = test::developer_peer_contents("developer2", "10.80.64.4")?;
|
let peer = test::developer_peer_contents("developer2", "10.80.64.4")?;
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -217,7 +220,8 @@ mod tests {
|
||||||
let peer = test::developer_peer_contents("developer3", "10.80.64.3")?;
|
let peer = test::developer_peer_contents("developer3", "10.80.64.3")?;
|
||||||
|
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -241,7 +245,8 @@ mod tests {
|
||||||
|
|
||||||
// Try to add IP outside of the CIDR network.
|
// Try to add IP outside of the CIDR network.
|
||||||
let peer = test::developer_peer_contents("developer3", "10.80.65.4")?;
|
let peer = test::developer_peer_contents("developer3", "10.80.65.4")?;
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -250,7 +255,8 @@ mod tests {
|
||||||
|
|
||||||
// Try to use the network address as peer IP.
|
// Try to use the network address as peer IP.
|
||||||
let peer = test::developer_peer_contents("developer3", "10.80.64.0")?;
|
let peer = test::developer_peer_contents("developer3", "10.80.64.0")?;
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -259,7 +265,8 @@ mod tests {
|
||||||
|
|
||||||
// Try to use the broadcast address as peer IP.
|
// Try to use the broadcast address as peer IP.
|
||||||
let peer = test::developer_peer_contents("developer3", "10.80.64.255")?;
|
let peer = test::developer_peer_contents("developer3", "10.80.64.255")?;
|
||||||
let res = test::post_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -281,7 +288,8 @@ mod tests {
|
||||||
|
|
||||||
// Try to create a new developer peer from a user peer.
|
// Try to create a new developer peer from a user peer.
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::post_request_from_ip(test::USER1_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::USER1_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -304,7 +312,8 @@ mod tests {
|
||||||
|
|
||||||
// Try to create a new developer peer from a user peer.
|
// Try to create a new developer peer from a user peer.
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::put_request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.put_request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path(&format!("/v1/admin/peers/{}", test::DEVELOPER1_PEER_ID))
|
.path(&format!("/v1/admin/peers/{}", test::DEVELOPER1_PEER_ID))
|
||||||
.body(serde_json::to_string(&change)?)
|
.body(serde_json::to_string(&change)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -325,7 +334,8 @@ mod tests {
|
||||||
|
|
||||||
// Try to create a new developer peer from a user peer.
|
// Try to create a new developer peer from a user peer.
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::put_request_from_ip(test::USER1_PEER_IP)
|
let res = server
|
||||||
|
.put_request_from_ip(test::USER1_PEER_IP)
|
||||||
.path(&format!("/v1/admin/peers/{}", test::ADMIN_PEER_ID))
|
.path(&format!("/v1/admin/peers/{}", test::ADMIN_PEER_ID))
|
||||||
.body(serde_json::to_string(&peer)?)
|
.body(serde_json::to_string(&peer)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -340,7 +350,8 @@ mod tests {
|
||||||
async fn test_list_all_peers_from_admin() -> Result<()> {
|
async fn test_list_all_peers_from_admin() -> Result<()> {
|
||||||
let server = test::Server::new()?;
|
let server = test::Server::new()?;
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
@ -369,7 +380,8 @@ mod tests {
|
||||||
async fn test_list_all_peers_from_non_admin() -> Result<()> {
|
async fn test_list_all_peers_from_non_admin() -> Result<()> {
|
||||||
let server = test::Server::new()?;
|
let server = test::Server::new()?;
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::request_from_ip(test::DEVELOPER1_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
@ -386,7 +398,8 @@ mod tests {
|
||||||
|
|
||||||
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
||||||
|
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID))
|
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -409,7 +422,8 @@ mod tests {
|
||||||
|
|
||||||
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
||||||
|
|
||||||
let res = test::request_from_ip(test::DEVELOPER1_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID))
|
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -429,7 +443,8 @@ mod tests {
|
||||||
let server = test::Server::new()?;
|
let server = test::Server::new()?;
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
|
|
||||||
let res = test::request_from_ip(test::ADMIN_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::ADMIN_PEER_IP)
|
||||||
.method("DELETE")
|
.method("DELETE")
|
||||||
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID + 100))
|
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID + 100))
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
|
|
@ -171,7 +171,8 @@ mod tests {
|
||||||
async fn test_get_state_from_developer1() -> Result<()> {
|
async fn test_get_state_from_developer1() -> Result<()> {
|
||||||
let server = test::Server::new()?;
|
let server = test::Server::new()?;
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
let res = test::request_from_ip(test::DEVELOPER1_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||||
.path("/v1/user/state")
|
.path("/v1/user/state")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
@ -195,7 +196,8 @@ mod tests {
|
||||||
let server = test::Server::new()?;
|
let server = test::Server::new()?;
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
test::put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
server
|
||||||
|
.put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||||
.path("/v1/user/endpoint")
|
.path("/v1/user/endpoint")
|
||||||
.body(serde_json::to_string(&EndpointContents::Set(
|
.body(serde_json::to_string(&EndpointContents::Set(
|
||||||
"1.1.1.1:51820".parse()?
|
"1.1.1.1:51820".parse()?
|
||||||
|
@ -208,7 +210,8 @@ mod tests {
|
||||||
|
|
||||||
println!("{}", serde_json::to_string(&EndpointContents::Unset)?);
|
println!("{}", serde_json::to_string(&EndpointContents::Unset)?);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
test::put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
server
|
||||||
|
.put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||||
.path("/v1/user/endpoint")
|
.path("/v1/user/endpoint")
|
||||||
.body(serde_json::to_string(&EndpointContents::Unset)?)
|
.body(serde_json::to_string(&EndpointContents::Unset)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -218,7 +221,8 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
test::put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
server
|
||||||
|
.put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||||
.path("/v1/user/endpoint")
|
.path("/v1/user/endpoint")
|
||||||
.body("endpoint=blah")
|
.body("endpoint=blah")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -236,7 +240,8 @@ mod tests {
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
|
|
||||||
// Request comes from an unknown IP.
|
// Request comes from an unknown IP.
|
||||||
let res = test::request_from_ip("10.80.80.80")
|
let res = server
|
||||||
|
.request_from_ip("10.80.80.80")
|
||||||
.path("/v1/user/state")
|
.path("/v1/user/state")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
@ -296,7 +301,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
for ip in &[test::DEVELOPER1_PEER_IP, test::EXPERIMENT_SUBCIDR_PEER_IP] {
|
for ip in &[test::DEVELOPER1_PEER_IP, test::EXPERIMENT_SUBCIDR_PEER_IP] {
|
||||||
let res = test::request_from_ip(ip)
|
let res = server
|
||||||
|
.request_from_ip(ip)
|
||||||
.path("/v1/user/state")
|
.path("/v1/user/state")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
@ -344,7 +350,8 @@ mod tests {
|
||||||
let filter = crate::routes(server.context());
|
let filter = crate::routes(server.context());
|
||||||
|
|
||||||
// Step 1: Ensure that before redeeming, other endpoints aren't yet accessible.
|
// Step 1: Ensure that before redeeming, other endpoints aren't yet accessible.
|
||||||
let res = test::request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||||
.path("/v1/user/state")
|
.path("/v1/user/state")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
@ -354,7 +361,8 @@ mod tests {
|
||||||
let body = RedeemContents {
|
let body = RedeemContents {
|
||||||
public_key: "YBVIgpfLbi/knrMCTEb0L6eVy0daiZnJJQkxBK9s+2I=".into(),
|
public_key: "YBVIgpfLbi/knrMCTEb0L6eVy0daiZnJJQkxBK9s+2I=".into(),
|
||||||
};
|
};
|
||||||
let res = test::post_request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||||
.path("/v1/user/redeem")
|
.path("/v1/user/redeem")
|
||||||
.body(serde_json::to_string(&body)?)
|
.body(serde_json::to_string(&body)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -362,7 +370,8 @@ mod tests {
|
||||||
assert!(res.status().is_success());
|
assert!(res.status().is_success());
|
||||||
|
|
||||||
// Step 3: Ensure that a second attempt at redemption DOESN'T work.
|
// Step 3: Ensure that a second attempt at redemption DOESN'T work.
|
||||||
let res = test::post_request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
let res = server
|
||||||
|
.post_request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||||
.path("/v1/user/redeem")
|
.path("/v1/user/redeem")
|
||||||
.body(serde_json::to_string(&body)?)
|
.body(serde_json::to_string(&body)?)
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
|
@ -370,7 +379,8 @@ mod tests {
|
||||||
assert!(res.status().is_client_error());
|
assert!(res.status().is_client_error());
|
||||||
|
|
||||||
// Step 3: Ensure that after redemption, fetching state works.
|
// Step 3: Ensure that after redemption, fetching state works.
|
||||||
let res = test::request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
let res = server
|
||||||
|
.request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||||
.path("/v1/user/state")
|
.path("/v1/user/state")
|
||||||
.reply(&filter)
|
.reply(&filter)
|
||||||
.await;
|
.await;
|
||||||
|
|
|
@ -6,7 +6,7 @@ use ipnetwork::IpNetwork;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use shared::IoErrorContext;
|
use shared::{IoErrorContext, INNERNET_PUBKEY_HEADER};
|
||||||
use std::{
|
use std::{
|
||||||
convert::Infallible,
|
convert::Infallible,
|
||||||
env,
|
env,
|
||||||
|
@ -18,8 +18,9 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use warp::Filter;
|
use subtle::ConstantTimeEq;
|
||||||
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, PeerConfigBuilder};
|
use warp::{filters, Filter};
|
||||||
|
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, Key, PeerConfigBuilder};
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
|
@ -68,6 +69,7 @@ pub struct Context {
|
||||||
pub db: Db,
|
pub db: Db,
|
||||||
pub endpoints: Arc<Endpoints>,
|
pub endpoints: Arc<Endpoints>,
|
||||||
pub interface: InterfaceName,
|
pub interface: InterfaceName,
|
||||||
|
pub public_key: Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
|
@ -297,11 +299,13 @@ async fn serve(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Err
|
||||||
|
|
||||||
log::info!("{} peers added to wireguard interface.", peers.len());
|
log::info!("{} peers added to wireguard interface.", peers.len());
|
||||||
|
|
||||||
|
let public_key = wgctrl::Key::from_base64(&config.private_key)?.generate_public();
|
||||||
let db = Arc::new(Mutex::new(conn));
|
let db = Arc::new(Mutex::new(conn));
|
||||||
let context = Context {
|
let context = Context {
|
||||||
db,
|
db,
|
||||||
interface: *interface,
|
interface: *interface,
|
||||||
endpoints,
|
endpoints,
|
||||||
|
public_key,
|
||||||
};
|
};
|
||||||
|
|
||||||
log::info!("innernet-server {} starting.", VERSION);
|
log::info!("innernet-server {} starting.", VERSION);
|
||||||
|
@ -363,34 +367,6 @@ pub fn routes(
|
||||||
.recover(handle_rejection)
|
.recover(handle_rejection)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_unredeemed_session(
|
|
||||||
context: Context,
|
|
||||||
) -> impl Filter<Extract = (UnredeemedSession,), Error = warp::Rejection> + Clone {
|
|
||||||
warp::filters::addr::remote()
|
|
||||||
.and_then(move |addr: Option<SocketAddr>| {
|
|
||||||
get_session(context.clone(), addr.map(|addr| addr.ip()), false, false)
|
|
||||||
})
|
|
||||||
.map(|session| UnredeemedSession(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_session(
|
|
||||||
context: Context,
|
|
||||||
) -> impl Filter<Extract = (Session,), Error = warp::Rejection> + Clone {
|
|
||||||
warp::filters::addr::remote().and_then(move |addr: Option<SocketAddr>| {
|
|
||||||
get_session(context.clone(), addr.map(|addr| addr.ip()), false, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_admin_session(
|
|
||||||
context: Context,
|
|
||||||
) -> impl Filter<Extract = (AdminSession,), Error = warp::Rejection> + Clone {
|
|
||||||
warp::filters::addr::remote()
|
|
||||||
.and_then(move |addr: Option<SocketAddr>| {
|
|
||||||
get_session(context.clone(), addr.map(|addr| addr.ip()), true, true)
|
|
||||||
})
|
|
||||||
.map(|session| AdminSession(session))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn form_body<T>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + Clone
|
pub fn form_body<T>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + Clone
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + Send,
|
T: DeserializeOwned + Send,
|
||||||
|
@ -398,24 +374,88 @@ where
|
||||||
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
|
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_unredeemed_session(
|
||||||
|
context: Context,
|
||||||
|
) -> impl Filter<Extract = (UnredeemedSession,), Error = warp::Rejection> + Clone {
|
||||||
|
filters::addr::remote()
|
||||||
|
.and(filters::header::header(INNERNET_PUBKEY_HEADER))
|
||||||
|
.and_then(move |addr: Option<SocketAddr>, pubkey: String| {
|
||||||
|
get_session(
|
||||||
|
context.clone(),
|
||||||
|
addr.map(|addr| addr.ip()),
|
||||||
|
pubkey,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|session| UnredeemedSession(session))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_session(
|
||||||
|
context: Context,
|
||||||
|
) -> impl Filter<Extract = (Session,), Error = warp::Rejection> + Clone {
|
||||||
|
filters::addr::remote()
|
||||||
|
.and(filters::header::header(INNERNET_PUBKEY_HEADER))
|
||||||
|
.and_then(move |addr: Option<SocketAddr>, pubkey: String| {
|
||||||
|
get_session(
|
||||||
|
context.clone(),
|
||||||
|
addr.map(|addr| addr.ip()),
|
||||||
|
pubkey,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_admin_session(
|
||||||
|
context: Context,
|
||||||
|
) -> impl Filter<Extract = (AdminSession,), Error = warp::Rejection> + Clone {
|
||||||
|
filters::addr::remote()
|
||||||
|
.and(filters::header::header(INNERNET_PUBKEY_HEADER))
|
||||||
|
.and_then(move |addr: Option<SocketAddr>, pubkey: String| {
|
||||||
|
get_session(
|
||||||
|
context.clone(),
|
||||||
|
addr.map(|addr| addr.ip()),
|
||||||
|
pubkey,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(|session| AdminSession(session))
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_session(
|
async fn get_session(
|
||||||
context: Context,
|
context: Context,
|
||||||
addr: Option<IpAddr>,
|
addr: Option<IpAddr>,
|
||||||
|
pubkey: String,
|
||||||
admin_only: bool,
|
admin_only: bool,
|
||||||
redeemed_only: bool,
|
redeemed_only: bool,
|
||||||
) -> Result<Session, warp::Rejection> {
|
) -> Result<Session, warp::Rejection> {
|
||||||
addr.map(|addr| -> Result<Session, ServerError> {
|
_get_session(context, addr, pubkey, admin_only, redeemed_only)
|
||||||
|
.map_err(|_| warp::reject::custom(ServerError::Unauthorized))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _get_session(
|
||||||
|
context: Context,
|
||||||
|
addr: Option<IpAddr>,
|
||||||
|
pubkey: String,
|
||||||
|
admin_only: bool,
|
||||||
|
redeemed_only: bool,
|
||||||
|
) -> Result<Session, Error> {
|
||||||
|
let pubkey = Key::from_base64(&pubkey)?;
|
||||||
|
if pubkey.0.ct_eq(&context.public_key.0).into() {
|
||||||
|
let addr = addr.ok_or(ServerError::NotFound)?;
|
||||||
let peer = DatabasePeer::get_from_ip(&context.db.lock(), addr)?;
|
let peer = DatabasePeer::get_from_ip(&context.db.lock(), addr)?;
|
||||||
|
|
||||||
if !peer.is_disabled && (!admin_only || peer.is_admin) && (!redeemed_only || peer.is_redeemed) {
|
if !peer.is_disabled
|
||||||
Ok(Session { context, peer })
|
&& (!admin_only || peer.is_admin)
|
||||||
} else {
|
&& (!redeemed_only || peer.is_redeemed)
|
||||||
Err(ServerError::Unauthorized)
|
{
|
||||||
|
return Ok(Session { context, peer });
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.map(|session| session.ok())
|
|
||||||
.flatten() // If no IP address is found, reject.
|
Err(ServerError::Unauthorized.into())
|
||||||
.ok_or_else(|| { warp::reject::custom(ServerError::Unauthorized)})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -442,7 +482,8 @@ mod tests {
|
||||||
let filter = routes(server.context());
|
let filter = routes(server.context());
|
||||||
|
|
||||||
// Request from an unknown IP, trying to disguise as an admin using HTTP headers.
|
// Request from an unknown IP, trying to disguise as an admin using HTTP headers.
|
||||||
let res = test::request_from_ip("10.80.80.80")
|
let res = server
|
||||||
|
.request_from_ip("10.80.80.80")
|
||||||
.path("/v1/admin/peers")
|
.path("/v1/admin/peers")
|
||||||
.header("Forwarded", format!("for={}", test::ADMIN_PEER_IP))
|
.header("Forwarded", format!("for={}", test::ADMIN_PEER_IP))
|
||||||
.header("X-Forwarded-For", test::ADMIN_PEER_IP)
|
.header("X-Forwarded-For", test::ADMIN_PEER_IP)
|
||||||
|
@ -457,4 +498,48 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_incorrect_public_key() -> Result<()> {
|
||||||
|
let server = test::Server::new()?;
|
||||||
|
let filter = routes(server.context());
|
||||||
|
|
||||||
|
let key = Key::generate_private().generate_public();
|
||||||
|
|
||||||
|
// Request from an unknown IP, trying to disguise as an admin using HTTP headers.
|
||||||
|
let res = server
|
||||||
|
.request_from_ip("10.80.80.80")
|
||||||
|
.path("/v1/admin/peers")
|
||||||
|
.header(shared::INNERNET_PUBKEY_HEADER, key.to_base64())
|
||||||
|
.reply(&filter)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// addr::remote() filter only look at remote_addr from TCP socket.
|
||||||
|
// HTTP headers are not considered. This also means that innernet
|
||||||
|
// server would not function behind an HTTP proxy.
|
||||||
|
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_unparseable_public_key() -> Result<()> {
|
||||||
|
let server = test::Server::new()?;
|
||||||
|
let filter = routes(server.context());
|
||||||
|
|
||||||
|
// Request from an unknown IP, trying to disguise as an admin using HTTP headers.
|
||||||
|
let res = server
|
||||||
|
.request_from_ip("10.80.80.80")
|
||||||
|
.path("/v1/admin/peers")
|
||||||
|
.header(shared::INNERNET_PUBKEY_HEADER, "")
|
||||||
|
.reply(&filter)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// addr::remote() filter only look at remote_addr from TCP socket.
|
||||||
|
// HTTP headers are not considered. This also means that innernet
|
||||||
|
// server would not function behind an HTTP proxy.
|
||||||
|
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use shared::{Cidr, CidrContents, PeerContents};
|
||||||
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
|
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
use warp::test::RequestBuilder;
|
use warp::test::RequestBuilder;
|
||||||
use wgctrl::{InterfaceName, KeyPair};
|
use wgctrl::{InterfaceName, Key, KeyPair};
|
||||||
|
|
||||||
pub const ROOT_CIDR: &str = "10.80.0.0/15";
|
pub const ROOT_CIDR: &str = "10.80.0.0/15";
|
||||||
pub const SERVER_CIDR: &str = "10.80.0.1/32";
|
pub const SERVER_CIDR: &str = "10.80.0.1/32";
|
||||||
|
@ -47,6 +47,7 @@ pub struct Server {
|
||||||
endpoints: Arc<Endpoints>,
|
endpoints: Arc<Endpoints>,
|
||||||
interface: InterfaceName,
|
interface: InterfaceName,
|
||||||
conf: ServerConfig,
|
conf: ServerConfig,
|
||||||
|
public_key: Key,
|
||||||
// The directory will be removed during destruction.
|
// The directory will be removed during destruction.
|
||||||
_test_dir: TempDir,
|
_test_dir: TempDir,
|
||||||
}
|
}
|
||||||
|
@ -56,6 +57,7 @@ impl Server {
|
||||||
let test_dir = tempfile::tempdir()?;
|
let test_dir = tempfile::tempdir()?;
|
||||||
let test_dir_path = test_dir.path();
|
let test_dir_path = test_dir.path();
|
||||||
|
|
||||||
|
let public_key = Key::generate_private().generate_public();
|
||||||
// Run the init wizard to initialize the database and create basic
|
// Run the init wizard to initialize the database and create basic
|
||||||
// cidrs and peers.
|
// cidrs and peers.
|
||||||
let interface = "test".to_string();
|
let interface = "test".to_string();
|
||||||
|
@ -116,6 +118,7 @@ impl Server {
|
||||||
db,
|
db,
|
||||||
endpoints,
|
endpoints,
|
||||||
interface,
|
interface,
|
||||||
|
public_key,
|
||||||
_test_dir: test_dir,
|
_test_dir: test_dir,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -129,12 +132,32 @@ impl Server {
|
||||||
db: self.db.clone(),
|
db: self.db.clone(),
|
||||||
interface: self.interface.clone(),
|
interface: self.interface.clone(),
|
||||||
endpoints: self.endpoints.clone(),
|
endpoints: self.endpoints.clone(),
|
||||||
|
public_key: self.public_key.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wg_conf_path(&self) -> PathBuf {
|
pub fn wg_conf_path(&self) -> PathBuf {
|
||||||
self.conf.config_path(&self.interface)
|
self.conf.config_path(&self.interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn request_from_ip(&self, ip_str: &str) -> RequestBuilder {
|
||||||
|
let port = 54321u16;
|
||||||
|
warp::test::request()
|
||||||
|
.remote_addr(SocketAddr::new(ip_str.parse().unwrap(), port))
|
||||||
|
.header(shared::INNERNET_PUBKEY_HEADER, self.public_key.to_base64())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_request_from_ip(&self, ip_str: &str) -> RequestBuilder {
|
||||||
|
self.request_from_ip(ip_str)
|
||||||
|
.method("POST")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_request_from_ip(&self, ip_str: &str) -> RequestBuilder {
|
||||||
|
self.request_from_ip(ip_str)
|
||||||
|
.method("PUT")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_cidr(db: &Connection, name: &str, cidr_str: &str) -> Result<Cidr> {
|
pub fn create_cidr(db: &Connection, name: &str, cidr_str: &str) -> Result<Cidr> {
|
||||||
|
@ -154,23 +177,6 @@ pub fn create_cidr(db: &Connection, name: &str, cidr_str: &str) -> Result<Cidr>
|
||||||
// Below are helper functions for writing tests.
|
// Below are helper functions for writing tests.
|
||||||
//
|
//
|
||||||
|
|
||||||
pub fn request_from_ip(ip_str: &str) -> RequestBuilder {
|
|
||||||
let port = 54321u16;
|
|
||||||
warp::test::request().remote_addr(SocketAddr::new(ip_str.parse().unwrap(), port))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post_request_from_ip(ip_str: &str) -> RequestBuilder {
|
|
||||||
request_from_ip(ip_str)
|
|
||||||
.method("POST")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put_request_from_ip(ip_str: &str) -> RequestBuilder {
|
|
||||||
request_from_ip(ip_str)
|
|
||||||
.method("PUT")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn peer_contents(
|
pub fn peer_contents(
|
||||||
name: &str,
|
name: &str,
|
||||||
ip_str: &str,
|
ip_str: &str,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
||||||
};
|
};
|
||||||
use wgctrl::InterfaceName;
|
use wgctrl::InterfaceName;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct InterfaceConfig {
|
pub struct InterfaceConfig {
|
||||||
/// The information to bring up the interface.
|
/// The information to bring up the interface.
|
||||||
|
@ -21,7 +21,7 @@ pub struct InterfaceConfig {
|
||||||
pub server: ServerInfo,
|
pub server: ServerInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct InterfaceInfo {
|
pub struct InterfaceInfo {
|
||||||
/// The interface name (i.e. "tonari")
|
/// The interface name (i.e. "tonari")
|
||||||
|
@ -38,7 +38,7 @@ pub struct InterfaceInfo {
|
||||||
pub listen_port: Option<u16>,
|
pub listen_port: Option<u16>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Debug)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
/// The server's WireGuard public key
|
/// The server's WireGuard public key
|
||||||
|
|
|
@ -27,7 +27,8 @@ lazy_static! {
|
||||||
pub static ref REDEEM_TRANSITION_WAIT: Duration = Duration::from_secs(5);
|
pub static ref REDEEM_TRANSITION_WAIT: Duration = Duration::from_secs(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static PERSISTENT_KEEPALIVE_INTERVAL_SECS: u16 = 25;
|
pub const PERSISTENT_KEEPALIVE_INTERVAL_SECS: u16 = 25;
|
||||||
|
pub const INNERNET_PUBKEY_HEADER: &str = "X-Innernet-Server-Key";
|
||||||
|
|
||||||
pub type Error = Box<dyn std::error::Error>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
|
|
|
@ -366,7 +366,7 @@ pub fn apply(builder: DeviceConfigBuilder, iface: &InterfaceName) -> io::Result<
|
||||||
/// `Key`s, especially ones created from external data.
|
/// `Key`s, especially ones created from external data.
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone)]
|
||||||
pub struct Key([u8; 32]);
|
pub struct Key(pub [u8; 32]);
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
impl Key {
|
impl Key {
|
||||||
|
|
Loading…
Reference in New Issue