Skip to content

Commit 78abfe9

Browse files
committed
add support for basic auth for admin api
1 parent c055656 commit 78abfe9

12 files changed

Lines changed: 151 additions & 6 deletions

File tree

libsql-server/src/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub struct AdminApiConfig<A = AddrIncoming, C = HttpsConnector<HttpConnector>> {
8383
pub acceptor: A,
8484
pub connector: C,
8585
pub disable_metrics: bool,
86+
pub auth_key: Option<String>,
8687
}
8788

8889
#[derive(Clone)]

libsql-server/src/http/admin/mod.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use anyhow::Context as _;
22
use axum::body::StreamBody;
33
use axum::extract::{FromRef, Path, State};
4+
use axum::middleware::Next;
45
use axum::routing::delete;
56
use axum::Json;
67
use chrono::NaiveDateTime;
78
use futures::{SinkExt, StreamExt, TryStreamExt};
8-
use hyper::{Body, Request};
9+
use hyper::{Body, Request, StatusCode};
910
use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle};
1011
use parking_lot::Mutex;
1112
use serde::{Deserialize, Serialize};
@@ -64,6 +65,7 @@ pub async fn run<A, C>(
6465
connector: C,
6566
disable_metrics: bool,
6667
shutdown: Arc<Notify>,
68+
auth: Option<Arc<str>>,
6769
) -> anyhow::Result<()>
6870
where
6971
A: crate::net::Accept,
@@ -162,15 +164,15 @@ where
162164
)
163165
.route("/v1/diagnostics", get(handle_diagnostics))
164166
.route("/metrics", get(handle_metrics))
167+
.route("/profile/heap/enable", post(enable_profile_heap))
168+
.route("/profile/heap/disable/:id", post(disable_profile_heap))
169+
.route("/profile/heap/:id", delete(delete_profile_heap))
165170
.with_state(Arc::new(AppState {
166171
namespaces,
167172
connector,
168173
user_http_server,
169174
metrics,
170175
}))
171-
.route("/profile/heap/enable", post(enable_profile_heap))
172-
.route("/profile/heap/disable/:id", post(disable_profile_heap))
173-
.route("/profile/heap/:id", delete(delete_profile_heap))
174176
.layer(
175177
tower_http::trace::TraceLayer::new_for_http()
176178
.on_request(trace_request)
@@ -179,7 +181,8 @@ where
179181
.level(tracing::Level::DEBUG)
180182
.latency_unit(tower_http::LatencyUnit::Micros),
181183
),
182-
);
184+
)
185+
.layer(axum::middleware::from_fn_with_state(auth, auth_middleware));
183186

184187
hyper::server::Server::builder(acceptor)
185188
.serve(router.into_make_service())
@@ -190,6 +193,34 @@ where
190193
Ok(())
191194
}
192195

196+
async fn auth_middleware<B>(
197+
State(auth): State<Option<Arc<str>>>,
198+
request: Request<B>,
199+
next: Next<B>,
200+
) -> Result<axum::response::Response, StatusCode> {
201+
if let Some(ref auth) = auth {
202+
let Some(auth_header) = request.headers().get("authorization") else {
203+
return Err(StatusCode::UNAUTHORIZED);
204+
};
205+
let Ok(auth_str) = std::str::from_utf8(auth_header.as_bytes()) else {
206+
return Err(StatusCode::UNAUTHORIZED);
207+
};
208+
209+
let mut split = auth_str.split_whitespace();
210+
match split.next() {
211+
Some(s) if s.trim().eq_ignore_ascii_case("basic") => (),
212+
_ => return Err(StatusCode::UNAUTHORIZED),
213+
}
214+
215+
match split.next() {
216+
Some(s) if s.trim() == auth.as_ref() => (),
217+
_ => return Err(StatusCode::UNAUTHORIZED),
218+
}
219+
}
220+
221+
Ok(next.run(request).await)
222+
}
223+
193224
async fn handle_get_index() -> &'static str {
194225
"Welcome to the sqld admin API"
195226
}

libsql-server/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ where
320320
acceptor,
321321
connector,
322322
disable_metrics,
323+
auth_key,
323324
}) = self.admin_api_config
324325
{
325326
task_manager.spawn_with_shutdown_notify(|shutdown| {
@@ -330,6 +331,7 @@ where
330331
connector,
331332
disable_metrics,
332333
shutdown,
334+
auth_key.map(Into::into),
333335
)
334336
});
335337
}

