Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/dev' into update/k8s
Browse files Browse the repository at this point in the history
  • Loading branch information
RWDai committed Apr 25, 2024
2 parents 92fb4c9 + 4c7fc8b commit 1c617a0
Show file tree
Hide file tree
Showing 75 changed files with 736 additions and 1,438 deletions.
8 changes: 7 additions & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
[env]
TS_RS_EXPORT_DIR = { value = "./sdk/admin-client/src/model", relative = true }
TS_RS_EXPORT_DIR = { value = "./sdk/admin-client/src/model", relative = true }
CONFIG = "file:./resource/local-example"
PLUGINS = "./target/debug"
RUST_LOG = "trace"
FORMAT = "json"
KEY = "moCZihByqvXt4dfMYjOz75fzBi0eul6Ffg2EoUzWyqA="
SK = "password"
21 changes: 2 additions & 19 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/model",
"crates/config",
"crates/shell",
"examples/sayhello"
]
resolver = "2"

Expand Down Expand Up @@ -37,7 +38,6 @@ rust-version = "1.64"
[workspace.dependencies]
spacegate-kernel = { path = "./crates/kernel" }
spacegate-plugin = { path = "./crates/plugin" }
spacegate-model = { path = "./crates/model" }
spacegate-config = { path = "./crates/config" }
spacegate-shell = { path = "./crates/shell" }
spacegate-ext-axum = { path = "./crates/extension/axum" }
Expand All @@ -51,11 +51,8 @@ serde = { version = "1", features = ["derive"] }
serde_json = { version = "1" }
toml = { version = "0.8", features = ["preserve_order"] }
lazy_static = { version = "1.4" }
async-trait = { version = "0.1" }
itertools = { version = "0" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = { version = "0" }
urlencoding = { version = "2" }


# Time
Expand All @@ -69,9 +66,6 @@ hyper-util = { version = "0" }

# ws
tokio-tungstenite = { version = "0" }

# tower
tower-service = { version = "0.3" }
tower-layer = { version = "0.3" }
tower-http = { version = "0.5" }
tower = { version = "0.4" }
Expand All @@ -86,12 +80,6 @@ schemars = { version = "0.8.6" }
# Test
reqwest = { version = "0.11", features = ["json", "gzip", "brotli"] }
testcontainers-modules = { version = "0.3" }
async-compression = { version = "0.3.13", features = [
"tokio",
"gzip",
"deflate",
"brotli",
] }


bytes = { version = "1" }
Expand All @@ -101,9 +89,6 @@ hyper-rustls = { version = "0.26" }
rustls-pemfile = "2"
tokio-rustls = { version = "0.25" }

# serde
duration-str = "0.7.1"

# regex
regex = { version = "1" }
serde_regex = { version = "1.1.0" }
Expand All @@ -119,8 +104,6 @@ ipnet = { version = "2" }
# notify
notify = { version = "6.1.1" }

# redis
deadpool-redis = { version = "0.14" }

# web-server
axum = "0.7.4"

5 changes: 5 additions & 0 deletions binary/admin-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ default = []

[dependencies]
clap = { version = "4.5", features = ["derive", "env"] }
base64 = "0.13"
spacegate-config = { workspace = true, features = [
"fs",
] }
axum = { workspace = true }
axum-extra = { version = "*", features = ["cookie"] }
tower = { version = "0.4" }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }
Expand All @@ -30,6 +32,9 @@ tower-http = { version = "*", features = ["trace"] }
serde_json = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
serde = { workspace = true, features = ["derive"] }
jsonwebtoken = "9"
digest = "0.10.7"
sha2 = "0.10.8"
[dev-dependencies]

[package.metadata.docs.rs]
Expand Down
14 changes: 14 additions & 0 deletions binary/admin-server/src/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::{

use clap::Parser;
use serde_json::Value;
use spacegate_config::BoxError;
use tracing::{info, warn};

use crate::state::PluginCode;
Expand All @@ -34,6 +35,19 @@ pub struct Args {
/// the format of the config file
#[arg(short, long, env, default_value_t = ConfigFormat::Toml)]
pub format: ConfigFormat,
#[arg(short, long, env)]
pub key: Option<Base64Decoded>,
#[arg(short, long, env)]
pub sk: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Base64Decoded(pub Vec<u8>);
impl FromStr for Base64Decoded {
type Err = BoxError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
base64::decode(s).map_err(Into::into).map(Base64Decoded)
}
}

#[derive(Debug, Clone)]
Expand Down
12 changes: 10 additions & 2 deletions binary/admin-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = <crate::clap::Args as ::clap::Parser>::parse();
tracing::info!("server started with args: {:?}", args);
let addr = SocketAddr::new(args.host, args.port);
let sec = args.key.map(|k| k.0.into());
let digest: Option<Arc<[u8; 32]>> = args.sk.map(|sk| {
let out = <sha2::Sha256 as digest::Digest>::digest(sk);
let out: [u8; 32] = out.into();
Arc::new(out)
});
// let schemas = args.schemas.load_all()?;
let app = match args.config {
clap::ConfigBackend::File(path) => {
let backend = spacegate_config::service::fs::Fs::new(path, config_format::Json::default());
create_app(backend)
create_app(backend, sec, digest)
}
clap::ConfigBackend::K8s(_ns) => {
// let backend = spacegate_config::service::backend::k8s::K8s::with_default_client(ns).await?;
Expand All @@ -41,13 +47,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}

/// create app for an backend
pub fn create_app<B>(backend: B) -> Router<()>
pub fn create_app<B>(backend: B, sec: Option<Arc<[u8]>>, sk_digest: Option<Arc<[u8; 32]>>) -> Router<()>
where
B: Discovery + Create + Retrieve + Update + Delete + Send + Sync + 'static,
{
let state = AppState {
backend: Arc::new(backend),
version: mw::version_control::Version::new(),
secret: sec,
sk_digest,
// plugin_schemas: Arc::new(schemas.into()),
};
service::router(state)
Expand Down
1 change: 1 addition & 0 deletions binary/admin-server/src/mw.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod authentication;
pub mod version_control;
43 changes: 43 additions & 0 deletions binary/admin-server/src/mw/authentication.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use axum::{
extract::{self, State},
http::StatusCode,
middleware::Next,
response::Response,
};
use axum_extra::extract::cookie::CookieJar;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};

use crate::state::AppState;

/// Our claims struct, it needs to derive `Serialize` and/or `Deserialize`
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub exp: u64,
pub username: String,
}

pub struct Authentication {
pub secret: String,
}

pub async fn authentication<B>(State(state): State<AppState<B>>, cookie: CookieJar, request: extract::Request, next: Next) -> Response {
use axum::http::header::AUTHORIZATION;
if let Some(secret) = state.secret {
let Some(jwt) = request
.headers()
.get(AUTHORIZATION)
.and_then(|header| header.to_str().ok())
.and_then(|header| header.strip_prefix("Bearer "))
.or(cookie.get("jwt").map(|cookie| cookie.value()))
else {
return Response::builder().status(StatusCode::UNAUTHORIZED).body("expect jwt token".into()).unwrap();
};
let Ok(_jwt) = decode::<Claims>(jwt, &DecodingKey::from_secret(secret.as_ref()), &Validation::new(Algorithm::HS256)) else {
return Response::builder().status(StatusCode::UNAUTHORIZED).body("invalid jwt token".into()).unwrap();
};
}

next.run(request).await
}
10 changes: 8 additions & 2 deletions binary/admin-server/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use spacegate_config::service::*;

use crate::{mw, state::AppState};

pub mod auth;
pub mod config;
pub mod instance;
pub mod plugin;
Expand All @@ -14,11 +15,16 @@ where
Router::new()
.nest(
"/config",
config::router::<B>().layer(middleware::from_fn_with_state(state.clone(), mw::version_control::version_control)),
config::router::<B>()
.layer(middleware::from_fn_with_state(state.clone(), mw::authentication::authentication))
.layer(middleware::from_fn_with_state(state.clone(), mw::version_control::version_control)),
)
.nest(
"/plugin",
plugin::router::<B>().layer(middleware::from_fn_with_state(state.clone(), mw::version_control::version_control)),
plugin::router::<B>()
.layer(middleware::from_fn_with_state(state.clone(), mw::authentication::authentication))
.layer(middleware::from_fn_with_state(state.clone(), mw::version_control::version_control)),
)
.nest("/auth", auth::router::<B>())
.with_state(state)
}
51 changes: 51 additions & 0 deletions binary/admin-server/src/service/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::time::SystemTime;

use crate::{
error::InternalError,
mw::authentication::Claims,
state::{self, AppState},
};
use axum::{
extract::State,
http::{header::SET_COOKIE, HeaderValue},
routing::post,
Json, Router,
};
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct Login {
pub ak: String,
pub sk: String,
}
const EXPIRE: u64 = 3600;
async fn login<B>(State(AppState { secret, sk_digest, .. }): State<AppState<B>>, login: Json<Login>) -> Result<axum::response::Response, InternalError> {
let mut response = axum::response::Response::new(axum::body::Body::empty());
if let Some(sk_digest) = sk_digest {
let out: [u8; 32] = <sha2::Sha256 as digest::Digest>::digest(&login.sk).into();
if &out != sk_digest.as_ref() {
*response.status_mut() = axum::http::StatusCode::UNAUTHORIZED;
return Ok(response);
}
}
if let Some(sec) = secret {
let jwt = encode(
&Header::default(),
&Claims {
sub: "admin".to_string(),
exp: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() + EXPIRE,
username: login.ak.to_string(),
},
&EncodingKey::from_secret(sec.as_ref()),
)
.map_err(InternalError::boxed)?;
response.headers_mut().insert(
SET_COOKIE,
HeaderValue::from_str(&format!("jwt={jwt}; path=/; HttpOnly; Max-Age=3600")).expect("invalid jwt"),
);
}
Ok(response)
}
pub fn router<B: Send + Sync + 'static>() -> axum::Router<state::AppState<B>> {
Router::new().route("/login", post(login::<B>))
}
6 changes: 0 additions & 6 deletions binary/admin-server/src/service/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,3 @@ pub struct K8sInstance {
pub name: Arc<str>,
pub namespace: Arc<str>,
}

// impl K8sInstance {
// pub async fn fetch_all() -> Vec<K8sInstance> {
// config::k8s::fetch_all().await
// }
// }
5 changes: 4 additions & 1 deletion binary/admin-server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ impl ToString for PluginCode {
}
}

#[derive(Debug)]
pub struct AppState<B> {
pub backend: Arc<B>,
pub version: mw::version_control::Version,
pub secret: Option<Arc<[u8]>>,
pub sk_digest: Option<Arc<[u8; 32]>>,
// pub plugin_schemas: Arc<RwLock<HashMap<PluginCode, serde_json::Value>>>,
}

Expand All @@ -28,6 +29,8 @@ impl<B> Clone for AppState<B> {
Self {
backend: self.backend.clone(),
version: self.version.clone(),
secret: self.secret.clone(),
sk_digest: self.sk_digest.clone(),
// plugin_schemas: self.plugin_schemas.clone(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions binary/spacegate/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ readme = "../../README.md"


[features]
default = ["fs"]
default = ["fs", "dylib"]
full = ["k8s", "fs", "redis", "axum"]
build-k8s = ["k8s", "redis", "axum"]
build-local = ["fs", "redis", "axum"]
Expand All @@ -25,7 +25,7 @@ redis = ["spacegate-shell/cache"]
axum = ["spacegate-shell/ext-axum"]
# Used to statically link openssl at compile time
static-openssl = ["openssl/vendored"]

dylib = ["spacegate-shell/plugin-dylib"]
[dependencies]
# envy = { }
clap = { version = "4.5", features = ["derive", "env"] }
Expand Down
11 changes: 8 additions & 3 deletions binary/spacegate/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,23 @@ impl<'de> Deserialize<'de> for Config {
Config::from_str(&s).map_err(serde::de::Error::custom)
}
}

/// Spacegate start up arguments
#[derive(Debug, Serialize, Deserialize, Clone, Parser)]
#[command(version, about, long_about = None)]
pub struct Args {
/// The config file path
///
/// # Example
/// ## File
/// file:/path/to/dir
/// `-c file:/path/to/dir`
/// ## K8s
/// k8s:namespace
/// `-c k8s:namespace`
/// ## Redis
/// redis:redis://some-redis-url
/// `-c redis:redis://some-redis-url`
#[arg(short, long, env)]
pub config: Config,
/// The dynamic lib plugins dir
#[arg(short, long, env)]
pub plugins: Option<PathBuf>,
}
Loading

0 comments on commit 1c617a0

Please sign in to comment.