Skip to content

Commit

Permalink
feat: support basic auth for admin web page
Browse files Browse the repository at this point in the history
vicanso committed Apr 13, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 1b340d8 commit ec0d7a2
Showing 10 changed files with 67 additions and 15 deletions.
3 changes: 2 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# TODO

- [ ] authentication for admin page
- [ ] support etcd or other storage for config
- [ ] better error handler
- [ ] log rotate
- [ ] tls cert auto update
- [ ] support validate config before save(web)
- [ ] auto reload config and restart
- [x] authentication for admin page
- [x] custom error for pingora error
- [x] support alpn for location
- [x] support add header for location
2 changes: 2 additions & 0 deletions conf/pingap.toml
Original file line number Diff line number Diff line change
@@ -117,3 +117,5 @@ stats_path = "/stats"

# Admin path for admin server. Default `None`
admin_path = "/pingap"
# Basic authorization for admin server, value is `base64(account + ":" + password). Default `None`
# authorization = "dGVzdDoxMjMxMjM="
1 change: 1 addition & 0 deletions src/config/load.rs
Original file line number Diff line number Diff line change
@@ -198,6 +198,7 @@ impl LocationConf {
pub struct ServerConf {
pub addr: String,
pub access_log: Option<String>,
pub authorization: Option<String>,
pub locations: Option<Vec<String>>,
pub tls_cert: Option<String>,
pub tls_key: Option<String>,
7 changes: 7 additions & 0 deletions src/http_extra/http_header.rs
Original file line number Diff line number Diff line change
@@ -54,6 +54,13 @@ pub static HTTP_HEADER_NO_STORE: Lazy<HttpHeader> = Lazy::new(|| {
)
});

pub static HTTP_HEADER_WWW_AUTHENTICATE: Lazy<HttpHeader> = Lazy::new(|| {
(
header::WWW_AUTHENTICATE,
HeaderValue::from_str(r###"Basic realm="Pingap""###).unwrap(),
)
});

pub static HTTP_HEADER_NO_CACHE: Lazy<HttpHeader> = Lazy::new(|| {
(
header::CACHE_CONTROL,
30 changes: 29 additions & 1 deletion src/proxy/server.rs
Original file line number Diff line number Diff line change
@@ -15,7 +15,7 @@
use super::logger::Parser;
use super::{Location, Upstream};
use crate::config::{LocationConf, PingapConf, UpstreamConf};
use crate::http_extra::{HttpResponse, HTTP_HEADER_CONTENT_JSON};
use crate::http_extra::{HttpResponse, HTTP_HEADER_CONTENT_JSON, HTTP_HEADER_WWW_AUTHENTICATE};
use crate::serve::Serve;
use crate::serve::ADMIN_SERVE;
use crate::state::{get_hostname, State};
@@ -72,6 +72,7 @@ pub struct ServerConf {
pub stats_path: Option<String>,
pub admin_path: Option<String>,
pub access_log: Option<String>,
pub authorization: Option<String>,
pub upstreams: Vec<(String, UpstreamConf)>,
pub locations: Vec<(String, LocationConf)>,
pub tls_cert: Option<Vec<u8>>,
@@ -135,6 +136,7 @@ impl From<PingapConf> for Vec<ServerConf> {
tls_cert,
tls_key,
admin: false,
authorization: item.authorization,
stats_path: item.stats_path,
admin_path: item.admin_path,
addr: item.addr,
@@ -164,6 +166,7 @@ pub struct Server {
processing: AtomicI32,
locations: Vec<Location>,
log_parser: Option<Parser>,
authorization: Option<String>,
error_template: String,
stats_path: Option<String>,
admin_path: Option<String>,
@@ -227,6 +230,7 @@ impl Server {
processing: AtomicI32::new(0),
stats_path: conf.stats_path,
admin_path: conf.admin_path,
authorization: conf.authorization,
addr: conf.addr,
log_parser: p,
locations,
@@ -314,12 +318,36 @@ impl Server {
ctx.status = Some(StatusCode::OK);
ctx.response_body_size = size;
}
fn auth_validate(&self, req_header: &RequestHeader) -> bool {
if let Some(authorization) = &self.authorization {
let value =
utils::get_req_header_value(req_header, "Authorization").unwrap_or_default();
if value.is_empty() {
return false;
}
if value != format!("Basic {authorization}") {
return false;
}
}
true
}
async fn serve_admin(
&self,
admin_path: &str,
session: &mut Session,
ctx: &mut State,
) -> pingora::Result<bool> {
if !self.auth_validate(session.req_header()) {
let _ = HttpResponse {
status: StatusCode::UNAUTHORIZED,
headers: Some(vec![HTTP_HEADER_WWW_AUTHENTICATE.clone()]),
..Default::default()
}
.send(session)
.await?;
return Ok(true);
}

let header = session.req_header_mut();
let path = header.uri.path();
let mut new_path = path.substring(admin_path.len(), path.len()).to_string();
2 changes: 1 addition & 1 deletion src/serve/admin.rs
Original file line number Diff line number Diff line change
@@ -249,7 +249,7 @@ impl Serve for AdminServe {
memory,
})
.unwrap_or(HttpResponse::unknown_error())
} else if path == "/restart" {
} else if path == "/restart" && method == Method::POST {
if let Err(e) = restart() {
error!("Restart fail: {e}");
return Err(utils::new_internal_error(400, e.to_string()));
2 changes: 1 addition & 1 deletion src/state/process.rs
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ pub fn restart() -> io::Result<process::Output> {
"Pingap is restarting",
));
}
info!("pingap will restart now");
info!("pingap will restart");
if let Some(cmd) = CMD.get() {
nix::sys::signal::kill(
nix::unistd::Pid::from_raw(std::process::id() as i32),
6 changes: 5 additions & 1 deletion src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ pub fn get_remote_addr(session: &Session) -> Option<String> {

/// Gets client ip from X-Forwarded-For,
/// If none, get from X-Real-Ip,
/// If none, get remote addr
/// If none, get remote addr.
pub fn get_client_ip(session: &Session) -> String {
if let Some(value) = session.get_header(HTTP_HEADER_X_FORWARDED_FOR.clone()) {
let arr: Vec<&str> = value.to_str().unwrap_or_default().split(',').collect();
@@ -84,6 +84,7 @@ pub fn get_client_ip(session: &Session) -> String {
"".to_string()
}

/// Gets string value from req header.
pub fn get_req_header_value<'a>(req_header: &'a RequestHeader, key: &str) -> Option<&'a str> {
if let Some(value) = req_header.headers.get(key) {
if let Ok(value) = value.to_str() {
@@ -93,6 +94,7 @@ pub fn get_req_header_value<'a>(req_header: &'a RequestHeader, key: &str) -> Opt
None
}

/// Gets cookie value from req header.
pub fn get_cookie_value<'a>(req_header: &'a RequestHeader, cookie_name: &str) -> Option<&'a str> {
if let Some(cookie_value) = get_req_header_value(req_header, "Cookie") {
for item in cookie_value.split(';') {
@@ -106,6 +108,7 @@ pub fn get_cookie_value<'a>(req_header: &'a RequestHeader, cookie_name: &str) ->
None
}

/// Gets query value from req header.
pub fn get_query_value<'a>(req_header: &'a RequestHeader, name: &str) -> Option<&'a str> {
if let Some(query) = req_header.uri.query() {
for item in query.split('&') {
@@ -119,6 +122,7 @@ pub fn get_query_value<'a>(req_header: &'a RequestHeader, name: &str) -> Option<
None
}

/// Creates a new internal error
pub fn new_internal_error(status: u16, message: String) -> pingora::BError {
pingora::Error::because(
pingora::ErrorType::HTTPStatus(status),
28 changes: 18 additions & 10 deletions web/src/pages/server-info.tsx
Original file line number Diff line number Diff line change
@@ -45,24 +45,24 @@ export default function ServerInfo() {
options: locations,
},
{
id: "stats_path",
label: "Stats Path",
defaultValue: server.stats_path,
id: "admin_path",
label: "Admin Path",
defaultValue: server.admin_path,
span: 6,
category: FormItemCategory.TEXT,
},
{
id: "admin_path",
label: "Admin Path",
defaultValue: server.admin_path,
id: "authorization",
label: "Authorization",
defaultValue: server.authorization,
span: 6,
category: FormItemCategory.TEXT,
},
{
id: "access_log",
label: "Access Log",
defaultValue: server.access_log,
span: 12,
id: "stats_path",
label: "Stats Path",
defaultValue: server.stats_path,
span: 6,
category: FormItemCategory.TEXT,
},
{
@@ -72,6 +72,14 @@ export default function ServerInfo() {
span: 6,
category: FormItemCategory.NUMBER,
},
{
id: "access_log",
label: "Access Log",
defaultValue: server.access_log,
span: 12,
category: FormItemCategory.TEXT,
},

{
id: "tls_cert",
label: "Tls Cert(base64)",
1 change: 1 addition & 0 deletions web/src/states/config.ts
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ interface Server {
tls_key?: string;
stats_path?: string;
admin_path?: string;
authorization?: string;
remark?: string;
}

0 comments on commit ec0d7a2

Please sign in to comment.