Compare commits
No commits in common. "c34578786eff176bb4a6bef9c0c907b8549f2ef2" and "9b640a555ae4a867e2050247d8c5b0997f87fab6" have entirely different histories.
c34578786e
...
9b640a555a
22 changed files with 183 additions and 1228 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -14,5 +14,4 @@ target/
|
||||||
tmp/
|
tmp/
|
||||||
|
|
||||||
*.db
|
*.db
|
||||||
*.sock
|
*.sock
|
||||||
*.sqlite3
|
|
||||||
881
Cargo.lock
generated
881
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["burrow", "server", "tun"]
|
members = ["burrow", "tun"]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
exclude = ["burrow-gtk"]
|
exclude = ["burrow-gtk"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ reqwest = { version = "0.12", default-features = false, features = [
|
||||||
] }
|
] }
|
||||||
rusqlite = { version = "0.31.0", features = ["blob"] }
|
rusqlite = { version = "0.31.0", features = ["blob"] }
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
tonic = "0.12.3"
|
tonic = "0.12.0"
|
||||||
prost = "0.13.1"
|
prost = "0.13.1"
|
||||||
prost-types = "0.13.1"
|
prost-types = "0.13.1"
|
||||||
tokio-stream = "0.1"
|
tokio-stream = "0.1"
|
||||||
|
|
@ -68,9 +68,6 @@ tower = "0.4.13"
|
||||||
hyper-util = "0.1.6"
|
hyper-util = "0.1.6"
|
||||||
toml = "0.8.15"
|
toml = "0.8.15"
|
||||||
rust-ini = "0.21.0"
|
rust-ini = "0.21.0"
|
||||||
jwt-simple = "0.12.10"
|
|
||||||
config = "0.14.1"
|
|
||||||
dotenvy = "0.15.7"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
caps = "0.5"
|
caps = "0.5"
|
||||||
|
|
@ -99,4 +96,4 @@ bundled = ["rusqlite/bundled"]
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = "0.12.3"
|
tonic-build = "0.12.0"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tonic_build::configure().compile_protos(
|
tonic_build::compile_protos("../proto/burrow.proto")?;
|
||||||
&["../proto/burrow.proto", "../proto/burrowweb.proto"],
|
|
||||||
&["../proto", "../proto"],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
burrow/src/auth/mod.rs
Normal file
2
burrow/src/auth/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod client;
|
||||||
|
pub mod server;
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use crate::daemon::rpc::grpc_defs::{Network, NetworkType};
|
||||||
|
|
||||||
pub static PATH: &str = "./server.sqlite3";
|
pub static PATH: &str = "./server.sqlite3";
|
||||||
|
|
||||||
pub fn init_db() -> Result<()> {
|
pub fn init_db() -> Result<()> {
|
||||||
|
|
@ -47,7 +49,7 @@ pub fn init_db() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn store_connection(
|
pub fn store_connection(
|
||||||
openid_user: &super::providers::OpenIdUser,
|
openid_user: super::providers::OpenIdUser,
|
||||||
openid_provider: &str,
|
openid_provider: &str,
|
||||||
access_token: &str,
|
access_token: &str,
|
||||||
refresh_token: Option<&str>,
|
refresh_token: Option<&str>,
|
||||||
|
|
@ -82,32 +84,8 @@ pub fn store_device(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
log::debug!("Storing openid user {:#?}", openid_user);
|
log::debug!("Storing openid user {:#?}", openid_user);
|
||||||
let conn = rusqlite::Connection::open(PATH)?;
|
let conn = rusqlite::Connection::open(PATH)?;
|
||||||
todo!();
|
|
||||||
conn.execute(
|
|
||||||
"INSERT INTO device (name, public_key, apns_token, user_id, ipv4, ipv6, access_token, refresh_token)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_device(id: i64) -> Result<()> {
|
// TODO
|
||||||
let conn = rusqlite::Connection::open(PATH)?;
|
|
||||||
|
|
||||||
conn.execute("DELETE FROM device WHERE id = ?", [id])?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_devices(user_id: i64) -> Result<Vec<String>> {
|
|
||||||
let conn = rusqlite::Connection::open(PATH)?;
|
|
||||||
let mut stmt = conn.prepare("SELECT name FROM device WHERE user_id = ?")?;
|
|
||||||
|
|
||||||
let result: Vec<String> = stmt
|
|
||||||
.query_map([user_id], |row| {
|
|
||||||
let name: String = row.get(0)?;
|
|
||||||
Ok(name)
|
|
||||||
})?
|
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod grpc_defs;
|
|
||||||
mod grpc_server;
|
|
||||||
pub mod providers;
|
pub mod providers;
|
||||||
pub mod settings;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use grpc_defs::burrow_web_server::BurrowWebServer;
|
use axum::{http::StatusCode, routing::post, Router};
|
||||||
use grpc_server::BurrowGrpcServer;
|
use providers::slack::auth;
|
||||||
use tokio::signal;
|
use tokio::signal;
|
||||||
use tonic::transport::Server;
|
|
||||||
|
|
||||||
pub async fn serve() -> Result<()> {
|
pub async fn serve() -> Result<()> {
|
||||||
db::init_db()?;
|
db::init_db()?;
|
||||||
let addr = "[::1]:8080".parse()?;
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/slack-auth", post(auth))
|
||||||
|
.route("/device/new", post(device_new));
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
|
||||||
log::info!("Starting auth server on port 8080");
|
log::info!("Starting auth server on port 8080");
|
||||||
let burrow_grpc_server = BurrowGrpcServer::new()?;
|
axum::serve(listener, app)
|
||||||
let svc = BurrowWebServer::new(burrow_grpc_server);
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
Server::builder()
|
.await
|
||||||
.accept_http1(true)
|
.unwrap();
|
||||||
.add_service(tonic_web::enable(svc))
|
|
||||||
.serve(addr)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn device_new() -> StatusCode {
|
||||||
|
StatusCode::OK
|
||||||
|
}
|
||||||
|
|
||||||
async fn shutdown_signal() {
|
async fn shutdown_signal() {
|
||||||
let ctrl_c = async {
|
let ctrl_c = async {
|
||||||
signal::ctrl_c()
|
signal::ctrl_c()
|
||||||
8
burrow/src/auth/server/providers/mod.rs
Normal file
8
burrow/src/auth/server/providers/mod.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod slack;
|
||||||
|
pub use super::db;
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Default, Debug)]
|
||||||
|
pub struct OpenIdUser {
|
||||||
|
pub sub: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use axum::{
|
||||||
|
extract::Json,
|
||||||
|
http::StatusCode,
|
||||||
|
routing::{get, post},
|
||||||
|
};
|
||||||
use reqwest::header::AUTHORIZATION;
|
use reqwest::header::AUTHORIZATION;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use super::db::store_connection;
|
use super::db::store_connection;
|
||||||
use super::grpc_defs::{JwtInfo, SlackAuthRequest};
|
|
||||||
use super::KeypairT;
|
|
||||||
use tonic::{Request as TRequest, Response as TResponse, Result as TResult, Status as TStatus};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct SlackToken {
|
pub struct SlackToken {
|
||||||
slack_token: String,
|
slack_token: String,
|
||||||
}
|
}
|
||||||
pub async fn auth(
|
pub async fn auth(Json(payload): Json<SlackToken>) -> (StatusCode, String) {
|
||||||
request: TRequest<SlackAuthRequest>,
|
let slack_user = match fetch_slack_user(&payload.slack_token).await {
|
||||||
key_pair: &KeypairT,
|
|
||||||
) -> TResult<TResponse<JwtInfo>, TStatus> {
|
|
||||||
let slack_token = request.into_inner().slack_token;
|
|
||||||
let slack_user = match fetch_slack_user(&slack_token).await {
|
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to fetch Slack user: {:?}", e);
|
log::error!("Failed to fetch Slack user: {:?}", e);
|
||||||
return Err(TStatus::unauthenticated("Failed to fetch slack user"));
|
return (StatusCode::UNAUTHORIZED, String::new());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -30,18 +28,15 @@ pub async fn auth(
|
||||||
slack_user.sub
|
slack_user.sub
|
||||||
);
|
);
|
||||||
|
|
||||||
let _conn = match store_connection(&slack_user, "slack", &slack_token, None) {
|
let conn = match store_connection(slack_user, "slack", &payload.slack_token, None) {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("Failed to fetch Slack user: {:?}", e);
|
log::error!("Failed to fetch Slack user: {:?}", e);
|
||||||
return Err(TStatus::unauthenticated("Failed to store connection"));
|
return (StatusCode::UNAUTHORIZED, String::new());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(TResponse::new(
|
(StatusCode::OK, String::new())
|
||||||
JwtInfo::try_from_oid(slack_user, &key_pair)
|
|
||||||
.map_err(|e| TStatus::unauthenticated(format!("JWT Generation failed: {e}")))?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_slack_user(access_token: &str) -> Result<super::OpenIdUser> {
|
async fn fetch_slack_user(access_token: &str) -> Result<super::OpenIdUser> {
|
||||||
|
|
@ -5,13 +5,18 @@ pub mod wireguard;
|
||||||
mod daemon;
|
mod daemon;
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
mod auth;
|
||||||
pub(crate) mod tracing;
|
pub(crate) mod tracing;
|
||||||
|
|
||||||
#[cfg(target_vendor = "apple")]
|
#[cfg(target_vendor = "apple")]
|
||||||
pub use daemon::apple::spawn_in_process;
|
pub use daemon::apple::spawn_in_process;
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
pub use daemon::{
|
pub use daemon::{
|
||||||
rpc::DaemonResponse, rpc::ServerInfo, DaemonClient, DaemonCommand, DaemonResponseData,
|
rpc::DaemonResponse,
|
||||||
|
rpc::ServerInfo,
|
||||||
|
DaemonClient,
|
||||||
|
DaemonCommand,
|
||||||
|
DaemonResponseData,
|
||||||
DaemonStartOptions,
|
DaemonStartOptions,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ pub(crate) mod tracing;
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
mod wireguard;
|
mod wireguard;
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
|
mod auth;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
|
||||||
use daemon::{DaemonClient, DaemonCommand};
|
use daemon::{DaemonClient, DaemonCommand};
|
||||||
|
|
||||||
|
|
@ -49,6 +52,8 @@ enum Commands {
|
||||||
ServerConfig,
|
ServerConfig,
|
||||||
/// Reload Config
|
/// Reload Config
|
||||||
ReloadConfig(ReloadConfigArgs),
|
ReloadConfig(ReloadConfigArgs),
|
||||||
|
/// Authentication server
|
||||||
|
AuthServer,
|
||||||
/// Server Status
|
/// Server Status
|
||||||
ServerStatus,
|
ServerStatus,
|
||||||
/// Tunnel Config
|
/// Tunnel Config
|
||||||
|
|
@ -271,6 +276,7 @@ async fn main() -> Result<()> {
|
||||||
Commands::ServerInfo => try_serverinfo().await?,
|
Commands::ServerInfo => try_serverinfo().await?,
|
||||||
Commands::ServerConfig => try_serverconfig().await?,
|
Commands::ServerConfig => try_serverconfig().await?,
|
||||||
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?,
|
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?,
|
||||||
|
Commands::AuthServer => crate::auth::server::serve().await?,
|
||||||
Commands::ServerStatus => try_serverstatus().await?,
|
Commands::ServerStatus => try_serverstatus().await?,
|
||||||
Commands::TunnelConfig => try_tun_config().await?,
|
Commands::TunnelConfig => try_tun_config().await?,
|
||||||
Commands::NetworkAdd(args) => {
|
Commands::NetworkAdd(args) => {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ syntax = "proto3";
|
||||||
|
|
||||||
package burrowweb;
|
package burrowweb;
|
||||||
|
|
||||||
|
import "wireguard.proto";
|
||||||
|
|
||||||
|
|
||||||
// TODO: Frontend sends slack token → receive JWT
|
// TODO: Frontend sends slack token → receive JWT
|
||||||
// TODO: create/delete/list routes
|
// TODO: create/delete/list routes
|
||||||
|
|
||||||
|
|
@ -12,67 +15,15 @@ service BurrowWeb {
|
||||||
rpc CreateDevice (CreateDeviceRequest) returns (CreateDeviceResponse);
|
rpc CreateDevice (CreateDeviceRequest) returns (CreateDeviceResponse);
|
||||||
rpc DeleteDevice (JWTInfo) returns (Empty);
|
rpc DeleteDevice (JWTInfo) returns (Empty);
|
||||||
rpc ListDevices (JWTInfo) returns (ListDevicesResponse);
|
rpc ListDevices (JWTInfo) returns (ListDevicesResponse);
|
||||||
rpc Status(Empty) returns (ServerStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message Peer {
|
|
||||||
string public_key = 1;
|
|
||||||
optional string preshared_key = 2;
|
|
||||||
repeated string allowed_ips = 3;
|
|
||||||
string endpoint = 4;
|
|
||||||
optional uint32 persistent_keepalive = 5;
|
|
||||||
optional string name = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InterfaceConfig {
|
|
||||||
// Does not include private key; the client is responsible for generating & persisting that
|
|
||||||
repeated string address = 1;
|
|
||||||
optional uint32 listen_port = 2;
|
|
||||||
repeated string dns = 3;
|
|
||||||
optional uint32 mtu = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Device {
|
|
||||||
int32 id = 1;
|
|
||||||
optional string name = 2;
|
|
||||||
string public_key = 3;
|
|
||||||
optional string apns_token = 4;
|
|
||||||
int32 user_id = 5;
|
|
||||||
string created_at = 6;
|
|
||||||
string ipv4 = 7;
|
|
||||||
string ipv6 = 8;
|
|
||||||
string access_token = 9;
|
|
||||||
string refresh_token = 10;
|
|
||||||
string expires_at = 11;
|
|
||||||
}
|
|
||||||
|
|
||||||
message User {
|
|
||||||
int32 id = 1;
|
|
||||||
string created_at = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UserConnection {
|
|
||||||
int32 user_id = 1;
|
|
||||||
string openid_provider = 2;
|
|
||||||
string openid_user_id = 3;
|
|
||||||
string openid_user_name = 4;
|
|
||||||
string access_token = 5;
|
|
||||||
string refresh_token = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
message Config {
|
|
||||||
InterfaceConfig interface = 1;
|
|
||||||
repeated Peer peers = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
message Empty {}
|
message Empty {}
|
||||||
|
|
||||||
message SlackAuthRequest {
|
message SlackAuthRequest {
|
||||||
string slack_token = 1;
|
string slack_token = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message JWTInfo {
|
message JWTInfo {
|
||||||
string jwt = 1;
|
string jwt = 1;
|
||||||
}
|
}
|
||||||
|
|
@ -83,13 +34,9 @@ message CreateDeviceRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateDeviceResponse {
|
message CreateDeviceResponse {
|
||||||
Config wg_config = 1;
|
wireguard.Config wg_config = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListDevicesResponse {
|
message ListDevicesResponse {
|
||||||
repeated Device devices = 1;
|
repeated wireguard.Device devices = 1;
|
||||||
}
|
|
||||||
|
|
||||||
message ServerStatus {
|
|
||||||
string status = 1;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
53
proto/wireguard.proto
Normal file
53
proto/wireguard.proto
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
package wireguard;
|
||||||
|
|
||||||
|
message Peer {
|
||||||
|
string public_key = 1;
|
||||||
|
optional string preshared_key = 2;
|
||||||
|
repeated string allowed_ips = 3;
|
||||||
|
string endpoint = 4;
|
||||||
|
optional uint32 persistent_keepalive = 5;
|
||||||
|
optional string name = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InterfaceConfig {
|
||||||
|
// Does not include private key; the client is responsible for generating & persisting that
|
||||||
|
repeated string address = 1;
|
||||||
|
optional uint32 listen_port = 2;
|
||||||
|
repeated string dns = 3;
|
||||||
|
optional uint32 mtu = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Device {
|
||||||
|
int32 id = 1;
|
||||||
|
optional string name = 2;
|
||||||
|
string public_key = 3;
|
||||||
|
optional string apns_token = 4;
|
||||||
|
int32 user_id = 5;
|
||||||
|
string created_at = 6;
|
||||||
|
string ipv4 = 7;
|
||||||
|
string ipv6 = 8;
|
||||||
|
string access_token = 9;
|
||||||
|
string refresh_token = 10;
|
||||||
|
string expires_at = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
message User {
|
||||||
|
int32 id = 1;
|
||||||
|
string created_at = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UserConnection {
|
||||||
|
int32 user_id = 1;
|
||||||
|
string openid_provider = 2;
|
||||||
|
string openid_user_id = 3;
|
||||||
|
string openid_user_name = 4;
|
||||||
|
string access_token = 5;
|
||||||
|
string refresh_token = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message Config {
|
||||||
|
InterfaceConfig interface = 1;
|
||||||
|
repeated Peer peers = 2;
|
||||||
|
}
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "server"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = "1.0.93"
|
|
||||||
jwt-simple = "0.12.10"
|
|
||||||
log = "0.4.22"
|
|
||||||
reqwest = { version = "0.12.9", default-features = false, features = [
|
|
||||||
"json",
|
|
||||||
"rustls-tls",
|
|
||||||
] }
|
|
||||||
serde = "1.0.215"
|
|
||||||
serde_json = "1.0.133"
|
|
||||||
tokio = { version = "1.41.1", features = [
|
|
||||||
"rt",
|
|
||||||
"macros",
|
|
||||||
"sync",
|
|
||||||
"io-util",
|
|
||||||
"rt-multi-thread",
|
|
||||||
"signal",
|
|
||||||
"time",
|
|
||||||
"tracing",
|
|
||||||
"fs",
|
|
||||||
] }
|
|
||||||
tonic = "0.12.3"
|
|
||||||
clap = { version = "4.4", features = ["derive"] }
|
|
||||||
rusqlite = { version = "0.31.0", features = ["blob"] }
|
|
||||||
dotenvy = "0.15.7"
|
|
||||||
config = "0.14.1"
|
|
||||||
prost = "0.13.3"
|
|
||||||
prost-types = "0.13.3"
|
|
||||||
tonic-web = "0.12.3"
|
|
||||||
|
|
||||||
|
|
||||||
[features]
|
|
||||||
bundled = ["rusqlite/bundled"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tonic-build = "0.12.3"
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
tonic_build::configure().compile_protos(&["../proto/burrowweb.proto"], &["../proto"])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
pub mod client;
|
|
||||||
pub mod server;
|
|
||||||
use anyhow::Result;
|
|
||||||
use clap::{Args, Parser, Subcommand};
|
|
||||||
use server::{providers::gen_keypem, serve};
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "Burrow Server")]
|
|
||||||
#[command(author = "Hack Club <team@hackclub.com>")]
|
|
||||||
#[command(version = "0.1")]
|
|
||||||
#[command(
|
|
||||||
about = "Server for hosting auth logic of Burrow",
|
|
||||||
long_about = "Burrow is a 🚀 blazingly fast 🚀 tool designed to penetrate unnecessarily restrictive firewalls, providing teenagers worldwide with secure, less-filtered, and safe access to the internet!
|
|
||||||
It's being built by teenagers from Hack Club, in public! Check it out: https://github.com/hackclub/burrow
|
|
||||||
Spotted a bug? Please open an issue! https://github.com/hackclub/burrow/issues/new"
|
|
||||||
)]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
StartServer,
|
|
||||||
#[command(name = "genkeys")]
|
|
||||||
GenKeys(GenKeyArgs),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
pub struct GenKeyArgs {
|
|
||||||
#[arg(short, long, default_value = "false")]
|
|
||||||
pub raw: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<()> {
|
|
||||||
let cli = Cli::parse();
|
|
||||||
match &cli.command {
|
|
||||||
Commands::GenKeys(args) => {
|
|
||||||
let pem = gen_keypem();
|
|
||||||
if args.raw {
|
|
||||||
println!(r"{pem:?}");
|
|
||||||
} else {
|
|
||||||
println!("Generated PEM:\n{pem}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Commands::StartServer => {
|
|
||||||
serve().await?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
pub use burrowwebrpc::*;
|
|
||||||
|
|
||||||
pub mod burrowwebrpc {
|
|
||||||
tonic::include_proto!("burrowweb");
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use jwt_simple::prelude::Ed25519KeyPair;
|
|
||||||
use tonic::{Request, Response, Status};
|
|
||||||
|
|
||||||
use super::providers::{KeypairT, OpenIdUser};
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
grpc_defs::{
|
|
||||||
burrowwebrpc::burrow_web_server::BurrowWeb, CreateDeviceRequest, CreateDeviceResponse,
|
|
||||||
Empty, JwtInfo, ListDevicesResponse, ServerStatus, SlackAuthRequest,
|
|
||||||
},
|
|
||||||
providers::slack::auth,
|
|
||||||
settings::BurrowAuthServerConfig,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BurrowGrpcServer {
|
|
||||||
config: Arc<BurrowAuthServerConfig>,
|
|
||||||
jwt_keypair: Arc<KeypairT>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for BurrowGrpcServer {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("BurrowGrpcServer")
|
|
||||||
.field("config", &self.config)
|
|
||||||
.field("jwt_keypair", &"<redacted>")
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BurrowGrpcServer {
|
|
||||||
pub fn new() -> anyhow::Result<Self> {
|
|
||||||
let config = BurrowAuthServerConfig::new_dotenv()?;
|
|
||||||
let jwt_keypair = Ed25519KeyPair::from_pem(&config.jwt_pem)?;
|
|
||||||
Ok(Self {
|
|
||||||
config: Arc::new(config),
|
|
||||||
jwt_keypair: Arc::new(jwt_keypair),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tonic::async_trait]
|
|
||||||
impl BurrowWeb for BurrowGrpcServer {
|
|
||||||
async fn slack_auth(
|
|
||||||
&self,
|
|
||||||
request: Request<SlackAuthRequest>,
|
|
||||||
) -> Result<Response<JwtInfo>, Status> {
|
|
||||||
auth(request, &self.jwt_keypair).await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_device(
|
|
||||||
&self,
|
|
||||||
request: Request<CreateDeviceRequest>,
|
|
||||||
) -> Result<Response<CreateDeviceResponse>, Status> {
|
|
||||||
let req = request.into_inner();
|
|
||||||
let jwt = req
|
|
||||||
.jwt
|
|
||||||
.ok_or(Status::invalid_argument("JWT Not existent!"))?;
|
|
||||||
let oid_user = OpenIdUser::try_from_jwt(&jwt, &self.jwt_keypair)
|
|
||||||
.map_err(|e| Status::invalid_argument(e.to_string()))?;
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn delete_device(&self, request: Request<JwtInfo>) -> Result<Response<Empty>, Status> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn list_devices(
|
|
||||||
&self,
|
|
||||||
request: Request<JwtInfo>,
|
|
||||||
) -> Result<Response<ListDevicesResponse>, Status> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn status(&self, _req: Request<Empty>) -> Result<Response<ServerStatus>, Status> {
|
|
||||||
Ok(Response::new(ServerStatus {
|
|
||||||
status: "Nobody expects the Spanish Inquisition".into(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
pub mod slack;
|
|
||||||
use self::grpc_defs::JwtInfo;
|
|
||||||
|
|
||||||
pub use super::{db, grpc_defs, settings::BurrowAuthServerConfig};
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use jwt_simple::{
|
|
||||||
claims::{Claims, NoCustomClaims},
|
|
||||||
prelude::{Duration, Ed25519KeyPair, EdDSAKeyPairLike, EdDSAPublicKeyLike},
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
pub type KeypairT = Ed25519KeyPair;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Eq, Clone)]
|
|
||||||
pub struct OpenIdUser {
|
|
||||||
pub sub: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
struct OpenIDCustomField {
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OpenIdUser {
|
|
||||||
pub fn try_from_jwt(jwt_info: &JwtInfo, keypair: &KeypairT) -> Result<Self> {
|
|
||||||
let claims = keypair
|
|
||||||
.public_key()
|
|
||||||
.verify_token::<OpenIDCustomField>(&jwt_info.jwt, None)?;
|
|
||||||
Ok(Self {
|
|
||||||
sub: claims.subject.ok_or(anyhow!("No Subject!"))?,
|
|
||||||
name: claims.custom.name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl JwtInfo {
|
|
||||||
fn try_from_oid(oid_user: OpenIdUser, keypair: &KeypairT) -> Result<Self> {
|
|
||||||
let claims = Claims::with_custom_claims(
|
|
||||||
OpenIDCustomField { name: oid_user.name },
|
|
||||||
Duration::from_days(10),
|
|
||||||
)
|
|
||||||
.with_subject(oid_user.sub);
|
|
||||||
let jwt = keypair.sign(claims)?;
|
|
||||||
Ok(Self { jwt })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gen_keypem() -> String {
|
|
||||||
let keypair = KeypairT::generate();
|
|
||||||
keypair.to_pem()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_keypem(pem: &String) -> Result<KeypairT> {
|
|
||||||
let keypair = KeypairT::from_pem(&pem)?;
|
|
||||||
Ok(keypair)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_jwt() -> Result<()> {
|
|
||||||
let key_pair = Ed25519KeyPair::generate();
|
|
||||||
let sample_usr = OpenIdUser {
|
|
||||||
sub: "Spanish".into(),
|
|
||||||
name: "Inquisition".into(),
|
|
||||||
};
|
|
||||||
let encoded = JwtInfo::try_from_oid(sample_usr.clone(), &key_pair)?;
|
|
||||||
println!("{}", encoded.jwt);
|
|
||||||
let decoded = OpenIdUser::try_from_jwt(&encoded, &key_pair)?;
|
|
||||||
assert_eq!(decoded, sample_usr);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
use config::{Config, ConfigError, Environment};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct BurrowAuthServerConfig {
|
|
||||||
pub jwt_pem: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BurrowAuthServerConfig {
|
|
||||||
pub fn new() -> Result<Self, ConfigError> {
|
|
||||||
let s = Config::builder()
|
|
||||||
.add_source(Environment::default())
|
|
||||||
.build()?;
|
|
||||||
s.try_deserialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new config that includes the dotenv
|
|
||||||
pub fn new_dotenv() -> Result<Self, ConfigError> {
|
|
||||||
dotenvy::dotenv().ok();
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue