mirror of
https://github.com/sadoyan/aralez.git
synced 2026-04-29 22:38:36 +08:00
JWT Authentication and token generation
This commit is contained in:
@@ -3,5 +3,6 @@ pub mod consul;
|
||||
pub mod discovery;
|
||||
mod filewatch;
|
||||
pub mod healthcheck;
|
||||
pub mod jwt;
|
||||
pub mod parceyaml;
|
||||
pub mod tools;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::utils::jwt::check_jwt;
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
use pingora_proxy::Session;
|
||||
@@ -7,6 +8,7 @@ trait AuthValidator {
|
||||
}
|
||||
struct BasicAuth<'a>(&'a str);
|
||||
struct ApiKeyAuth<'a>(&'a str);
|
||||
struct JwtAuth<'a>(&'a str);
|
||||
|
||||
impl AuthValidator for BasicAuth<'_> {
|
||||
fn validate(&self, session: &Session) -> bool {
|
||||
@@ -30,6 +32,16 @@ impl AuthValidator for ApiKeyAuth<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthValidator for JwtAuth<'_> {
|
||||
fn validate(&self, session: &Session) -> bool {
|
||||
let jwtsecret = self.0;
|
||||
if let Some(header) = session.get_header("x-jwt-token") {
|
||||
let tok = header.to_str().ok().unwrap();
|
||||
return check_jwt(tok, jwtsecret);
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
fn validate(auth: &dyn AuthValidator, session: &Session) -> bool {
|
||||
auth.validate(session)
|
||||
}
|
||||
@@ -44,6 +56,10 @@ pub fn authenticate(c: &[String], session: &Session) -> bool {
|
||||
let auth = ApiKeyAuth(c[1].as_str().into());
|
||||
validate(&auth, session)
|
||||
}
|
||||
"jwt" => {
|
||||
let auth = JwtAuth(c[1].as_str().into());
|
||||
validate(&auth, session)
|
||||
}
|
||||
_ => {
|
||||
println!("Unsupported authentication mechanism : {}", c[0]);
|
||||
false
|
||||
|
||||
@@ -10,6 +10,7 @@ pub struct FromFileProvider {
|
||||
}
|
||||
pub struct APIUpstreamProvider {
|
||||
pub address: String,
|
||||
pub masterkey: String,
|
||||
}
|
||||
|
||||
pub struct ConsulProvider {
|
||||
@@ -24,7 +25,7 @@ pub trait Discovery {
|
||||
#[async_trait]
|
||||
impl Discovery for APIUpstreamProvider {
|
||||
async fn start(&self, toreturn: Sender<Configuration>) {
|
||||
webserver::run_server(self.address.clone(), toreturn).await;
|
||||
webserver::run_server(self.address.clone(), self.masterkey.clone(), toreturn).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
16
src/utils/jwt.rs
Normal file
16
src/utils/jwt.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub(crate) struct Claims {
|
||||
pub(crate) user: String,
|
||||
pub(crate) exp: u64,
|
||||
}
|
||||
pub fn check_jwt(input: &str, secret: &str) -> bool {
|
||||
let validation = Validation::new(Algorithm::HS256);
|
||||
let token_data = decode::<Claims>(&input, &DecodingKey::from_secret(secret.as_ref()), &validation);
|
||||
match token_data {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
@@ -52,12 +52,13 @@ impl BackgroundService for LB {
|
||||
error!("Can't read config file");
|
||||
}
|
||||
}
|
||||
|
||||
let config_address = self.config.get("config_address");
|
||||
let masterkey = self.config.get("master_key").unwrap();
|
||||
match config_address {
|
||||
Some(config_address) => {
|
||||
let api_load = APIUpstreamProvider {
|
||||
address: config_address.to_string(),
|
||||
masterkey: masterkey.value().to_string(),
|
||||
};
|
||||
let tx_api = tx.clone();
|
||||
let _ = tokio::spawn(async move { api_load.start(tx_api).await });
|
||||
@@ -248,13 +249,13 @@ impl ProxyHttp for LB {
|
||||
let authenticated = authenticate(&auth.value(), &session);
|
||||
if !authenticated {
|
||||
let _ = session.respond_error(401).await;
|
||||
info!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path().to_string());
|
||||
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path().to_string());
|
||||
return Ok(true);
|
||||
}
|
||||
};
|
||||
if session.req_header().uri.path().starts_with("/denied") {
|
||||
let _ = session.respond_error(403).await;
|
||||
info!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path().to_string());
|
||||
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path().to_string());
|
||||
return Ok(true);
|
||||
};
|
||||
Ok(false)
|
||||
|
||||
@@ -1,23 +1,41 @@
|
||||
use crate::utils::parceyaml::Configuration;
|
||||
use axum::body::Body;
|
||||
use axum::extract::State;
|
||||
use axum::http::{Response, StatusCode};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::{delete, get, head, post, put};
|
||||
use axum::Router;
|
||||
use axum::{Json, Router};
|
||||
use futures::channel::mpsc::Sender;
|
||||
use futures::SinkExt;
|
||||
use log::info;
|
||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct InputKey {
|
||||
masterkey: String,
|
||||
owner: String,
|
||||
valid: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct OutToken {
|
||||
token: String,
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
pub async fn run_server(bindaddress: String, mut toreturn: Sender<Configuration>) {
|
||||
pub async fn run_server(bindaddress: String, masterkey: String, mut toreturn: Sender<Configuration>) {
|
||||
let mut tr = toreturn.clone();
|
||||
let app = Router::new()
|
||||
.route("/{*wildcard}", get(getconfig))
|
||||
.route("/{*wildcard}", post(getconfig))
|
||||
.route("/{*wildcard}", put(getconfig))
|
||||
.route("/{*wildcard}", head(getconfig))
|
||||
.route("/{*wildcard}", delete(getconfig))
|
||||
.route("/{*wildcard}", get(senderror))
|
||||
.route("/{*wildcard}", post(senderror))
|
||||
.route("/{*wildcard}", put(senderror))
|
||||
.route("/{*wildcard}", head(senderror))
|
||||
.route("/{*wildcard}", delete(senderror))
|
||||
.route("/jwt", post(jwt_gen))
|
||||
.with_state(masterkey.clone())
|
||||
.route(
|
||||
"/conf",
|
||||
post(|up: String| async move {
|
||||
@@ -42,26 +60,32 @@ pub async fn run_server(bindaddress: String, mut toreturn: Sender<Configuration>
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn getconfig() -> impl IntoResponse {
|
||||
"Hello from Axum API inside Pingora!\n".to_string();
|
||||
async fn senderror() -> impl IntoResponse {
|
||||
Response::builder().status(StatusCode::BAD_GATEWAY).body(Body::from("No live upstream found!\n")).unwrap()
|
||||
}
|
||||
// curl -XPOST -H 'Content-Type: application/json' --data-binary @./push.json 127.0.0.1:3000/json
|
||||
// curl -XPOST --data-binary @./etc/upstreams.txt 127.0.0.1:3000/conf
|
||||
|
||||
/*
|
||||
async fn config(Json(payload): Json<HashMap<String, UpstreamData>>) -> impl IntoResponse {
|
||||
let upstreams = DashMap::new();
|
||||
for (key, value) in payload {
|
||||
upstreams.insert(key, (value.servers, AtomicUsize::new(value.counter)));
|
||||
async fn jwt_gen(State(masterkey): State<String>, Json(payload): Json<InputKey>) -> (StatusCode, Json<OutToken>) {
|
||||
if payload.masterkey == masterkey {
|
||||
let now = SystemTime::now() + Duration::from_secs(payload.valid * 60);
|
||||
let a = now.duration_since(UNIX_EPOCH).unwrap().as_secs();
|
||||
let claim = crate::utils::jwt::Claims { user: payload.owner, exp: a };
|
||||
match encode(&Header::default(), &claim, &EncodingKey::from_secret(payload.masterkey.as_ref())) {
|
||||
Ok(t) => {
|
||||
let tok = OutToken { token: t };
|
||||
info!("Generating token: {:?}", tok);
|
||||
(StatusCode::CREATED, Json(tok))
|
||||
}
|
||||
Err(e) => {
|
||||
let tok = OutToken { token: "ERROR".to_string() };
|
||||
error!("Failed to generate token: {:?}", e);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Json(tok))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let tok = OutToken {
|
||||
token: "Unauthorised".to_string(),
|
||||
};
|
||||
warn!("Unauthorised JWT generate request: {:?}", tok);
|
||||
(StatusCode::FORBIDDEN, Json(tok))
|
||||
}
|
||||
println!("{:?}", upstreams);
|
||||
Response::builder().status(StatusCode::CREATED).body(Body::from("Config updated!\n")).unwrap()
|
||||
}
|
||||
async fn parse_upstreams(up: String) -> impl IntoResponse {
|
||||
println!("Parsing: {}", up);
|
||||
let serverlist = read_upstreams_from_file(up.as_str());
|
||||
println!("{:?}", serverlist);
|
||||
Response::builder().status(StatusCode::CREATED).body(Body::from("Config updated!\n")).unwrap()
|
||||
}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user