Skip to content

Commit

Permalink
added check
Browse files Browse the repository at this point in the history
  • Loading branch information
fulmicoton committed Nov 1, 2024
1 parent 5a924f6 commit d6e6c0b
Show file tree
Hide file tree
Showing 46 changed files with 1,073 additions and 536 deletions.
1 change: 1 addition & 0 deletions quickwit/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion quickwit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ members = [
"quickwit-serve",
"quickwit-storage",
"quickwit-telemetry",
"quickwit-telemetry",
]

# The following list excludes `quickwit-metastore-utils` and `quickwit-lambda`
Expand Down
21 changes: 21 additions & 0 deletions quickwit/quickwit-auth/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "quickwit-auth"
version.workspace = true
edition.workspace = true
homepage.workspace = true
documentation.workspace = true
repository.workspace = true
authors.workspace = true
license.workspace = true

[dependencies]
biscuit-auth = { workspace = true, optional=true }
http = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
tonic = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }

[features]
enterprise = ["biscuit-auth"]
67 changes: 67 additions & 0 deletions quickwit/quickwit-auth/src/community.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use std::future::Future;

use crate::AuthorizationError;

pub type AuthorizationToken = ();

pub trait Authorization {
fn attenuate(
&self,
_auth_token: AuthorizationToken,
) -> Result<AuthorizationToken, AuthorizationError> {
Ok(())
}
}

impl<T> Authorization for T {}

pub trait StreamAuthorization {
fn attenuate(
_auth_token: AuthorizationToken,
) -> std::result::Result<AuthorizationToken, AuthorizationError> {
Ok(())
}
}

impl<T> StreamAuthorization for T {}


pub fn get_auth_token(
_req_metadata: &tonic::metadata::MetadataMap,
) -> Result<AuthorizationToken, AuthorizationError> {
Ok(())
}

pub fn set_auth_token(
_auth_token: &AuthorizationToken,
_req_metadata: &mut tonic::metadata::MetadataMap,
) {}

pub fn authorize<R: Authorization>(
_req: &R,
_auth_token: &AuthorizationToken,
) -> Result<(), AuthorizationError> {
Ok(())
}

pub fn build_tonic_stream_request_with_auth_token<R>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
Ok(tonic::Request::new(req))
}

pub fn build_tonic_request_with_auth_token<R: Authorization>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
Ok(tonic::Request::new(req))
}

pub fn authorize_stream<R: StreamAuthorization>(
_auth_token: &AuthorizationToken,
) -> Result<(), AuthorizationError> {
Ok(())
}

pub fn execute_with_authorization<F, O>(_: AuthorizationToken, f: F) -> impl Future<Output=O> where F: Future<Output=O> {
f
}
196 changes: 196 additions & 0 deletions quickwit/quickwit-auth/src/enterprise.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
use std::future::Future;
use std::sync::{Arc, OnceLock};
use crate::AuthorizationError;

use biscuit_auth::macros::authorizer;
use biscuit_auth::{Authorizer, Biscuit, RootKeyProvider};

pub type AuthorizationToken = Biscuit;

tokio::task_local! {
pub static AUTHORIZATION_TOKEN: AuthorizationToken;
}

const AUTHORIZATION_VALUE_PREFIX: &str = "Bearer ";

fn default_operation_authorizer<T: ?Sized>(
auth_token: &AuthorizationToken,
) -> Result<Authorizer, AuthorizationError> {
let request_type = std::any::type_name::<T>();
let operation: &str = request_type.strip_suffix("Request").unwrap();
let mut authorizer: Authorizer = authorizer!(
r#"
operation({operation});
// We generate the actual user role, by doing an union of the rights granted via roles.
user_right($operation) <- role($role), right($role, $operation);
user_right($operation, $resource) <- role($role), right($role, $operation, $resource);
user_right($operation) <- role("root"), operation($operation);
user_right($operation, $resource) <- role("root"), operation($operation), resource($resource);
// Finally we check that we have access to index1 and index2.
check all operation($operation), right($operation);
allow if true;
"#
);
authorizer.set_time();
authorizer.add_token(auth_token)?;
Ok(authorizer)
}

pub trait Authorization {
fn attenuate(
&self,
auth_token: AuthorizationToken,
) -> Result<AuthorizationToken, AuthorizationError>;
fn authorizer(
&self,
auth_token: &AuthorizationToken,
) -> Result<Authorizer, AuthorizationError> {
default_operation_authorizer::<Self>(auth_token)
}
}

pub trait StreamAuthorization {
fn attenuate(
auth_token: AuthorizationToken,
) -> std::result::Result<AuthorizationToken, AuthorizationError> {
Ok(auth_token)
}
fn authorizer(
auth_token: &AuthorizationToken,
) -> std::result::Result<Authorizer, AuthorizationError> {
default_operation_authorizer::<Self>(&auth_token)
}
}

static ROOT_KEY_PROVIDER: OnceLock<Arc<dyn RootKeyProvider + Sync + Send>> = OnceLock::new();

pub fn set_root_key_provider(key_provider: Arc<dyn RootKeyProvider + Sync + Send>) {
if ROOT_KEY_PROVIDER.set(key_provider).is_err() {
tracing::error!("root key provider was already initialized");
}
}

