mirror of
https://github.com/tonarino/innernet.git
synced 2025-01-04 03:48:20 +02:00
parent
d8de58c8a8
commit
c01c2be4bb
324
Cargo.lock
generated
324
Cargo.lock
generated
@ -1,7 +1,5 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.4.7"
|
||||
@ -80,15 +78,6 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@ -200,81 +189,6 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cd5a7748210e7ec1a9696610b1015e6e31fbf58f77a160801f124bd1c36592a"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52fb27eab85b17fbb9f6fd667089e07d6a2eb8743d02639ee7f6a7a7729c9c94"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.0.2"
|
||||
@ -287,16 +201,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "4.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dialoguer"
|
||||
version = "0.8.0"
|
||||
@ -365,20 +269,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d5813545e459ad3ca1bff9915e9ad7f1a47dc6a91b627ce321d5863b7dd253"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.14"
|
||||
@ -386,7 +276,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -395,18 +284,6 @@ version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5629433c555de3d82861a7a4e3794a4c40040390907cfbfd7143a92a426c23"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.14"
|
||||
@ -420,7 +297,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
@ -453,25 +329,6 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.9.1"
|
||||
@ -490,31 +347,6 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"http",
|
||||
"mime",
|
||||
"sha-1",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers-core"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
|
||||
dependencies = [
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.2"
|
||||
@ -599,7 +431,6 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
@ -624,16 +455,6 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "1.0.3"
|
||||
@ -724,31 +545,6 @@ version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.11"
|
||||
@ -806,12 +602,6 @@ version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
@ -1053,12 +843,6 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@ -1096,26 +880,13 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "server"
|
||||
version = "1.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
"colored",
|
||||
"crossbeam",
|
||||
"dashmap",
|
||||
"dialoguer",
|
||||
"hyper",
|
||||
"indoc",
|
||||
@ -1139,23 +910,9 @@ dependencies = [
|
||||
"toml",
|
||||
"ureq",
|
||||
"url",
|
||||
"warp",
|
||||
"wgctrl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b659df5fc3ce22274daac600ffb845300bd2125bcfaec047823075afdab81c00"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared"
|
||||
version = "1.2.0"
|
||||
@ -1180,12 +937,6 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
@ -1323,16 +1074,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.2.0"
|
||||
@ -1355,9 +1096,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
@ -1375,31 +1114,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e177a5d8c3bf36de9ebe6d58537d8879e964332f93fb3339e43f618c81361af0"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
@ -1422,7 +1136,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
@ -1448,15 +1161,6 @@ version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.5"
|
||||
@ -1554,32 +1258,6 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "warp"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/tonarino/warp#bd70fb6249810eb63e9acdfe2dcc79e41ab53304"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures",
|
||||
"headers",
|
||||
"http",
|
||||
"hyper",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"scoped-tls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
|
@ -13,11 +13,10 @@ name = "innernet-server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
bytes = "1"
|
||||
colored = "2"
|
||||
crossbeam = "0.8"
|
||||
dashmap = "4"
|
||||
dialoguer = "0.8"
|
||||
hyper = "0.14"
|
||||
hyper = { version = "0.14", default-features = false, features = ["http1", "server", "runtime", "stream"] }
|
||||
indoc = "1"
|
||||
ipnetwork = { git = "https://github.com/mcginty/ipnetwork" } # pending https://github.com/achanda/ipnetwork/pull/129
|
||||
libc = "0.2"
|
||||
@ -33,11 +32,10 @@ shared = { path = "../shared" }
|
||||
structopt = "0.3"
|
||||
subtle = "2"
|
||||
thiserror = "1"
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
|
||||
toml = "0.5"
|
||||
ureq = { version = "2", default-features = false }
|
||||
url = "2"
|
||||
warp = { git = "https://github.com/tonarino/warp", default-features = false } # pending https://github.com/seanmonstar/warp/issues/830
|
||||
wgctrl = { path = "../wgctrl-rs" }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
|
@ -2,82 +2,60 @@
|
||||
//!
|
||||
//! A peer belongs to one parent CIDR, and can by default see all peers within that parent.
|
||||
|
||||
use crate::{db::DatabaseAssociation, form_body, with_admin_session, AdminSession, Context};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
db::DatabaseAssociation,
|
||||
util::{form_body, json_response, status_response},
|
||||
ServerError, Session,
|
||||
};
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use shared::AssociationContents;
|
||||
use warp::{http::StatusCode, Filter};
|
||||
|
||||
pub mod routes {
|
||||
use super::*;
|
||||
|
||||
pub fn all(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path("associations").and(
|
||||
list(context.clone())
|
||||
.or(create(context.clone()))
|
||||
.or(delete(context)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path::end()
|
||||
.and(warp::get())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::list)
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(form_body())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::create)
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::delete())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::delete)
|
||||
pub async fn routes(
|
||||
req: Request<Body>,
|
||||
mut components: VecDeque<String>,
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
match (req.method(), components.pop_front().as_deref()) {
|
||||
(&Method::GET, None) => handlers::list(session).await,
|
||||
(&Method::POST, None) => {
|
||||
let form = form_body(req).await?;
|
||||
handlers::create(form, session).await
|
||||
},
|
||||
(&Method::DELETE, Some(id)) => {
|
||||
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
||||
handlers::delete(id, session).await
|
||||
},
|
||||
_ => Err(ServerError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
mod handlers {
|
||||
|
||||
use super::*;
|
||||
|
||||
pub async fn create(
|
||||
contents: AssociationContents,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
|
||||
DatabaseAssociation::create(&conn, contents)?;
|
||||
|
||||
Ok(StatusCode::CREATED)
|
||||
status_response(StatusCode::CREATED)
|
||||
}
|
||||
|
||||
pub async fn list(session: AdminSession) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn list(session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let auths = DatabaseAssociation::list(&conn)?;
|
||||
|
||||
Ok(warp::reply::json(&auths))
|
||||
json_response(&auths)
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
id: i64,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn delete(id: i64, session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
DatabaseAssociation::delete(&conn, id)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
status_response(StatusCode::NO_CONTENT)
|
||||
}
|
||||
}
|
||||
|
@ -1,86 +1,60 @@
|
||||
use crate::{db::DatabaseCidr, form_body, with_admin_session, AdminSession, Context};
|
||||
use shared::CidrContents;
|
||||
use warp::{
|
||||
http::{response::Response, StatusCode},
|
||||
Filter,
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
db::DatabaseCidr,
|
||||
util::{form_body, json_response, status_response},
|
||||
ServerError, Session,
|
||||
};
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use shared::CidrContents;
|
||||
|
||||
pub mod routes {
|
||||
use super::*;
|
||||
|
||||
pub fn all(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path("cidrs").and(
|
||||
list(context.clone())
|
||||
.or(create(context.clone()))
|
||||
.or(delete(context)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn list(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::end()
|
||||
.and(warp::get())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::list)
|
||||
}
|
||||
|
||||
pub fn create(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(form_body())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::create)
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::delete())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::delete)
|
||||
pub async fn routes(
|
||||
req: Request<Body>,
|
||||
mut components: VecDeque<String>,
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
match (req.method(), components.pop_front().as_deref()) {
|
||||
(&Method::GET, None) => handlers::list(session).await,
|
||||
(&Method::POST, None) => {
|
||||
let form = form_body(req).await?;
|
||||
handlers::create(form, session).await
|
||||
},
|
||||
(&Method::DELETE, Some(id)) => {
|
||||
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
||||
handlers::delete(id, session).await
|
||||
},
|
||||
_ => Err(ServerError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
mod handlers {
|
||||
use crate::util::json_status_response;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub async fn create(
|
||||
contents: CidrContents,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
|
||||
let cidr = DatabaseCidr::create(&conn, contents)?;
|
||||
|
||||
let response = Response::builder()
|
||||
.status(StatusCode::CREATED)
|
||||
.body(serde_json::to_string(&cidr).unwrap())
|
||||
.unwrap();
|
||||
Ok(response)
|
||||
json_status_response(&cidr, StatusCode::CREATED)
|
||||
}
|
||||
|
||||
pub async fn list(session: AdminSession) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn list(session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let cidrs = DatabaseCidr::list(&conn)?;
|
||||
|
||||
Ok(warp::reply::json(&cidrs))
|
||||
json_response(&cidrs)
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
id: i64,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn delete(id: i64, session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
DatabaseCidr::delete(&conn, id)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
status_response(StatusCode::NO_CONTENT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,6 +63,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{test, DatabasePeer};
|
||||
use anyhow::Result;
|
||||
use bytes::Buf;
|
||||
use shared::Cidr;
|
||||
|
||||
#[tokio::test]
|
||||
@ -103,17 +78,14 @@ mod tests {
|
||||
parent: Some(test::ROOT_CIDR_ID),
|
||||
};
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), 201);
|
||||
|
||||
let cidr_res: Cidr = serde_json::from_slice(&res.body())?;
|
||||
let whole_body = hyper::body::aggregate(res).await?;
|
||||
let cidr_res: Cidr = serde_json::from_reader(whole_body.reader())?;
|
||||
assert_eq!(contents, cidr_res.contents);
|
||||
|
||||
let new_cidrs = DatabaseCidr::list(&server.db().lock())?;
|
||||
@ -132,15 +104,12 @@ mod tests {
|
||||
parent: Some(test::ROOT_CIDR_ID),
|
||||
};
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
let cidr_res: Cidr = serde_json::from_slice(&res.body())?;
|
||||
let whole_body = hyper::body::aggregate(res).await?;
|
||||
let cidr_res: Cidr = serde_json::from_reader(whole_body.reader())?;
|
||||
|
||||
let contents = CidrContents {
|
||||
name: "experimental".to_string(),
|
||||
@ -148,10 +117,7 @@ mod tests {
|
||||
parent: Some(cidr_res.id),
|
||||
};
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
assert!(!res.status().is_success());
|
||||
|
||||
@ -168,12 +134,8 @@ mod tests {
|
||||
parent: Some(test::ROOT_CIDR_ID),
|
||||
};
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::USER1_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::USER1_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
assert!(!res.status().is_success());
|
||||
|
||||
@ -189,12 +151,8 @@ mod tests {
|
||||
cidr: test::EXPERIMENTAL_CIDR.parse()?,
|
||||
parent: Some(test::ROOT_CIDR_ID),
|
||||
};
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
|
||||
@ -204,12 +162,8 @@ mod tests {
|
||||
parent: Some(test::ROOT_CIDR_ID),
|
||||
};
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
assert!(!res.status().is_success());
|
||||
|
||||
@ -225,12 +179,8 @@ mod tests {
|
||||
cidr: "10.80.1.0/21".parse()?,
|
||||
parent: Some(test::ROOT_CIDR_ID),
|
||||
};
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/cidrs")
|
||||
.body(serde_json::to_string(&contents)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/cidrs", &contents)
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
@ -258,31 +208,32 @@ mod tests {
|
||||
},
|
||||
)?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/cidrs/{}", experimental_cidr.id),
|
||||
)
|
||||
.await;
|
||||
// Should fail because child CIDR exists.
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/cidrs/{}", experimental_subcidr.id))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/cidrs/{}", experimental_subcidr.id),
|
||||
)
|
||||
.await;
|
||||
// Deleting child "leaf" CIDR should fail because peer exists inside it.
|
||||
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/cidrs/{}", experimental_cidr.id),
|
||||
)
|
||||
.await;
|
||||
// Now deleting parent CIDR should work because child is gone.
|
||||
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||
@ -312,13 +263,12 @@ mod tests {
|
||||
)?,
|
||||
)?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/cidrs/{}", experimental_cidr.id))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/cidrs/{}", experimental_cidr.id),
|
||||
)
|
||||
.await;
|
||||
// Deleting CIDR should fail because peer exists inside it.
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
@ -1,17 +1,26 @@
|
||||
use warp::Filter;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::Context;
|
||||
use hyper::{Body, Request, Response};
|
||||
|
||||
use crate::{ServerError, Session};
|
||||
|
||||
pub mod association;
|
||||
pub mod cidr;
|
||||
pub mod peer;
|
||||
|
||||
pub fn routes(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path("admin").and(
|
||||
association::routes::all(context.clone())
|
||||
.or(cidr::routes::all(context.clone()))
|
||||
.or(peer::routes::all(context.clone())),
|
||||
)
|
||||
pub async fn routes(
|
||||
req: Request<Body>,
|
||||
mut components: VecDeque<String>,
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
if !session.admin_capable() {
|
||||
return Err(ServerError::Unauthorized);
|
||||
}
|
||||
|
||||
match components.pop_front().as_deref() {
|
||||
Some("associations") => association::routes(req, components, session).await,
|
||||
Some("cidrs") => cidr::routes(req, components, session).await,
|
||||
Some("peers") => peer::routes(req, components, session).await,
|
||||
_ => Err(ServerError::NotFound),
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,47 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
api::inject_endpoints, db::DatabasePeer, with_admin_session, AdminSession, Context, ServerError,
|
||||
api::inject_endpoints,
|
||||
db::DatabasePeer,
|
||||
util::{form_body, json_response, json_status_response, status_response},
|
||||
ServerError, Session,
|
||||
};
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use shared::PeerContents;
|
||||
use warp::{
|
||||
http::{response::Response, StatusCode},
|
||||
Filter,
|
||||
};
|
||||
use wgctrl::DeviceConfigBuilder;
|
||||
|
||||
pub mod routes {
|
||||
use crate::form_body;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn all(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path("peers").and(
|
||||
list(context.clone())
|
||||
.or(list(context.clone()))
|
||||
.or(create(context.clone()))
|
||||
.or(update(context.clone()))
|
||||
.or(delete(context)),
|
||||
)
|
||||
}
|
||||
|
||||
// POST /v1/admin/peers
|
||||
pub fn create(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::end()
|
||||
.and(warp::post())
|
||||
.and(form_body())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::create)
|
||||
}
|
||||
|
||||
// PUT /v1/admin/peers/:id
|
||||
pub fn update(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::put())
|
||||
.and(form_body())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::update)
|
||||
}
|
||||
|
||||
// GET /v1/admin/peers
|
||||
pub fn list(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::end()
|
||||
.and(warp::get())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::list)
|
||||
}
|
||||
|
||||
// DELETE /v1/admin/peers/:id
|
||||
pub fn delete(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path::param()
|
||||
.and(warp::path::end())
|
||||
.and(warp::delete())
|
||||
.and(with_admin_session(context))
|
||||
.and_then(handlers::delete)
|
||||
pub async fn routes(
|
||||
req: Request<Body>,
|
||||
mut components: VecDeque<String>,
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
match (req.method(), components.pop_front().as_deref()) {
|
||||
(&Method::GET, None) => handlers::list(session).await,
|
||||
(&Method::POST, None) => {
|
||||
let form = form_body(req).await?;
|
||||
handlers::create(form, session).await
|
||||
},
|
||||
(&Method::PUT, Some(id)) => {
|
||||
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
||||
let form = form_body(req).await?;
|
||||
handlers::update(id, form, session).await
|
||||
},
|
||||
(&Method::DELETE, Some(id)) => {
|
||||
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
||||
handlers::delete(id, session).await
|
||||
},
|
||||
_ => Err(ServerError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
mod handlers {
|
||||
|
||||
use super::*;
|
||||
|
||||
pub async fn create(
|
||||
form: PeerContents,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
|
||||
let peer = DatabasePeer::create(&conn, form)?;
|
||||
@ -91,43 +56,37 @@ mod handlers {
|
||||
log::info!("updated WireGuard interface, adding {}", &*peer);
|
||||
}
|
||||
|
||||
let response = Response::builder()
|
||||
.status(StatusCode::CREATED)
|
||||
.body(serde_json::to_string(&*peer).unwrap());
|
||||
Ok(response)
|
||||
json_status_response(&*peer, StatusCode::CREATED)
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
id: i64,
|
||||
form: PeerContents,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let mut peer = DatabasePeer::get(&conn, id)?;
|
||||
peer.update(&conn, form)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
status_response(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
/// List all peers, including disabled ones. This is an admin-only endpoint.
|
||||
pub async fn list(session: AdminSession) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn list(session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let mut peers = DatabasePeer::list(&conn)?
|
||||
.into_iter()
|
||||
.map(|peer| peer.inner)
|
||||
.collect::<Vec<_>>();
|
||||
inject_endpoints(&session, &mut peers);
|
||||
Ok(warp::reply::json(&peers))
|
||||
json_response(&peers)
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
id: i64,
|
||||
session: AdminSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn delete(id: i64, session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
DatabasePeer::disable(&conn, id)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
status_response(StatusCode::NO_CONTENT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,6 +95,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test;
|
||||
use anyhow::Result;
|
||||
use bytes::Buf;
|
||||
use shared::Peer;
|
||||
|
||||
#[tokio::test]
|
||||
@ -146,17 +106,15 @@ mod tests {
|
||||
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.64.4")?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::CREATED);
|
||||
// The response contains the new peer information.
|
||||
let peer_res: Peer = serde_json::from_slice(&res.body())?;
|
||||
let whole_body = hyper::body::aggregate(res).await?;
|
||||
let peer_res: Peer = serde_json::from_reader(whole_body.reader())?;
|
||||
|
||||
assert_eq!(peer, peer_res.contents);
|
||||
|
||||
// The number of peer entries in the database increased by 1.
|
||||
@ -172,12 +130,8 @@ mod tests {
|
||||
|
||||
let peer = test::developer_peer_contents("devel oper", "10.80.64.4")?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
@ -193,12 +147,8 @@ mod tests {
|
||||
// Try to add a peer with a name that is already taken.
|
||||
let peer = test::developer_peer_contents("developer2", "10.80.64.4")?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
@ -219,12 +169,8 @@ mod tests {
|
||||
// Try to add a peer with an IP that is already taken.
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.64.3")?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
@ -239,37 +185,27 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_add_peer_with_outside_cidr_range_ip() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
||||
|
||||
// Try to add IP outside of the CIDR network.
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.65.4")?;
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
// Try to use the network address as peer IP.
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.64.0")?;
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
// Try to use the broadcast address as peer IP.
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.64.255")?;
|
||||
let res = server
|
||||
.post_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::ADMIN_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
|
||||
|
||||
@ -287,12 +223,8 @@ mod tests {
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.64.4")?;
|
||||
|
||||
// Try to create a new developer peer from a user peer.
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.post_request_from_ip(test::USER1_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(test::USER1_PEER_IP, "POST", "/v1/admin/peers", &peer)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
@ -311,12 +243,13 @@ mod tests {
|
||||
};
|
||||
|
||||
// Try to create a new developer peer from a user peer.
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.put_request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path(&format!("/v1/admin/peers/{}", test::DEVELOPER1_PEER_ID))
|
||||
.body(serde_json::to_string(&change)?)
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"PUT",
|
||||
&format!("/v1/admin/peers/{}", test::DEVELOPER1_PEER_ID),
|
||||
&change,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::NO_CONTENT);
|
||||
@ -333,12 +266,13 @@ mod tests {
|
||||
let peer = test::developer_peer_contents("developer3", "10.80.64.4")?;
|
||||
|
||||
// Try to create a new developer peer from a user peer.
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.put_request_from_ip(test::USER1_PEER_IP)
|
||||
.path(&format!("/v1/admin/peers/{}", test::ADMIN_PEER_ID))
|
||||
.body(serde_json::to_string(&peer)?)
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::USER1_PEER_IP,
|
||||
"PUT",
|
||||
&format!("/v1/admin/peers/{}", test::DEVELOPER1_PEER_ID),
|
||||
&peer,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
@ -349,16 +283,14 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_list_all_peers_from_admin() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.reply(&filter)
|
||||
.request(test::ADMIN_PEER_IP, "GET", "/v1/admin/peers")
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let peers: Vec<Peer> = serde_json::from_slice(&res.body())?;
|
||||
let whole_body = hyper::body::aggregate(res).await?;
|
||||
let peers: Vec<Peer> = serde_json::from_reader(whole_body.reader())?;
|
||||
let peer_names = peers.iter().map(|p| &p.contents.name).collect::<Vec<_>>();
|
||||
// An admin peer should see all the peers.
|
||||
assert_eq!(
|
||||
@ -379,11 +311,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_list_all_peers_from_non_admin() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||
.path("/v1/admin/peers")
|
||||
.reply(&filter)
|
||||
.request(test::DEVELOPER1_PEER_IP, "GET", "/v1/admin/peers")
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
@ -394,15 +323,14 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_delete() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/peers/{}", test::USER1_PEER_ID),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(res.status().is_success());
|
||||
@ -418,15 +346,15 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_delete_from_non_admin() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
let old_peers = DatabasePeer::list(&server.db().lock())?;
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::DEVELOPER1_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/peers/{}", test::USER1_PEER_ID),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
@ -441,13 +369,13 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_delete_unknown_id() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
let res = server
|
||||
.request_from_ip(test::ADMIN_PEER_IP)
|
||||
.method("DELETE")
|
||||
.path(&format!("/v1/admin/peers/{}", test::USER1_PEER_ID + 100))
|
||||
.reply(&filter)
|
||||
.request(
|
||||
test::ADMIN_PEER_IP,
|
||||
"DELETE",
|
||||
&format!("/v1/admin/peers/{}", test::USER1_PEER_ID + 100),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Trying to delete a peer of non-existing ID will result in error.
|
||||
|
@ -10,7 +10,7 @@ pub mod user;
|
||||
pub fn inject_endpoints(session: &Session, peers: &mut Vec<Peer>) {
|
||||
for mut peer in peers {
|
||||
if peer.contents.endpoint.is_none() {
|
||||
if let Some(endpoint) = session.context.endpoints.get(&peer.public_key) {
|
||||
if let Some(endpoint) = session.context.endpoints.read().get(&peer.public_key) {
|
||||
peer.contents.endpoint = Some(endpoint.to_owned().into());
|
||||
}
|
||||
}
|
||||
|
@ -1,57 +1,42 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::{
|
||||
api::inject_endpoints,
|
||||
db::{DatabaseCidr, DatabasePeer},
|
||||
form_body, with_session, with_unredeemed_session, Context, ServerError, Session,
|
||||
UnredeemedSession,
|
||||
util::{form_body, json_response, status_response},
|
||||
ServerError, Session,
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use shared::{EndpointContents, PeerContents, RedeemContents, State, REDEEM_TRANSITION_WAIT};
|
||||
use warp::Filter;
|
||||
use wgctrl::DeviceConfigBuilder;
|
||||
|
||||
pub fn routes(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path("user").and(
|
||||
routes::state(context.clone())
|
||||
.or(routes::redeem(context.clone()))
|
||||
.or(routes::override_endpoint(context.clone())),
|
||||
)
|
||||
}
|
||||
|
||||
pub mod routes {
|
||||
use super::*;
|
||||
|
||||
pub fn state(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path("state")
|
||||
.and(warp::path::end())
|
||||
.and(warp::get())
|
||||
.and(with_session(context))
|
||||
.and_then(handlers::state)
|
||||
}
|
||||
|
||||
pub fn redeem(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path("redeem")
|
||||
.and(warp::path::end())
|
||||
.and(warp::post())
|
||||
.and(form_body())
|
||||
.and(with_unredeemed_session(context))
|
||||
.and_then(handlers::redeem)
|
||||
}
|
||||
|
||||
pub fn override_endpoint(
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = (impl warp::Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path("endpoint")
|
||||
.and(warp::path::end())
|
||||
.and(warp::put())
|
||||
.and(form_body())
|
||||
.and(with_session(context))
|
||||
.and_then(handlers::endpoint)
|
||||
pub async fn routes(
|
||||
req: Request<Body>,
|
||||
mut components: VecDeque<String>,
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
match (req.method(), components.pop_front().as_deref()) {
|
||||
(&Method::GET, Some("state")) => {
|
||||
if !session.user_capable() {
|
||||
return Err(ServerError::Unauthorized);
|
||||
}
|
||||
handlers::state(session).await
|
||||
},
|
||||
(&Method::POST, Some("redeem")) => {
|
||||
if !session.redeemable() {
|
||||
return Err(ServerError::Unauthorized);
|
||||
}
|
||||
let form = form_body(req).await?;
|
||||
handlers::redeem(form, session).await
|
||||
},
|
||||
(&Method::PUT, Some("endpoint")) => {
|
||||
if !session.user_capable() {
|
||||
return Err(ServerError::Unauthorized);
|
||||
}
|
||||
let form = form_body(req).await?;
|
||||
handlers::endpoint(form, session).await
|
||||
},
|
||||
_ => Err(ServerError::NotFound),
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +47,7 @@ mod handlers {
|
||||
///
|
||||
/// This endpoint returns the visible CIDRs and Peers, providing all the necessary
|
||||
/// information for the peer to create connections to all of them.
|
||||
pub async fn state(session: Session) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
pub async fn state(session: Session) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let selected_peer = DatabasePeer::get(&conn, session.peer.id)?;
|
||||
|
||||
@ -74,8 +59,7 @@ mod handlers {
|
||||
.map(|p| p.inner)
|
||||
.collect();
|
||||
inject_endpoints(&session, &mut peers);
|
||||
|
||||
Ok(warp::reply::json(&State { cidrs, peers }))
|
||||
json_response(State { peers, cidrs })
|
||||
}
|
||||
|
||||
/// Redeems an invitation. An invitation includes a WireGuard keypair generated by either the server
|
||||
@ -88,8 +72,8 @@ mod handlers {
|
||||
/// it is called and succeeds, it cannot be called again.
|
||||
pub async fn redeem(
|
||||
form: RedeemContents,
|
||||
session: UnredeemedSession,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
session: Session,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let mut selected_peer = DatabasePeer::get(&conn, session.peer.id)?;
|
||||
|
||||
@ -97,12 +81,14 @@ mod handlers {
|
||||
.map_err(|_| ServerError::WireGuard)?;
|
||||
|
||||
if selected_peer.is_redeemed {
|
||||
Ok(StatusCode::GONE)
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::GONE)
|
||||
.body(Body::empty())?)
|
||||
} else {
|
||||
selected_peer.redeem(&conn, &form.public_key)?;
|
||||
|
||||
if cfg!(not(test)) {
|
||||
let interface = session.context.interface.clone();
|
||||
let interface = session.context.interface;
|
||||
|
||||
// If we were to modify the WireGuard interface immediately, the HTTP response wouldn't
|
||||
// get through. Instead, we need to wait a reasonable amount for the HTTP response to
|
||||
@ -129,7 +115,7 @@ mod handlers {
|
||||
.ok();
|
||||
});
|
||||
}
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
status_response(StatusCode::NO_CONTENT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,7 +130,7 @@ mod handlers {
|
||||
pub async fn endpoint(
|
||||
contents: EndpointContents,
|
||||
session: Session,
|
||||
) -> Result<impl warp::Reply, warp::Rejection> {
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let conn = session.context.db.lock();
|
||||
let mut selected_peer = DatabasePeer::get(&conn, session.peer.id)?;
|
||||
selected_peer.update(
|
||||
@ -155,7 +141,7 @@ mod handlers {
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
status_response(StatusCode::NO_CONTENT)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,22 +150,20 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{db::DatabaseAssociation, test};
|
||||
use anyhow::Result;
|
||||
use bytes::Buf;
|
||||
use shared::{AssociationContents, CidrContents, EndpointContents};
|
||||
use warp::http::StatusCode;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_state_from_developer1() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
let res = server
|
||||
.request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||
.path("/v1/user/state")
|
||||
.reply(&filter)
|
||||
.request(test::DEVELOPER1_PEER_IP, "GET", "/v1/user/state")
|
||||
.await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
|
||||
let State { peers, .. } = serde_json::from_slice(&res.body())?;
|
||||
let whole_body = hyper::body::aggregate(res).await?;
|
||||
let State { peers, .. } = serde_json::from_reader(whole_body.reader())?;
|
||||
let mut peer_names = peers.iter().map(|p| &p.contents.name).collect::<Vec<_>>();
|
||||
peer_names.sort();
|
||||
// Developers should see only peers in infra CIDR and developer CIDR.
|
||||
@ -194,15 +178,14 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_override_endpoint() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
assert_eq!(
|
||||
server
|
||||
.put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||
.path("/v1/user/endpoint")
|
||||
.body(serde_json::to_string(&EndpointContents::Set(
|
||||
"1.1.1.1:51820".parse().unwrap()
|
||||
))?)
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::DEVELOPER1_PEER_IP,
|
||||
"PUT",
|
||||
"/v1/user/endpoint",
|
||||
&EndpointContents::Set("1.1.1.1:51820".parse().unwrap())
|
||||
)
|
||||
.await
|
||||
.status(),
|
||||
StatusCode::NO_CONTENT
|
||||
@ -211,10 +194,12 @@ mod tests {
|
||||
println!("{}", serde_json::to_string(&EndpointContents::Unset)?);
|
||||
assert_eq!(
|
||||
server
|
||||
.put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||
.path("/v1/user/endpoint")
|
||||
.body(serde_json::to_string(&EndpointContents::Unset)?)
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::DEVELOPER1_PEER_IP,
|
||||
"PUT",
|
||||
"/v1/user/endpoint",
|
||||
&EndpointContents::Unset,
|
||||
)
|
||||
.await
|
||||
.status(),
|
||||
StatusCode::NO_CONTENT
|
||||
@ -222,10 +207,12 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
server
|
||||
.put_request_from_ip(test::DEVELOPER1_PEER_IP)
|
||||
.path("/v1/user/endpoint")
|
||||
.body("endpoint=blah")
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::DEVELOPER1_PEER_IP,
|
||||
"PUT",
|
||||
"/v1/user/endpoint",
|
||||
"endpoint=blah",
|
||||
)
|
||||
.await
|
||||
.status(),
|
||||
StatusCode::BAD_REQUEST
|
||||
@ -237,14 +224,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_list_peers_from_unknown_ip() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
// Request comes from an unknown IP.
|
||||
let res = server
|
||||
.request_from_ip("10.80.80.80")
|
||||
.path("/v1/user/state")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
let res = server.request("10.80.80.80", "GET", "/v1/user/state").await;
|
||||
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
@ -254,7 +236,6 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_list_peers_for_developer_subcidr() -> Result<()> {
|
||||
let server = test::Server::new()?;
|
||||
let filter = crate::routes(server.context());
|
||||
{
|
||||
let db = server.db.lock();
|
||||
let cidr = DatabaseCidr::create(
|
||||
@ -301,13 +282,10 @@ mod tests {
|
||||
}
|
||||
|
||||
for ip in &[test::DEVELOPER1_PEER_IP, test::EXPERIMENT_SUBCIDR_PEER_IP] {
|
||||
let res = server
|
||||
.request_from_ip(ip)
|
||||
.path("/v1/user/state")
|
||||
.reply(&filter)
|
||||
.await;
|
||||
let res = server.request(ip, "GET", "/v1/user/state").await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
let State { peers, .. } = serde_json::from_slice(&res.body())?;
|
||||
let whole_body = hyper::body::aggregate(res).await?;
|
||||
let State { peers, .. } = serde_json::from_reader(whole_body.reader())?;
|
||||
let mut peer_names = peers.iter().map(|p| &p.contents.name).collect::<Vec<_>>();
|
||||
peer_names.sort();
|
||||
// Developers should see only peers in infra CIDR and developer CIDR.
|
||||
@ -347,13 +325,9 @@ mod tests {
|
||||
peer_contents.is_redeemed = false;
|
||||
let _experiment_peer = DatabasePeer::create(&server.db().lock(), peer_contents)?;
|
||||
|
||||
let filter = crate::routes(server.context());
|
||||
|
||||
// Step 1: Ensure that before redeeming, other endpoints aren't yet accessible.
|
||||
let res = server
|
||||
.request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||
.path("/v1/user/state")
|
||||
.reply(&filter)
|
||||
.request(test::EXPERIMENT_SUBCIDR_PEER_IP, "GET", "/v1/user/state")
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
|
||||
|
||||
@ -362,27 +336,29 @@ mod tests {
|
||||
public_key: "YBVIgpfLbi/knrMCTEb0L6eVy0daiZnJJQkxBK9s+2I=".into(),
|
||||
};
|
||||
let res = server
|
||||
.post_request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||
.path("/v1/user/redeem")
|
||||
.body(serde_json::to_string(&body)?)
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::EXPERIMENT_SUBCIDR_PEER_IP,
|
||||
"POST",
|
||||
"/v1/user/redeem",
|
||||
&body,
|
||||
)
|
||||
.await;
|
||||
assert!(res.status().is_success());
|
||||
|
||||
// Step 3: Ensure that a second attempt at redemption DOESN'T work.
|
||||
let res = server
|
||||
.post_request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||
.path("/v1/user/redeem")
|
||||
.body(serde_json::to_string(&body)?)
|
||||
.reply(&filter)
|
||||
.form_request(
|
||||
test::EXPERIMENT_SUBCIDR_PEER_IP,
|
||||
"POST",
|
||||
"/v1/user/redeem",
|
||||
&body,
|
||||
)
|
||||
.await;
|
||||
assert!(res.status().is_client_error());
|
||||
|
||||
// Step 3: Ensure that after redemption, fetching state works.
|
||||
let res = server
|
||||
.request_from_ip(test::EXPERIMENT_SUBCIDR_PEER_IP)
|
||||
.path("/v1/user/state")
|
||||
.reply(&filter)
|
||||
.request(test::EXPERIMENT_SUBCIDR_PEER_IP, "GET", "/v1/user/state")
|
||||
.await;
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
Ok(())
|
||||
|
@ -201,9 +201,9 @@ impl DatabasePeer {
|
||||
cidr_id,
|
||||
public_key,
|
||||
endpoint,
|
||||
persistent_keepalive_interval,
|
||||
is_admin,
|
||||
is_disabled,
|
||||
persistent_keepalive_interval,
|
||||
is_redeemed,
|
||||
},
|
||||
}
|
||||
@ -223,7 +223,7 @@ impl DatabasePeer {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn get_from_ip(conn: &Connection, ip: IpAddr) -> Result<Self, ServerError> {
|
||||
pub fn get_from_ip(conn: &Connection, ip: IpAddr) -> Result<Self, rusqlite::Error> {
|
||||
let result = conn.query_row(
|
||||
"SELECT
|
||||
id, name, ip, cidr_id, public_key, endpoint, is_admin, is_disabled, is_redeemed
|
||||
|
@ -1,16 +1,25 @@
|
||||
use crossbeam::channel::{self, select};
|
||||
use dashmap::DashMap;
|
||||
use parking_lot::RwLock;
|
||||
use wgctrl::{DeviceInfo, InterfaceName};
|
||||
|
||||
use std::{io, net::SocketAddr, sync::Arc, thread, time::Duration};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
net::SocketAddr,
|
||||
sync::{
|
||||
mpsc::{sync_channel, SyncSender, TryRecvError},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub struct Endpoints {
|
||||
pub endpoints: Arc<DashMap<String, SocketAddr>>,
|
||||
stop_tx: channel::Sender<()>,
|
||||
pub endpoints: Arc<RwLock<HashMap<String, SocketAddr>>>,
|
||||
stop_tx: SyncSender<()>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Endpoints {
|
||||
type Target = DashMap<String, SocketAddr>;
|
||||
type Target = RwLock<HashMap<String, SocketAddr>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.endpoints
|
||||
@ -19,30 +28,28 @@ impl std::ops::Deref for Endpoints {
|
||||
|
||||
impl Endpoints {
|
||||
pub fn new(iface: &InterfaceName) -> Result<Self, io::Error> {
|
||||
let endpoints = Arc::new(DashMap::new());
|
||||
let (stop_tx, stop_rx) = channel::bounded(1);
|
||||
let endpoints = Arc::new(RwLock::new(HashMap::new()));
|
||||
let (stop_tx, stop_rx) = sync_channel(1);
|
||||
|
||||
let iface = iface.to_owned();
|
||||
let thread_endpoints = endpoints.clone();
|
||||
log::info!("spawning endpoint watch thread.");
|
||||
if cfg!(not(test)) {
|
||||
thread::spawn(move || loop {
|
||||
select! {
|
||||
recv(stop_rx) -> _ => {
|
||||
break;
|
||||
},
|
||||
default => {
|
||||
if let Ok(info) = DeviceInfo::get_by_name(&iface) {
|
||||
for peer in info.peers {
|
||||
if let Some(endpoint) = peer.config.endpoint {
|
||||
thread_endpoints.insert(peer.config.public_key.to_base64(), endpoint);
|
||||
}
|
||||
}
|
||||
if matches!(stop_rx.try_recv(), Ok(_) | Err(TryRecvError::Disconnected)) {
|
||||
break;
|
||||
}
|
||||
if let Ok(info) = DeviceInfo::get_by_name(&iface) {
|
||||
for peer in info.peers {
|
||||
if let Some(endpoint) = peer.config.endpoint {
|
||||
thread_endpoints
|
||||
.write()
|
||||
.insert(peer.config.public_key.to_base64(), endpoint);
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
});
|
||||
}
|
||||
Ok(Self { endpoints, stop_tx })
|
||||
|
@ -1,5 +1,7 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use hyper::{http, Body, Response, StatusCode};
|
||||
use thiserror::Error;
|
||||
use warp::{http::StatusCode, reject::Rejection};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServerError {
|
||||
@ -20,17 +22,15 @@ pub enum ServerError {
|
||||
|
||||
#[error("internal I/O error")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
impl warp::reject::Reject for ServerError {}
|
||||
#[error("JSON parsing/serialization error")]
|
||||
Json(#[from] serde_json::Error),
|
||||
|
||||
pub async fn handle_rejection(err: Rejection) -> Result<StatusCode, warp::Rejection> {
|
||||
eprintln!("rejection: {:?}", err);
|
||||
if let Some(error) = err.find::<ServerError>() {
|
||||
Ok(error.into())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
#[error("Generic HTTP error")]
|
||||
Http(#[from] http::Error),
|
||||
|
||||
#[error("Generic Hyper error")]
|
||||
Hyper(#[from] hyper::Error),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a ServerError> for StatusCode {
|
||||
@ -39,7 +39,7 @@ impl<'a> From<&'a ServerError> for StatusCode {
|
||||
match error {
|
||||
Unauthorized => StatusCode::UNAUTHORIZED,
|
||||
NotFound => StatusCode::NOT_FOUND,
|
||||
InvalidQuery => StatusCode::BAD_REQUEST,
|
||||
InvalidQuery | Json(_) => StatusCode::BAD_REQUEST,
|
||||
// Special-case the constraint violation situation.
|
||||
Database(rusqlite::Error::SqliteFailure(libsqlite3_sys::Error { code, .. }, ..))
|
||||
if *code == libsqlite3_sys::ErrorCode::ConstraintViolation =>
|
||||
@ -47,7 +47,19 @@ impl<'a> From<&'a ServerError> for StatusCode {
|
||||
StatusCode::BAD_REQUEST
|
||||
},
|
||||
Database(rusqlite::Error::QueryReturnedNoRows) => StatusCode::NOT_FOUND,
|
||||
WireGuard | Io(_) | Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
WireGuard | Io(_) | Database(_) | Http(_) | Hyper(_) => {
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ServerError> for Response<Body> {
|
||||
type Error = http::Error;
|
||||
|
||||
fn try_from(e: ServerError) -> Result<Self, Self::Error> {
|
||||
Response::builder()
|
||||
.status(StatusCode::from(&e))
|
||||
.body(Body::empty())
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
||||
})?;
|
||||
|
||||
let name: String = if let Some(name) = opts.network_name {
|
||||
name.clone()
|
||||
name
|
||||
} else {
|
||||
println!("Here you'll specify the network CIDR, which will encompass the entire network.");
|
||||
Input::with_theme(&theme)
|
||||
@ -115,7 +115,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
||||
};
|
||||
|
||||
let root_cidr: IpNetwork = if let Some(cidr) = opts.network_cidr {
|
||||
cidr.clone()
|
||||
cidr
|
||||
} else {
|
||||
Input::with_theme(&theme)
|
||||
.with_prompt("Network CIDR")
|
||||
@ -127,7 +127,7 @@ pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Erro
|
||||
let name = name.parse()?;
|
||||
|
||||
let endpoint: Endpoint = if let Some(endpoint) = opts.external_endpoint {
|
||||
endpoint.clone()
|
||||
endpoint
|
||||
} else {
|
||||
let external_ip: Option<IpAddr> = ureq::get("http://4.icanhazip.com")
|
||||
.call()
|
||||
|
@ -1,15 +1,15 @@
|
||||
use colored::*;
|
||||
use dialoguer::Confirm;
|
||||
use error::handle_rejection;
|
||||
use hyper::{server::conn::AddrStream, Body, Request};
|
||||
use hyper::{http, server::conn::AddrStream, Body, Request, Response};
|
||||
use indoc::printdoc;
|
||||
use ipnetwork::IpNetwork;
|
||||
use parking_lot::Mutex;
|
||||
use rusqlite::Connection;
|
||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::{AddCidrOpts, AddPeerOpts, IoErrorContext, INNERNET_PUBKEY_HEADER};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
collections::VecDeque,
|
||||
convert::TryInto,
|
||||
env,
|
||||
fs::File,
|
||||
io::prelude::*,
|
||||
@ -20,7 +20,6 @@ use std::{
|
||||
};
|
||||
use structopt::StructOpt;
|
||||
use subtle::ConstantTimeEq;
|
||||
use warp::{filters, Filter};
|
||||
use wgctrl::{DeviceConfigBuilder, DeviceInfo, InterfaceName, Key, PeerConfigBuilder};
|
||||
|
||||
pub mod api;
|
||||
@ -29,6 +28,7 @@ pub mod endpoints;
|
||||
pub mod error;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
pub mod util;
|
||||
|
||||
mod initialize;
|
||||
|
||||
@ -95,21 +95,17 @@ pub struct Session {
|
||||
pub peer: DatabasePeer,
|
||||
}
|
||||
|
||||
pub struct AdminSession(Session);
|
||||
impl Deref for AdminSession {
|
||||
type Target = Session;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
impl Session {
|
||||
pub fn admin_capable(&self) -> bool {
|
||||
self.peer.is_admin && self.user_capable()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UnredeemedSession(Session);
|
||||
impl Deref for UnredeemedSession {
|
||||
type Target = Session;
|
||||
pub fn user_capable(&self) -> bool {
|
||||
!self.peer.is_disabled && self.peer.is_redeemed
|
||||
}
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
pub fn redeemable(&self) -> bool {
|
||||
!self.peer.is_disabled && !self.peer.is_redeemed
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +227,11 @@ fn open_database_connection(
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(Connection::open(&database_path)?)
|
||||
let conn = Connection::open(&database_path)?;
|
||||
// Foreign key constraints aren't on in SQLite by default. Enable.
|
||||
conn.pragma_update(None, "foreign_keys", &1)?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
||||
fn add_peer(
|
||||
@ -304,11 +304,39 @@ fn add_cidr(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error> {
|
||||
if Confirm::with_theme(&*prompts::THEME)
|
||||
.with_prompt(&format!(
|
||||
"Permanently delete network \"{}\"?",
|
||||
interface.as_str_lossy().yellow()
|
||||
))
|
||||
.default(false)
|
||||
.interact()?
|
||||
{
|
||||
println!("{} bringing down interface (if up).", "[*]".dimmed());
|
||||
wg::down(interface).ok();
|
||||
let config = conf.config_path(interface);
|
||||
let data = conf.database_path(interface);
|
||||
std::fs::remove_file(&config)
|
||||
.with_path(&config)
|
||||
.map_err(|e| println!("[!] {}", e.to_string().yellow()))
|
||||
.ok();
|
||||
std::fs::remove_file(&data)
|
||||
.with_path(&data)
|
||||
.map_err(|e| println!("[!] {}", e.to_string().yellow()))
|
||||
.ok();
|
||||
println!(
|
||||
"{} network {} is uninstalled.",
|
||||
"[*]".dimmed(),
|
||||
interface.as_str_lossy().yellow()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serve(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error> {
|
||||
let config = ConfigFile::from_file(conf.config_path(interface))?;
|
||||
let conn = open_database_connection(interface, conf)?;
|
||||
// Foreign key constraints aren't on in SQLite by default. Enable.
|
||||
conn.pragma_update(None, "foreign_keys", &1)?;
|
||||
|
||||
let peers = DatabasePeer::list(&conn)?;
|
||||
let peer_configs = peers
|
||||
@ -343,55 +371,23 @@ async fn serve(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Err
|
||||
};
|
||||
|
||||
log::info!("innernet-server {} starting.", VERSION);
|
||||
let routes = routes(context.clone()).with(warp::log("warp")).boxed();
|
||||
|
||||
let listener = get_listener((config.address, config.listen_port).into(), interface)?;
|
||||
|
||||
let warp_svc = warp::service(routes);
|
||||
let make_svc = hyper::service::make_service_fn(move |socket: &AddrStream| {
|
||||
let remote_addr = socket.remote_addr();
|
||||
let warp_svc = warp_svc.clone();
|
||||
let context = context.clone();
|
||||
async move {
|
||||
let svc = hyper::service::service_fn(move |req: Request<Body>| {
|
||||
let warp_svc = warp_svc.clone();
|
||||
async move { warp_svc.call_with_addr(req, Some(remote_addr)).await }
|
||||
});
|
||||
Ok::<_, Infallible>(svc)
|
||||
Ok::<_, http::Error>(hyper::service::service_fn(move |req: Request<Body>| {
|
||||
hyper_service(req, context.clone(), remote_addr)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
hyper::Server::from_tcp(listener)?.serve(make_svc).await?;
|
||||
let server = hyper::Server::from_tcp(listener)?.serve(make_svc);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
server.await?;
|
||||
|
||||
fn uninstall(interface: &InterfaceName, conf: &ServerConfig) -> Result<(), Error> {
|
||||
if Confirm::with_theme(&*prompts::THEME)
|
||||
.with_prompt(&format!(
|
||||
"Permanently delete network \"{}\"?",
|
||||
interface.as_str_lossy().yellow()
|
||||
))
|
||||
.default(false)
|
||||
.interact()?
|
||||
{
|
||||
println!("{} bringing down interface (if up).", "[*]".dimmed());
|
||||
wg::down(interface).ok();
|
||||
let config = conf.config_path(interface);
|
||||
let data = conf.database_path(interface);
|
||||
std::fs::remove_file(&config)
|
||||
.with_path(&config)
|
||||
.map_err(|e| println!("[!] {}", e.to_string().yellow()))
|
||||
.ok();
|
||||
std::fs::remove_file(&data)
|
||||
.with_path(&data)
|
||||
.map_err(|e| println!("[!] {}", e.to_string().yellow()))
|
||||
.ok();
|
||||
println!(
|
||||
"{} network {} is uninstalled.",
|
||||
"[*]".dimmed(),
|
||||
interface.as_str_lossy().yellow()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -423,103 +419,68 @@ fn get_listener(addr: SocketAddr, _interface: &InterfaceName) -> Result<TcpListe
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
pub fn routes(
|
||||
pub(crate) async fn hyper_service(
|
||||
req: Request<Body>,
|
||||
context: Context,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path("v1")
|
||||
.and(api::admin::routes(context.clone()).or(api::user::routes(context)))
|
||||
.recover(handle_rejection)
|
||||
remote_addr: SocketAddr,
|
||||
) -> Result<Response<Body>, http::Error> {
|
||||
// Break the path into components.
|
||||
let components: VecDeque<_> = req
|
||||
.uri()
|
||||
.path()
|
||||
.trim_start_matches('/')
|
||||
.split('/')
|
||||
.map(String::from)
|
||||
.collect();
|
||||
|
||||
routes(req, context, remote_addr, components)
|
||||
.await
|
||||
.or_else(TryInto::try_into)
|
||||
}
|
||||
|
||||
pub fn form_body<T>() -> impl Filter<Extract = (T,), Error = warp::Rejection> + Clone
|
||||
where
|
||||
T: DeserializeOwned + Send,
|
||||
{
|
||||
warp::body::content_length_limit(1024 * 16).and(warp::body::json())
|
||||
async fn routes(
|
||||
req: Request<Body>,
|
||||
context: Context,
|
||||
remote_addr: SocketAddr,
|
||||
mut components: VecDeque<String>,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
// Must be "/v1/[something]"
|
||||
if components.pop_front().as_deref() != Some("v1") {
|
||||
Err(ServerError::NotFound)
|
||||
} else {
|
||||
let session = get_session(&req, context, remote_addr.ip())?;
|
||||
let component = components.pop_front();
|
||||
match component.as_deref() {
|
||||
Some("user") => api::user::routes(req, components, session).await,
|
||||
Some("admin") => api::admin::routes(req, components, session).await,
|
||||
_ => Err(ServerError::NotFound),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_unredeemed_session(
|
||||
fn get_session(
|
||||
req: &Request<Body>,
|
||||
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(
|
||||
context: Context,
|
||||
addr: Option<IpAddr>,
|
||||
pubkey: String,
|
||||
admin_only: bool,
|
||||
redeemed_only: bool,
|
||||
) -> Result<Session, warp::Rejection> {
|
||||
_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)?;
|
||||
addr: IpAddr,
|
||||
) -> Result<Session, ServerError> {
|
||||
let pubkey = req
|
||||
.headers()
|
||||
.get(INNERNET_PUBKEY_HEADER)
|
||||
.ok_or(ServerError::Unauthorized)?;
|
||||
let pubkey = pubkey.to_str().map_err(|_| ServerError::Unauthorized)?;
|
||||
let pubkey = Key::from_base64(&pubkey).map_err(|_| ServerError::Unauthorized)?;
|
||||
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).map_err(|e| match e {
|
||||
rusqlite::Error::QueryReturnedNoRows => ServerError::Unauthorized,
|
||||
e => ServerError::Database(e),
|
||||
})?;
|
||||
|
||||
if !peer.is_disabled
|
||||
&& (!admin_only || peer.is_admin)
|
||||
&& (!redeemed_only || peer.is_redeemed)
|
||||
{
|
||||
if !peer.is_disabled {
|
||||
return Ok(Session { context, peer });
|
||||
}
|
||||
}
|
||||
|
||||
Err(ServerError::Unauthorized.into())
|
||||
Err(ServerError::Unauthorized)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -527,8 +488,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test;
|
||||
use anyhow::Result;
|
||||
use hyper::StatusCode;
|
||||
use std::path::Path;
|
||||
use warp::http::StatusCode;
|
||||
|
||||
#[test]
|
||||
fn test_init_wizard() -> Result<()> {
|
||||
@ -543,17 +504,17 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn test_with_session_disguised_with_headers() -> 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")
|
||||
let req = Request::builder()
|
||||
.uri(format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP))
|
||||
.header("Forwarded", format!("for={}", test::ADMIN_PEER_IP))
|
||||
.header("X-Forwarded-For", test::ADMIN_PEER_IP)
|
||||
.header("X-Real-IP", test::ADMIN_PEER_IP)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
// Request from an unknown IP, trying to disguise as an admin using HTTP headers.
|
||||
let res = server.raw_request("10.80.80.80", req).await;
|
||||
|
||||
// addr::remote() filter only look at remote_addr from TCP socket.
|
||||
// HTTP headers are not considered. This also means that innernet
|
||||
@ -566,17 +527,16 @@ mod tests {
|
||||
#[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")
|
||||
let req = Request::builder()
|
||||
.uri(format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP))
|
||||
.header(shared::INNERNET_PUBKEY_HEADER, key.to_base64())
|
||||
.reply(&filter)
|
||||
.await;
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = server.raw_request("10.80.80.80", req).await;
|
||||
|
||||
// addr::remote() filter only look at remote_addr from TCP socket.
|
||||
// HTTP headers are not considered. This also means that innernet
|
||||
@ -589,15 +549,13 @@ mod tests {
|
||||
#[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;
|
||||
let req = Request::builder()
|
||||
.uri(format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP))
|
||||
.header(shared::INNERNET_PUBKEY_HEADER, "!!!")
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let res = server.raw_request("10.80.80.80", req).await;
|
||||
|
||||
// addr::remote() filter only look at remote_addr from TCP socket.
|
||||
// HTTP headers are not considered. This also means that innernet
|
||||
|
@ -6,12 +6,13 @@ use crate::{
|
||||
Context, ServerConfig,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use hyper::{header::HeaderValue, http, Body, Request, Response};
|
||||
use parking_lot::Mutex;
|
||||
use rusqlite::Connection;
|
||||
use serde::Serialize;
|
||||
use shared::{Cidr, CidrContents, PeerContents};
|
||||
use std::{net::SocketAddr, path::PathBuf, sync::Arc};
|
||||
use tempfile::TempDir;
|
||||
use warp::test::RequestBuilder;
|
||||
use wgctrl::{InterfaceName, Key, KeyPair};
|
||||
|
||||
pub const ROOT_CIDR: &str = "10.80.0.0/15";
|
||||
@ -144,23 +145,50 @@ impl Server {
|
||||
self.conf.config_path(&self.interface)
|
||||
}
|
||||
|
||||
pub fn request_from_ip(&self, ip_str: &str) -> RequestBuilder {
|
||||
pub async fn raw_request(&self, ip_str: &str, req: Request<Body>) -> Response<Body> {
|
||||
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())
|
||||
crate::hyper_service(
|
||||
req,
|
||||
self.context(),
|
||||
SocketAddr::new(ip_str.parse().unwrap(), port),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn post_request_from_ip(&self, ip_str: &str) -> RequestBuilder {
|
||||
self.request_from_ip(ip_str)
|
||||
.method("POST")
|
||||
.header("Content-Type", "application/json")
|
||||
fn base_request_builder(&self, verb: &str, path: &str) -> http::request::Builder {
|
||||
Request::builder()
|
||||
.uri(format!("http://{}{}", WG_MANAGE_PEER_IP, path))
|
||||
.method(verb)
|
||||
.header(
|
||||
shared::INNERNET_PUBKEY_HEADER,
|
||||
HeaderValue::from_str(&self.public_key.to_base64()).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn put_request_from_ip(&self, ip_str: &str) -> RequestBuilder {
|
||||
self.request_from_ip(ip_str)
|
||||
.method("PUT")
|
||||
pub async fn request(&self, ip_str: &str, verb: &str, path: &str) -> Response<Body> {
|
||||
let req = self
|
||||
.base_request_builder(verb, path)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
self.raw_request(ip_str, req).await
|
||||
}
|
||||
|
||||
pub async fn form_request<F: Serialize>(
|
||||
&self,
|
||||
ip_str: &str,
|
||||
verb: &str,
|
||||
path: &str,
|
||||
form: F,
|
||||
) -> Response<Body> {
|
||||
let json = serde_json::to_string(&form).unwrap();
|
||||
let req = self
|
||||
.base_request_builder(verb, path)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Length", json.len().to_string())
|
||||
.body(Body::from(json))
|
||||
.unwrap();
|
||||
self.raw_request(ip_str, req).await
|
||||
}
|
||||
}
|
||||
|
||||
|
45
server/src/util.rs
Normal file
45
server/src/util.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use bytes::Buf;
|
||||
use hyper::{header, Body, Request, Response, StatusCode};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::ServerError;
|
||||
|
||||
pub async fn form_body<F: DeserializeOwned>(req: Request<Body>) -> Result<F, ServerError> {
|
||||
let content_len: usize = req
|
||||
.headers()
|
||||
.get(header::CONTENT_LENGTH)
|
||||
.and_then(|header| header.to_str().ok())
|
||||
.and_then(|header| header.parse().ok())
|
||||
.ok_or(ServerError::InvalidQuery)?;
|
||||
|
||||
if content_len > 16 * 1024 {
|
||||
return Err(ServerError::InvalidQuery);
|
||||
}
|
||||
|
||||
let whole_body = hyper::body::aggregate(req).await?;
|
||||
|
||||
serde_json::from_reader(whole_body.reader()).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn json_response<F: Serialize>(form: F) -> Result<Response<Body>, ServerError> {
|
||||
let json = serde_json::to_string(&form)?;
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(json))?)
|
||||
}
|
||||
|
||||
pub fn json_status_response<F: Serialize>(
|
||||
form: F,
|
||||
status: StatusCode,
|
||||
) -> Result<Response<Body>, ServerError> {
|
||||
let json = serde_json::to_string(&form)?;
|
||||
Ok(Response::builder()
|
||||
.status(status)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(json))?)
|
||||
}
|
||||
|
||||
pub fn status_response(status: StatusCode) -> Result<Response<Body>, ServerError> {
|
||||
Ok(Response::builder().status(status).body(Body::empty())?)
|
||||
}
|
@ -23,6 +23,7 @@ pub fn is_valid_hostname(name: &str) -> bool {
|
||||
name.len() < 64 && PEER_NAME_REGEX.is_match(name)
|
||||
}
|
||||
|
||||
#[allow(clippy::ptr_arg)]
|
||||
pub fn hostname_validator(name: &String) -> Result<(), &'static str> {
|
||||
if is_valid_hostname(name) {
|
||||
Ok(())
|
||||
@ -119,8 +120,8 @@ pub fn choose_association<'a>(
|
||||
}
|
||||
|
||||
pub fn add_association(cidrs: &[Cidr]) -> Result<Option<(&Cidr, &Cidr)>, Error> {
|
||||
let cidr1 = choose_cidr(&cidrs[..], "First CIDR")?;
|
||||
let cidr2 = choose_cidr(&cidrs[..], "Second CIDR")?;
|
||||
let cidr1 = choose_cidr(cidrs, "First CIDR")?;
|
||||
let cidr2 = choose_cidr(cidrs, "Second CIDR")?;
|
||||
|
||||
Ok(
|
||||
if Confirm::with_theme(&*THEME)
|
||||
@ -178,7 +179,7 @@ pub fn add_peer(
|
||||
let mut available_ip = None;
|
||||
let candidate_ips = cidr.iter().filter(|ip| cidr.is_assignable(*ip));
|
||||
for ip in candidate_ips {
|
||||
if peers.iter().find(|peer| peer.ip == ip).is_none() {
|
||||
if !peers.iter().any(|peer| peer.ip == ip) {
|
||||
available_ip = Some(ip);
|
||||
break;
|
||||
}
|
||||
@ -388,7 +389,7 @@ pub fn ask_endpoint(external_ip: Option<IpAddr>) -> Result<Endpoint, Error> {
|
||||
endpoint_builder
|
||||
.with_prompt("External endpoint")
|
||||
.interact()
|
||||
.map_err(|e| Error::from(e))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn override_endpoint(unset: bool) -> Result<Option<Option<Endpoint>>, Error> {
|
||||
|
@ -136,11 +136,11 @@ pub enum EndpointContents {
|
||||
Unset,
|
||||
}
|
||||
|
||||
impl Into<Option<Endpoint>> for EndpointContents {
|
||||
fn into(self) -> Option<Endpoint> {
|
||||
match self {
|
||||
Self::Set(addr) => Some(addr),
|
||||
Self::Unset => None,
|
||||
impl From<EndpointContents> for Option<Endpoint> {
|
||||
fn from(endpoint: EndpointContents) -> Self {
|
||||
match endpoint {
|
||||
EndpointContents::Set(addr) => Some(addr),
|
||||
EndpointContents::Unset => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ pub fn get_by_name(name: &InterfaceName) -> Result<DeviceInfo, io::Error> {
|
||||
/// available.
|
||||
fn get_userspace_implementation() -> String {
|
||||
std::env::var("WG_USERSPACE_IMPLEMENTATION")
|
||||
.or(std::env::var("WG_QUICK_USERSPACE_IMPLEMENTATION"))
|
||||
.or_else(|_| std::env::var("WG_QUICK_USERSPACE_IMPLEMENTATION"))
|
||||
.unwrap_or_else(|_| "wireguard-go".to_string())
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user