diff --git a/config/quickwit.yaml b/config/quickwit.yaml
index 7072c569fc0..1d03988b737 100644
--- a/config/quickwit.yaml
+++ b/config/quickwit.yaml
@@ -150,3 +150,9 @@ indexer:
jaeger:
enable_endpoint: ${QW_ENABLE_JAEGER_ENDPOINT:-true}
+
+license: ${QW_LICENSE}
+
+# authorization:
+# root_key: ${QW_ROOT_KEY}
+# node_token: ${QW_NODE_TOKEN}
diff --git a/quickwit/Cargo.lock b/quickwit/Cargo.lock
index 905e999110a..d1df21bb5e7 100644
--- a/quickwit/Cargo.lock
+++ b/quickwit/Cargo.lock
@@ -1684,6 +1684,26 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom 0.2.15",
+ "once_cell",
+ "tiny-keccak",
+]
+
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@@ -5951,6 +5971,7 @@ dependencies = [
name = "quickwit-authorize"
version = "0.8.0"
dependencies = [
+ "anyhow",
"biscuit-auth",
"futures",
"http 0.2.12",
@@ -5959,6 +5980,7 @@ dependencies = [
"serde",
"thiserror",
"tokio",
+ "tokio-inherit-task-local",
"tonic",
"tower",
"tracing",
@@ -6134,6 +6156,7 @@ dependencies = [
"tempfile",
"thiserror",
"tokio",
+ "tokio-inherit-task-local",
"tokio-metrics",
"tokio-stream",
"tonic",
@@ -6158,6 +6181,7 @@ dependencies = [
"json_comments",
"new_string_template",
"once_cell",
+ "quickwit-authorize",
"quickwit-common",
"quickwit-doc-mapper",
"quickwit-license",
@@ -6621,6 +6645,7 @@ version = "0.8.0"
dependencies = [
"anyhow",
"async-trait",
+ "biscuit-auth",
"bytes",
"bytesize",
"bytestring",
@@ -6772,6 +6797,7 @@ dependencies = [
"prost 0.11.9",
"prost-types 0.11.9",
"quickwit-actors",
+ "quickwit-authorize",
"quickwit-cluster",
"quickwit-common",
"quickwit-config",
@@ -8888,6 +8914,16 @@ dependencies = [
"windows-sys 0.52.0",
]
+[[package]]
+name = "tokio-inherit-task-local"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d42db185acdff44279cff7f8765608129ae4a01a2f955008a4f96054c75e77ac"
+dependencies = [
+ "const-random",
+ "tokio",
+]
+
[[package]]
name = "tokio-io-timeout"
version = "1.2.0"
diff --git a/quickwit/Cargo.toml b/quickwit/Cargo.toml
index c8878d3c515..f2578232b1e 100644
--- a/quickwit/Cargo.toml
+++ b/quickwit/Cargo.toml
@@ -240,6 +240,7 @@ tikv-jemalloc-ctl = "0.5"
tikv-jemallocator = "0.5"
time = { version = "0.3", features = ["std", "formatting", "macros"] }
tokio = { version = "1.40", features = ["full"] }
+tokio-inherit-task-local = "0.2"
tokio-metrics = { version = "0.3.1", features = ["rt"] }
tokio-stream = { version = "0.1", features = ["sync"] }
tokio-util = { version = "0.7", features = ["full"] }
diff --git a/quickwit/quickwit-authorize/Cargo.toml b/quickwit/quickwit-authorize/Cargo.toml
index 4c3985fcff3..1b21761a408 100644
--- a/quickwit/quickwit-authorize/Cargo.toml
+++ b/quickwit/quickwit-authorize/Cargo.toml
@@ -9,10 +9,12 @@ authors.workspace = true
license.workspace = true
[dependencies]
+anyhow = { workspace = true, optional = true }
tower = { workspace = true}
biscuit-auth = { workspace = true, optional=true }
futures = { workspace = true }
http = { workspace = true }
+tokio-inherit-task-local = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
tonic = { workspace = true }
@@ -23,4 +25,4 @@ pin-project = { workspace = true }
quickwit-common = { workspace = true }
[features]
-enterprise = ["biscuit-auth"]
+enterprise = ["dep:biscuit-auth", "dep:anyhow"]
diff --git a/quickwit/quickwit-authorize/src/community.rs b/quickwit/quickwit-authorize/src/community/mod.rs
similarity index 100%
rename from quickwit/quickwit-authorize/src/community.rs
rename to quickwit/quickwit-authorize/src/community/mod.rs
diff --git a/quickwit/quickwit-authorize/src/authorization_layer.rs b/quickwit/quickwit-authorize/src/enterprise/authorization_layer.rs
similarity index 63%
rename from quickwit/quickwit-authorize/src/authorization_layer.rs
rename to quickwit/quickwit-authorize/src/enterprise/authorization_layer.rs
index 3131bef4715..ae29555ee02 100644
--- a/quickwit/quickwit-authorize/src/authorization_layer.rs
+++ b/quickwit/quickwit-authorize/src/enterprise/authorization_layer.rs
@@ -1,3 +1,22 @@
+// Copyright (C) 2024 Quickwit, Inc.
+//
+// Quickwit is offered under the AGPL v3.0 and as commercial software.
+// For commercial licensing, contact us at hello@quickwit.io.
+//
+// AGPL:
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
use std::fmt;
use std::task::{Context, Poll};
@@ -7,6 +26,7 @@ use tower::{Layer, Service};
use crate::AuthorizationError;
+#[derive(Clone, Copy, Debug)]
pub struct AuthorizationLayer;
impl Layer for AuthorizationLayer {
diff --git a/quickwit/quickwit-authorize/src/enterprise/authorization_token_extraction_layer.rs b/quickwit/quickwit-authorize/src/enterprise/authorization_token_extraction_layer.rs
new file mode 100644
index 00000000000..a2a9b08bfef
--- /dev/null
+++ b/quickwit/quickwit-authorize/src/enterprise/authorization_token_extraction_layer.rs
@@ -0,0 +1,74 @@
+// Copyright (C) 2024 Quickwit, Inc.
+//
+// Quickwit is offered under the AGPL v3.0 and as commercial software.
+// For commercial licensing, contact us at hello@quickwit.io.
+//
+// AGPL:
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+use std::task::{Context, Poll};
+
+use futures::future::Either;
+use http::Request;
+use tokio::task::futures::TaskLocalFuture;
+use tokio_inherit_task_local::TaskLocalInheritableTable;
+use tower::{Layer, Service};
+use tracing::debug;
+
+use super::AuthorizationToken;
+
+#[derive(Clone, Copy, Debug)]
+pub struct AuthorizationTokenExtractionLayer;
+
+impl Layer for AuthorizationTokenExtractionLayer {
+ type Service = AuthorizationTokenExtractionService;
+
+ fn layer(&self, service: S) -> Self::Service {
+ AuthorizationTokenExtractionService { service }
+ }
+}
+
+#[derive(Clone)]
+pub struct AuthorizationTokenExtractionService {
+ service: S,
+}
+
+fn get_authorization_token_opt(headers: &http::HeaderMap) -> Option {
+ let authorization_header_value = headers.get("Authorization")?;
+ let authorization_header_str = authorization_header_value.to_str().ok()?;
+ crate::get_auth_token_from_str(authorization_header_str).ok()
+}
+
+impl Service> for AuthorizationTokenExtractionService
+where S: Service>
+{
+ type Response = S::Response;
+ type Error = S::Error;
+ type Future = Either>;
+
+ fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> {
+ self.service.poll_ready(cx)
+ }
+
+ fn call(&mut self, request: Request) -> Self::Future {
+ let authorization_token_opt = get_authorization_token_opt(request.headers());
+ debug!(authorization_token_opt = ?authorization_token_opt, "Authorization token extracted");
+ let fut = self.service.call(request);
+ if let Some(authorization_token) = authorization_token_opt {
+ Either::Right(crate::execute_with_authorization(authorization_token, fut))
+ } else {
+ Either::Left(fut)
+ }
+ }
+}
diff --git a/quickwit/quickwit-authorize/src/enterprise.rs b/quickwit/quickwit-authorize/src/enterprise/mod.rs
similarity index 85%
rename from quickwit/quickwit-authorize/src/enterprise.rs
rename to quickwit/quickwit-authorize/src/enterprise/mod.rs
index e1aa02e4436..407bf60d21e 100644
--- a/quickwit/quickwit-authorize/src/enterprise.rs
+++ b/quickwit/quickwit-authorize/src/enterprise/mod.rs
@@ -19,15 +19,31 @@
// components are licensed under the original license provided by the owner of the
// applicable component.
+mod authorization_layer;
+mod authorization_token_extraction_layer;
+
use std::future::Future;
use std::str::FromStr;
use std::sync::{Arc, OnceLock};
+use anyhow::Context;
+pub use authorization_layer::AuthorizationLayer;
+pub use authorization_token_extraction_layer::AuthorizationTokenExtractionLayer;
use biscuit_auth::macros::authorizer;
use biscuit_auth::{Authorizer, Biscuit, RootKeyProvider};
+use tokio::task::futures::TaskLocalFuture;
+use tokio_inherit_task_local::TaskLocalInheritableTable;
+use tracing::info;
use crate::AuthorizationError;
+tokio_inherit_task_local::inheritable_task_local! {
+ pub static AUTHORIZATION_TOKEN: AuthorizationToken;
+}
+
+static ROOT_KEY_PROVIDER: OnceLock> = OnceLock::new();
+static NODE_TOKEN: OnceLock> = OnceLock::new();
+
pub struct AuthorizationToken(Biscuit);
impl AuthorizationToken {
@@ -54,7 +70,22 @@ impl std::fmt::Debug for AuthorizationToken {
}
}
-static ROOT_KEY_PROVIDER: OnceLock> = OnceLock::new();
+pub fn set_node_token_hex(node_token_hex: &str) -> anyhow::Result<()> {
+ let node_token =
+ AuthorizationToken::from_str(node_token_hex).context("failed to set node token")?;
+ if NODE_TOKEN.set(Arc::new(node_token)).is_err() {
+ tracing::error!("node token was already initialized");
+ }
+ Ok(())
+}
+
+pub fn set_root_public_key(root_key_hex: &str) -> anyhow::Result<()> {
+ let public_key = biscuit_auth::PublicKey::from_bytes_hex(root_key_hex)
+ .context("failed to parse root public key")?;
+ let key_provider: Arc = Arc::new(public_key);
+ set_root_key_provider(key_provider);
+ Ok(())
+}
pub fn set_root_key_provider(key_provider: Arc) {
if ROOT_KEY_PROVIDER.set(key_provider).is_err() {
@@ -79,10 +110,6 @@ impl FromStr for AuthorizationToken {
}
}
-tokio::task_local! {
- pub static AUTHORIZATION_TOKEN: AuthorizationToken;
-}
-
const AUTHORIZATION_VALUE_PREFIX: &str = "Bearer ";
fn default_operation_authorizer(
@@ -146,6 +173,16 @@ impl From for AuthorizationError {
}
}
+pub fn get_auth_token_from_str(
+ authorization_header_value: &str,
+) -> Result {
+ let authorization_token_str: &str = authorization_header_value
+ .strip_prefix(AUTHORIZATION_VALUE_PREFIX)
+ .ok_or(AuthorizationError::InvalidToken)?;
+ let biscuit: Biscuit = Biscuit::from_base64(authorization_token_str, get_root_key_provider())?;
+ Ok(AuthorizationToken(biscuit))
+}
+
pub fn get_auth_token(
req_metadata: &tonic::metadata::MetadataMap,
) -> Result {
@@ -154,11 +191,7 @@ pub fn get_auth_token(
.ok_or(AuthorizationError::AuthorizationTokenMissing)?
.to_str()
.map_err(|_| AuthorizationError::InvalidToken)?;
- let authorization_token_str: &str = authorization_header_value
- .strip_prefix(AUTHORIZATION_VALUE_PREFIX)
- .ok_or(AuthorizationError::InvalidToken)?;
- let biscuit: Biscuit = Biscuit::from_base64(authorization_token_str, get_root_key_provider())?;
- Ok(AuthorizationToken(biscuit))
+ get_auth_token_from_str(authorization_header_value)
}
pub fn set_auth_token(
@@ -216,15 +249,17 @@ pub fn authorize_stream(
}
pub fn authorize_request(req: &R) -> Result<(), AuthorizationError> {
- AUTHORIZATION_TOKEN
+ let res = AUTHORIZATION_TOKEN
.try_with(|auth_token| authorize(req, auth_token))
- .unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
+ .unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing));
+ info!("request authorization");
+ res
}
pub fn execute_with_authorization(
token: AuthorizationToken,
f: F,
-) -> impl Future