Skip to content

Commit

Permalink
feat: support basic auth for admin web page
Browse files Browse the repository at this point in the history
  • Loading branch information
vicanso committed Apr 13, 2024
1 parent 1b340d8 commit ec0d7a2
Show file tree
Hide file tree
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
Expand Down
2 changes: 2 additions & 0 deletions conf/pingap.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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>,
Expand Down
7 changes: 7 additions & 0 deletions src/http_extra/http_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
30 changes: 29 additions & 1 deletion src/proxy/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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>>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion src/serve/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
2 changes: 1 addition & 1 deletion src/state/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
6 changes: 5 additions & 1 deletion src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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() {
Expand All @@ -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(';') {
Expand All @@ -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('&') {
Expand All @@ -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),
Expand Down
28 changes: 18 additions & 10 deletions web/src/pages/server-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
{
Expand All @@ -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)",
Expand Down
1 change: 1 addition & 0 deletions web/src/states/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ interface Server {
tls_key?: string;
stats_path?: string;
admin_path?: string;
authorization?: string;
remark?: string;
}

Expand Down

0 comments on commit ec0d7a2

Please sign in to comment.