Skip to content

Commit

Permalink
feat: support add admin plugin to server, #58
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Nov 28, 2024
1 parent 6e458f2 commit d9cd254
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 37 deletions.
13 changes: 10 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ fn run_admin_node(args: Args) -> Result<(), Box<dyn Error>> {
..Default::default()
})?;
let (server_conf, name, proxy_plugin_info) =
plugin::parse_admin_plugin(&args.admin.unwrap_or_default());
plugin::parse_admin_plugin(&args.admin.unwrap_or_default())?;

if let Err(e) =
plugin::try_init_plugins(&HashMap::from([(name, proxy_plugin_info)]))
Expand Down Expand Up @@ -433,8 +433,15 @@ fn run() -> Result<(), Box<dyn Error>> {
let mut server_conf_list: Vec<ServerConf> = conf.into();

if let Some(addr) = &get_admin_addr() {
let (server_conf, _, _) = plugin::parse_admin_plugin(addr);
server_conf_list.push(server_conf);
let (server_conf, _, _) = plugin::parse_admin_plugin(addr)?;
if let Some(server) = server_conf_list
.iter_mut()
.find(|item| item.addr == server_conf.addr)
{
server.admin = true;
} else {
server_conf_list.push(server_conf);
}
}

let mut enabled_lets_encrypt = false;
Expand Down
23 changes: 19 additions & 4 deletions src/plugin/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use hex::encode;
use hex::ToHex;
use http::Method;
use http::{header, HeaderValue, StatusCode};
use humantime::parse_duration;
use pingora::http::RequestHeader;
use pingora::proxy::Session;
use regex::Regex;
Expand Down Expand Up @@ -119,6 +120,7 @@ pub struct AdminServe {
pub path: String,
pub authorizations: Vec<(String, String)>,
pub plugin_step: PluginStep,
max_age: Duration,
hash_value: String,
ip_fail_limit: TtlLruLimit,
}
Expand Down Expand Up @@ -185,8 +187,20 @@ impl TryFrom<&PluginConf> for AdminServe {
if ip_fail_limit <= 0 {
ip_fail_limit = 10;
}
let max_age_value = &get_str_conf(value, "max_age");
let mut max_age = Duration::from_secs(2 * 24 * 3600);
if !max_age_value.is_empty() {
max_age = parse_duration(max_age_value).map_err(|e| {
Error::ParseDuration {
category: "admin".to_string(),
source: e,
}
})?;
}

let params = AdminServe {
hash_value,
max_age,
plugin_step: get_step_conf(value),
path: get_str_conf(value, "path"),
ip_fail_limit: TtlLruLimit::new(
Expand Down Expand Up @@ -256,7 +270,7 @@ impl AdminServe {
};
let offset = util::now().as_secs() as i64
- ts.parse::<i64>().unwrap_or_default();
if offset.abs() > 48 * 3600 {
if offset.abs() > self.max_age.as_secs() as i64 {
return false;
}

Expand Down Expand Up @@ -459,6 +473,9 @@ impl Plugin for AdminServe {
if self.plugin_step != step {
return Ok(None);
}
if !session.req_header().uri.path().starts_with(&self.path) {
return Ok(None);
}
let ip = util::get_client_ip(session);
if !self.ip_fail_limit.validate(&ip).await {
return Ok(Some(HttpResponse {
Expand All @@ -467,9 +484,7 @@ impl Plugin for AdminServe {
..Default::default()
}));
}
if !session.req_header().uri.path().starts_with(&self.path) {
return Ok(None);
}

let header = session.req_header_mut();
let path = header.uri.path();
let mut new_path =
Expand Down
52 changes: 39 additions & 13 deletions src/plugin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::config::{PluginCategory, PluginConf, PluginStep};
use crate::http_extra::HttpResponse;
use crate::proxy::ServerConf;
use crate::state::{get_admin_addr, State};
use crate::util::{self, base64_encode};
use ahash::AHashMap;
use arc_swap::ArcSwap;
use async_trait::async_trait;
Expand Down Expand Up @@ -53,30 +54,50 @@ mod ua_restriction;
pub static ADMIN_SERVER_PLUGIN: Lazy<String> =
Lazy::new(|| uuid::Uuid::now_v7().to_string());

pub fn parse_admin_plugin(addr: &str) -> (ServerConf, String, PluginConf) {
let arr: Vec<&str> = addr.split('@').collect();
let mut addr = arr[0].to_string();
pub fn parse_admin_plugin(
addr: &str,
) -> Result<(ServerConf, String, PluginConf)> {
let info = url::Url::from_str(&format!("http://{addr}")).map_err(|e| {
Error::Invalid {
category: "url".to_string(),
message: e.to_string(),
}
})?;
let mut addr = info.host_str().unwrap_or_default().to_string();
if let Some(port) = info.port() {
addr = format!("{addr}:{port}")
}
let mut authorization = "".to_string();
if arr.len() >= 2 {
authorization = arr[0].trim().to_string();
addr = arr[1].trim().to_string();
if !info.username().is_empty() {
authorization = info.username().to_string();
// if not base64 string
if let Some(pass) = info.password() {
authorization = base64_encode(format!("{authorization}:{pass}"));
}
}
let mut path = "/".to_string();
if let Some(arr) = addr.clone().split_once("/") {
addr = arr.0.to_string();
path = format!("/{}", arr.1);
let mut path = info.path().to_string();
if path.is_empty() {
path = "/".to_string();
}
let query = util::convert_query_map(info.query().unwrap_or_default());
let max_age = if let Some(value) = query.get("max_age") {
value.to_string()
} else {
"2d".to_string()
};

let data = format!(
r#"
category = "admin"
path = "{path}"
authorizations = [
"{authorization}"
]
max_age = "{max_age}"
remark = "Admin serve"
"#,
);
(
Ok((
ServerConf {
name: "pingap:admin".to_string(),
admin: true,
Expand All @@ -85,7 +106,7 @@ pub fn parse_admin_plugin(addr: &str) -> (ServerConf, String, PluginConf) {
},
ADMIN_SERVER_PLUGIN.clone(),
toml::from_str::<PluginConf>(&data).unwrap(),
)
))
}

#[derive(Debug, Snafu)]
Expand All @@ -103,6 +124,11 @@ pub enum Error {
category: String,
source: base64::DecodeError,
},
#[snafu(display("Plugin {category}, base64 decode error {source}"))]
ParseDuration {
category: String,
source: humantime::DurationError,
},
}
type Result<T, E = Error> = std::result::Result<T, E>;

Expand Down Expand Up @@ -331,7 +357,7 @@ pub fn try_init_plugins(

// add admin plugin
if let Some(addr) = &get_admin_addr() {
let (_, name, proxy_plugin_info) = parse_admin_plugin(addr);
let (_, name, proxy_plugin_info) = parse_admin_plugin(addr)?;
plugin_confs.push((name, proxy_plugin_info));
}

Expand Down
13 changes: 4 additions & 9 deletions src/proxy/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,22 +349,18 @@ impl Server {
&self,
session: &mut Session,
ctx: &mut State,
) -> pingora::Result<()> {
) -> pingora::Result<bool> {
if let Some(plugin) = get_plugin(ADMIN_SERVER_PLUGIN.as_str()) {
let result = plugin
.handle_request(PluginStep::Request, session, ctx)
.await?;
if let Some(resp) = result {
ctx.status = Some(resp.status);
resp.send(session).await?;
} else {
return Err(util::new_internal_error(
500,
"Admin server is unavailable".to_string(),
));
return Ok(true);
}
}
Ok(())
Ok(false)
}
}

Expand Down Expand Up @@ -577,8 +573,7 @@ impl ProxyHttp for Server {
{
debug!("--> request filter");
defer!(debug!("<-- request filter"););
if self.admin {
self.serve_admin(session, ctx).await?;
if self.admin && self.serve_admin(session, ctx).await? {
return Ok(true);
}
// only enable for http 80
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,15 @@ export function MainHeader({
await fetchBasicInfo();
await fetchConfig();
} catch (err) {
const status = ((err as HTTPError)?.status || 0) as number;
if (status == 401) {
goToLogin();
return;
}
toast({
title: t("fetchFail"),
description: formatError(err),
});

const status = ((err as HTTPError)?.status || 0) as number;
if (status == 401 || status === 403) {
goToLogin();
}
}
}, []);
const zhLang = "zh";
Expand Down
2 changes: 1 addition & 1 deletion web/src/helpers/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ request.interceptors.response.use(
} else {
he.exception = true;
he.category = "exception";
he.message = "Unknown error";
he.message = response.data || "Unknown error";
}
}
addRequestStats(response?.config, response, he);
Expand Down
2 changes: 2 additions & 0 deletions web/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,8 @@ export default {
pingPathPlaceholder: "Input the path for ping health check",
adminPath: "Admin Path",
adminPathPlaceholder: "Input the path for admin plugin",
adminMaxAge: "Max Age",
adminMaxAgePalceholder: "Input the max session age for login",
adminIpFailLimit: "Ip Fail Limit",
adminIpFailLimitPlaceholder: "Input the fail ip limit count",
adminAuthorization: "Authorization",
Expand Down
2 changes: 2 additions & 0 deletions web/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ export default {
pingPathPlaceholder: "输入响应ping对应的路径",
adminPath: "路径",
adminPathPlaceholder: "输入管理配置对应路径",
adminMaxAge: "有效期",
adminMaxAgePalceholder: "输入登录态的有效期",
adminIpFailLimit: "失败ip限制",
adminIpFailLimitPlaceholder: "输入失败时的ip限制次数",
adminAuthorization: "认证信息",
Expand Down
12 changes: 10 additions & 2 deletions web/src/pages/Plugins.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,15 +175,23 @@ export default function Plugins() {
label: pluginI18n("adminPath"),
placeholder: pluginI18n("adminPathPlaceholder"),
defaultValue: (pluginConfig.path || "") as string,
span: 3,
span: 2,
category: ExFormItemCategory.TEXT,
},
{
name: "max_age",
label: pluginI18n("adminMaxAge"),
placeholder: pluginI18n("adminMaxAgePalceholder"),
defaultValue: (pluginConfig.max_age || "") as string,
span: 2,
category: ExFormItemCategory.TEXT,
},
{
name: "ip_fail_limit",
label: pluginI18n("adminIpFailLimit"),
placeholder: pluginI18n("adminIpFailLimitPlaceholder"),
defaultValue: Number(pluginConfig.ip_fail_limit || 0),
span: 3,
span: 2,
category: ExFormItemCategory.NUMBER,
},
{
Expand Down

0 comments on commit d9cd254

Please sign in to comment.