fn get_root_key_provider() -> Arc<dyn RootKeyProvider> {
ROOT_KEY_PROVIDER
.get()
.expect("root key provider should have been initialized beforehand")
.clone()
}

impl From<biscuit_auth::error::Token> for AuthorizationError {
fn from(_token_error: biscuit_auth::error::Token) -> AuthorizationError {
AuthorizationError::InvalidToken
}
}

pub fn get_auth_token(
req_metadata: &tonic::metadata::MetadataMap,
) -> Result<AuthorizationToken, AuthorizationError> {
let authorization_header_value: &str = req_metadata
.get(http::header::AUTHORIZATION.as_str())
.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(biscuit)
}

pub fn set_auth_token(
auth_token: &AuthorizationToken,
req_metadata: &mut tonic::metadata::MetadataMap,
) {
let authorization_header_value = format!("{AUTHORIZATION_VALUE_PREFIX}{auth_token}");
req_metadata.insert(
http::header::AUTHORIZATION.as_str(),
authorization_header_value.parse().unwrap(),
);
}

pub fn authorize<R: Authorization>(
req: &R,
auth_token: &Biscuit,
) -> Result<(), AuthorizationError> {
let mut authorizer = req.authorizer(auth_token)?;
authorizer.add_token(&auth_token)?;
authorizer.authorize()?;
Ok(())
}

pub fn build_tonic_stream_request_with_auth_token<R>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
AUTHORIZATION_TOKEN
.try_with(|token| {
let mut request = tonic::Request::new(req);
set_auth_token(token, request.metadata_mut());
Ok(request)
})
.unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
}

pub fn build_tonic_request_with_auth_token<R: Authorization>(
req: R,
) -> Result<tonic::Request<R>, AuthorizationError> {
AUTHORIZATION_TOKEN
.try_with(|token| {
let mut request = tonic::Request::new(req);
set_auth_token(token, request.metadata_mut());
Ok(request)
})
.unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
}

pub fn authorize_stream<R: StreamAuthorization>(
auth_token: &Biscuit,
) -> Result<(), AuthorizationError> {
let mut authorizer = R::authorizer(auth_token)?;
authorizer.add_token(&auth_token)?;
authorizer.authorize()?;
Ok(())
}

pub fn execute_with_authorization<F, O>(token: AuthorizationToken, f: F) -> impl Future<Output=O> where F: Future<Output=O> {
AUTHORIZATION_TOKEN
.scope(token, f)
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_auth_token() {
let mut req_metadata = tonic::metadata::MetadataMap::new();
let auth_token = "test_token".to_string();
set_auth_token(&auth_token, &mut req_metadata);
let auth_token_retrieved = get_auth_token(&req_metadata).unwrap();
assert_eq!(auth_token_retrieved, auth_token);
}

#[test]
fn test_auth_token_missing() {
let req_metadata = tonic::metadata::MetadataMap::new();
let missing_error = get_auth_token(&req_metadata).unwrap_err();
assert!(matches!(
missing_error,
AuthorizationError::AuthorizationTokenMissing
));
}

#[test]
fn test_auth_token_invalid() {
let mut req_metadata = tonic::metadata::MetadataMap::new();
req_metadata.insert(
http::header::AUTHORIZATION.as_str(),
"some_token".parse().unwrap(),
);
let missing_error = get_auth_token(&req_metadata).unwrap_err();
assert!(matches!(missing_error, AuthorizationError::InvalidToken));
}
}
38 changes: 38 additions & 0 deletions quickwit/quickwit-auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use serde::{Serialize, Deserialize};

#[cfg(not(feature="enterprise"))]
#[path ="community.rs"]
mod implementation;

#[cfg(feature="enterprise")]
#[path ="enterprise.rs"]
mod implementation;

pub use implementation::*;


#[derive(thiserror::Error, Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
pub enum AuthorizationError {
#[error("authorization token missing")]
AuthorizationTokenMissing,
#[error("invalid token")]
InvalidToken,
#[error("permission denied")]
PermissionDenied,
}

impl From<AuthorizationError> for tonic::Status {
fn from(authorization_error: AuthorizationError) -> tonic::Status {
match authorization_error {
AuthorizationError::AuthorizationTokenMissing => {
tonic::Status::unauthenticated("Authorization token missing")
}
AuthorizationError::InvalidToken => {
tonic::Status::unauthenticated("Invalid authorization token")
}
AuthorizationError::PermissionDenied => {
tonic::Status::permission_denied("Permission denied")
}
}
}
}
2 changes: 1 addition & 1 deletion quickwit/quickwit-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ quickwit-metastore = { workspace = true, features = ["testsuite"] }
quickwit-storage = { workspace = true, features = ["testsuite"] }

[features]
enterprise = ["quickwit-config/enterprise"]
enterprise = ["quickwit-config/enterprise", "quickwit-ingest/enterprise", "quickwit-proto/enterprise"]
jemalloc = ["dep:tikv-jemalloc-ctl", "dep:tikv-jemallocator"]
ci-test = []
pprof = ["quickwit-serve/pprof"]
Expand Down
1 change: 1 addition & 0 deletions quickwit/quickwit-codegen/example/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ quickwit-codegen = { workspace = true }

[features]
testsuite = ["mockall"]
enterprise = [ "quickwit-auth/enterprise" ]
Loading

0 comments on commit d6e6c0b

Please sign in to comment.