libsql-server/src/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,10 @@ struct Cli {
276276
/// Enables the main runtime deadlock monitor: if the main runtime deadlocks, logs an error
277277
#[clap(long)]
278278
enable_deadlock_monitor: bool,
279+
280+
/// Auth key for the admin API
281+
#[clap(long, env = "LIBSQL_ADMIN_AUTH_KEY", requires = "admin_listen_addr")]
282+
admin_auth_key: Option<String>,
279283
}
280284

281285
#[derive(clap::Subcommand, Debug)]
@@ -468,6 +472,7 @@ async fn make_admin_api_config(config: &Cli) -> anyhow::Result<Option<AdminApiCo
468472
acceptor,
469473
connector,
470474
disable_metrics: false,
475+
auth_key: config.admin_auth_key.clone(),
471476
}))
472477
}
473478
None => Ok(None),

libsql-server/tests/cluster/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub fn make_cluster(sim: &mut Sim, num_replica: usize, disable_namespaces: bool)
3434
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
3535
connector: TurmoilConnector,
3636
disable_metrics: true,
37+
auth_key: None,
3738
}),
3839
rpc_server_config: Some(RpcServerConfig {
3940
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await?,
@@ -64,6 +65,7 @@ pub fn make_cluster(sim: &mut Sim, num_replica: usize, disable_namespaces: bool)
6465
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
6566
connector: TurmoilConnector,
6667
disable_metrics: true,
68+
auth_key: None,
6769
}),
6870
rpc_client_config: Some(RpcClientConfig {
6971
remote_url: "http://primary:4567".into(),

libsql-server/tests/cluster/replica_restart.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ fn replica_restart() {
3434
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
3535
connector: TurmoilConnector,
3636
disable_metrics: true,
37+
auth_key: None,
3738
}),
3839
rpc_server_config: Some(RpcServerConfig {
3940
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await?,
@@ -67,6 +68,7 @@ fn replica_restart() {
6768
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
6869
connector: TurmoilConnector,
6970
disable_metrics: true,
71+
auth_key: None,
7072
}),
7173
rpc_client_config: Some(RpcClientConfig {
7274
remote_url: "http://primary:4567".into(),
@@ -187,6 +189,7 @@ fn primary_regenerate_log_no_replica_restart() {
187189
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
188190
connector: TurmoilConnector,
189191
disable_metrics: true,
192+
auth_key: None,
190193
}),
191194
rpc_server_config: Some(RpcServerConfig {
192195
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await.unwrap(),
@@ -241,6 +244,7 @@ fn primary_regenerate_log_no_replica_restart() {
241244
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
242245
connector: TurmoilConnector,
243246
disable_metrics: true,
247+
auth_key: None,
244248
}),
245249
rpc_client_config: Some(RpcClientConfig {
246250
remote_url: "http://primary:4567".into(),
@@ -365,6 +369,7 @@ fn primary_regenerate_log_with_replica_restart() {
365369
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
366370
connector: TurmoilConnector,
367371
disable_metrics: true,
372+
auth_key: None,
368373
}),
369374
rpc_server_config: Some(RpcServerConfig {
370375
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await.unwrap(),
@@ -421,6 +426,7 @@ fn primary_regenerate_log_with_replica_restart() {
421426
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
422427
connector: TurmoilConnector,
423428
disable_metrics: true,
429+
auth_key: None,
424430
}),
425431
rpc_client_config: Some(RpcClientConfig {
426432
remote_url: "http://primary:4567".into(),

libsql-server/tests/cluster/replication.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fn apply_partial_snapshot() {
4040
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
4141
connector: TurmoilConnector,
4242
disable_metrics: true,
43+
auth_key: None,
4344
}),
4445
rpc_server_config: Some(RpcServerConfig {
4546
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 5050)).await.unwrap(),
@@ -71,6 +72,7 @@ fn apply_partial_snapshot() {
7172
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
7273
connector: TurmoilConnector,
7374
disable_metrics: true,
75+
auth_key: None,
7476
}),
7577
rpc_client_config: Some(RpcClientConfig {
7678
remote_url: "http://primary:5050".into(),
@@ -167,6 +169,7 @@ fn replica_lazy_creation() {
167169
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
168170
connector: TurmoilConnector,
169171
disable_metrics: true,
172+
auth_key: None,
170173
}),
171174
rpc_server_config: Some(RpcServerConfig {
172175
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 5050)).await.unwrap(),
@@ -197,6 +200,7 @@ fn replica_lazy_creation() {
197200
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
198201
connector: TurmoilConnector,
199202
disable_metrics: true,
203+
auth_key: None,
200204
}),
201205
rpc_client_config: Some(RpcClientConfig {
202206
remote_url: "http://primary:5050".into(),

libsql-server/tests/common/http.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use axum::http::HeaderName;
12
use bytes::Bytes;
23
use hyper::Body;
34
use serde::{de::DeserializeOwned, Serialize};
@@ -41,11 +42,27 @@ impl Client {
4142
}
4243

4344
pub(crate) async fn post<T: Serialize>(&self, url: &str, body: T) -> anyhow::Result<Response> {
45+
self.post_with_headers(url, &[], body).await
46+
}
47+
48+
pub(crate) async fn post_with_headers<T: Serialize>(
49+
&self,
50+
url: &str,
51+
headers: &[(HeaderName, &str)],
52+
body: T,
53+
) -> anyhow::Result<Response> {
4454
let bytes: Bytes = serde_json::to_vec(&body)?.into();
4555
let body = Body::from(bytes);
46-
let request = hyper::Request::post(url)
56+
let mut request = hyper::Request::post(url)
4757
.header("Content-Type", "application/json")
4858
.body(body)?;
59+
60+
for (key, val) in headers {
61+
request
62+
.headers_mut()
63+
.insert(key.clone(), val.parse().unwrap());
64+
}
65+
4966
let resp = self.0.request(request).await?;
5067

5168
if resp.status().is_server_error() {

libsql-server/tests/embedded_replica/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ fn make_primary(sim: &mut Sim, path: PathBuf) {
5555
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
5656
connector: TurmoilConnector,
5757
disable_metrics: false,
58+
auth_key: None,
5859
}),
5960
rpc_server_config: Some(RpcServerConfig {
6061
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await?,
@@ -408,6 +409,7 @@ fn replica_primary_reset() {
408409
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
409410
connector: TurmoilConnector,
410411
disable_metrics: true,
412+
auth_key: None,
411413
}),
412414
rpc_server_config: Some(RpcServerConfig {
413415
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await.unwrap(),
@@ -692,6 +694,7 @@ fn replicate_with_snapshots() {
692694
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
693695
connector: TurmoilConnector,
694696
disable_metrics: true,
697+
auth_key: None,
695698
}),
696699
rpc_server_config: Some(RpcServerConfig {
697700
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await.unwrap(),
@@ -1266,6 +1269,7 @@ fn replicated_return() {
12661269
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await.unwrap(),
12671270
connector: TurmoilConnector,
12681271
disable_metrics: true,
1272+
auth_key: None,
12691273
}),
12701274
rpc_server_config: Some(RpcServerConfig {
12711275
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await.unwrap(),
@@ -1395,6 +1399,7 @@ fn replicate_auth() {
13951399
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
13961400
connector: TurmoilConnector,
13971401
disable_metrics: true,
1402+
auth_key: None,
13981403
}),
13991404
rpc_server_config: Some(RpcServerConfig {
14001405
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await?,
@@ -1430,6 +1435,7 @@ fn replicate_auth() {
14301435
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
14311436
connector: TurmoilConnector,
14321437
disable_metrics: true,
1438+
auth_key: None,
14331439
}),
14341440
rpc_client_config: Some(RpcClientConfig {
14351441
remote_url: "http://primary:4567".into(),

libsql-server/tests/namespaces/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ fn make_primary(sim: &mut Sim, path: PathBuf) {
2929
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 9090)).await?,
3030
connector: TurmoilConnector,
3131
disable_metrics: true,
32+
auth_key: None,
3233
}),
3334
rpc_server_config: Some(RpcServerConfig {
3435
acceptor: TurmoilAcceptor::bind(([0, 0, 0, 0], 4567)).await?,

0 commit comments

Comments
 (0)