Compare commits
10 commits
9b640a555a
...
c34578786e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c34578786e | ||
|
|
6c32ae8b68 | ||
|
|
d1a223fac9 | ||
|
|
321d36b743 | ||
|
|
b806b28a6e | ||
|
|
d60b70ffb4 | ||
|
|
820f619aeb | ||
|
|
e1fa45e39b | ||
|
|
269a23a8b7 | ||
|
|
82d6eaa2a8 |
22 changed files with 1228 additions and 183 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -14,4 +14,5 @@ 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", "tun"]
|
members = ["burrow", "server", "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.0"
|
tonic = "0.12.3"
|
||||||
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,6 +68,9 @@ 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"
|
||||||
|
|
@ -96,4 +99,4 @@ bundled = ["rusqlite/bundled"]
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = "0.12.0"
|
tonic-build = "0.12.3"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tonic_build::compile_protos("../proto/burrow.proto")?;
|
tonic_build::configure().compile_protos(
|
||||||
|
&["../proto/burrow.proto", "../proto/burrowweb.proto"],
|
||||||
|
&["../proto", "../proto"],
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod client;
|
|
||||||
pub mod server;
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
pub mod slack;
|
|
||||||
pub use super::db;
|
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Default, Debug)]
|
|
||||||
pub struct OpenIdUser {
|
|
||||||
pub sub: String,
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
|
|
@ -5,18 +5,13 @@ 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::DaemonResponse, rpc::ServerInfo, DaemonClient, DaemonCommand, DaemonResponseData,
|
||||||
rpc::ServerInfo,
|
|
||||||
DaemonClient,
|
|
||||||
DaemonCommand,
|
|
||||||
DaemonResponseData,
|
|
||||||
DaemonStartOptions,
|
DaemonStartOptions,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,6 @@ 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};
|
||||||
|
|
||||||
|
|
@ -52,8 +49,6 @@ 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
|
||||||
|
|
@ -276,7 +271,6 @@ 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,9 +2,6 @@ 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
|
||||||
|
|
||||||
|
|
@ -15,15 +12,67 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
@ -34,9 +83,13 @@ message CreateDeviceRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateDeviceResponse {
|
message CreateDeviceResponse {
|
||||||
wireguard.Config wg_config = 1;
|
Config wg_config = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListDevicesResponse {
|
message ListDevicesResponse {
|
||||||
repeated wireguard.Device devices = 1;
|
repeated Device devices = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerStatus {
|
||||||
|
string status = 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
41
server/Cargo.toml
Normal file
41
server/Cargo.toml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
[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"
|
||||||
4
server/build.rs
Normal file
4
server/build.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
tonic_build::configure().compile_protos(&["../proto/burrowweb.proto"], &["../proto"])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
52
server/src/main.rs
Normal file
52
server/src/main.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
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,7 +1,5 @@
|
||||||
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<()> {
|
||||||
|
|
@ -49,7 +47,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>,
|
||||||
|
|
@ -84,8 +82,32 @@ 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(())
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
pub fn delete_device(id: i64) -> Result<()> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
5
server/src/server/grpc_defs.rs
Normal file
5
server/src/server/grpc_defs.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub use burrowwebrpc::*;
|
||||||
|
|
||||||
|
pub mod burrowwebrpc {
|
||||||
|
tonic::include_proto!("burrowweb");
|
||||||
|
}
|
||||||
82
server/src/server/grpc_server.rs
Normal file
82
server/src/server/grpc_server.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
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,32 +1,29 @@
|
||||||
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 axum::{http::StatusCode, routing::post, Router};
|
use grpc_defs::burrow_web_server::BurrowWebServer;
|
||||||
use providers::slack::auth;
|
use grpc_server::BurrowGrpcServer;
|
||||||
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");
|
||||||
axum::serve(listener, app)
|
let burrow_grpc_server = BurrowGrpcServer::new()?;
|
||||||
.with_graceful_shutdown(shutdown_signal())
|
let svc = BurrowWebServer::new(burrow_grpc_server);
|
||||||
.await
|
Server::builder()
|
||||||
.unwrap();
|
.accept_http1(true)
|
||||||
|
.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()
|
||||||
76
server/src/server/providers/mod.rs
Normal file
76
server/src/server/providers/mod.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
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,24 +1,26 @@
|
||||||
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(Json(payload): Json<SlackToken>) -> (StatusCode, String) {
|
pub async fn auth(
|
||||||
let slack_user = match fetch_slack_user(&payload.slack_token).await {
|
request: TRequest<SlackAuthRequest>,
|
||||||
|
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 (StatusCode::UNAUTHORIZED, String::new());
|
return Err(TStatus::unauthenticated("Failed to fetch slack user"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -28,15 +30,18 @@ pub async fn auth(Json(payload): Json<SlackToken>) -> (StatusCode, String) {
|
||||||
slack_user.sub
|
slack_user.sub
|
||||||
);
|
);
|
||||||
|
|
||||||
let conn = match store_connection(slack_user, "slack", &payload.slack_token, None) {
|
let _conn = match store_connection(&slack_user, "slack", &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 (StatusCode::UNAUTHORIZED, String::new());
|
return Err(TStatus::unauthenticated("Failed to store connection"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(StatusCode::OK, String::new())
|
Ok(TResponse::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> {
|
||||||
22
server/src/server/settings.rs
Normal file
22
server/src/server/settings.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
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