Skip to content

Commit 4f69bd6

Browse files
committed
added check
1 parent 5a924f6 commit 4f69bd6

File tree

40 files changed

+709
-190
lines changed

40 files changed

+709
-190
lines changed

quickwit/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

quickwit/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ members = [
3636
"quickwit-serve",
3737
"quickwit-storage",
3838
"quickwit-telemetry",
39-
"quickwit-telemetry",
4039
]
4140

4241
# The following list excludes `quickwit-metastore-utils` and `quickwit-lambda`

quickwit/quickwit-auth/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "quickwit-auth"
3+
version.workspace = true
4+
edition.workspace = true
5+
homepage.workspace = true
6+
documentation.workspace = true
7+
repository.workspace = true
8+
authors.workspace = true
9+
license.workspace = true
10+
11+
[dependencies]
12+
biscuit-auth = { workspace = true }
13+
http = { workspace = true }
14+
serde = { workspace = true }
15+
thiserror = { workspace = true }
16+
tonic = { workspace = true }
17+
tokio = { workspace = true }
18+
tracing = { workspace = true }

quickwit/quickwit-auth/src/lib.rs

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
use std::sync::{Arc, OnceLock};
2+
3+
use biscuit_auth::macros::authorizer;
4+
use biscuit_auth::{Authorizer, Biscuit, RootKeyProvider};
5+
use serde::{Deserialize, Serialize};
6+
7+
pub type AuthorizationToken = Biscuit;
8+
9+
tokio::task_local! {
10+
pub static AUTHORIZATION_TOKEN: AuthorizationToken;
11+
}
12+
13+
#[derive(thiserror::Error, Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
14+
pub enum AuthorizationError {
15+
#[error("authorization token missing")]
16+
AuthorizationTokenMissing,
17+
#[error("invalid token")]
18+
InvalidToken,
19+
#[error("permission denied")]
20+
PermissionDenied,
21+
}
22+
23+
const AUTHORIZATION_VALUE_PREFIX: &str = "Bearer ";
24+
25+
fn default_operation_authorizer<T: ?Sized>(
26+
auth_token: &AuthorizationToken,
27+
) -> Result<Authorizer, AuthorizationError> {
28+
let request_type = std::any::type_name::<T>();
29+
let operation: &str = request_type.strip_suffix("Request").unwrap();
30+
let mut authorizer: Authorizer = authorizer!(
31+
r#"
32+
operation({operation});
33+
34+
// We generate the actual user role, by doing an union of the rights granted via roles.
35+
user_right($operation) <- role($role), right($role, $operation);
36+
user_right($operation, $resource) <- role($role), right($role, $operation, $resource);
37+
user_right($operation) <- role("root"), operation($operation);
38+
user_right($operation, $resource) <- role("root"), operation($operation), resource($resource);
39+
40+
// Finally we check that we have access to index1 and index2.
41+
check all operation($operation), right($operation);
42+
43+
allow if true;
44+
"#
45+
);
46+
authorizer.set_time();
47+
authorizer.add_token(auth_token)?;
48+
Ok(authorizer)
49+
}
50+
51+
pub trait Authorization {
52+
fn attenuate(
53+
&self,
54+
auth_token: AuthorizationToken,
55+
) -> Result<AuthorizationToken, AuthorizationError>;
56+
fn authorizer(
57+
&self,
58+
auth_token: &AuthorizationToken,
59+
) -> Result<Authorizer, AuthorizationError> {
60+
default_operation_authorizer::<Self>(auth_token)
61+
}
62+
}
63+
64+
pub trait StreamAuthorization {
65+
fn attenuate(
66+
auth_token: AuthorizationToken,
67+
) -> std::result::Result<AuthorizationToken, AuthorizationError> {
68+
Ok(auth_token)
69+
}
70+
fn authorizer(
71+
auth_token: &AuthorizationToken,
72+
) -> std::result::Result<Authorizer, AuthorizationError> {
73+
default_operation_authorizer::<Self>(&auth_token)
74+
}
75+
}
76+
77+
static ROOT_KEY_PROVIDER: OnceLock<Arc<dyn RootKeyProvider + Sync + Send>> = OnceLock::new();
78+
79+
pub fn set_root_key_provider(key_provider: Arc<dyn RootKeyProvider + Sync + Send>) {
80+
if ROOT_KEY_PROVIDER.set(key_provider).is_err() {
81+
tracing::error!("root key provider was already initialized");
82+
}
83+
}
84+
85+
fn get_root_key_provider() -> Arc<dyn RootKeyProvider> {
86+
ROOT_KEY_PROVIDER
87+
.get()
88+
.expect("root key provider should have been initialized beforehand")
89+
.clone()
90+
}
91+
92+
impl From<biscuit_auth::error::Token> for AuthorizationError {
93+
fn from(_token_error: biscuit_auth::error::Token) -> AuthorizationError {
94+
AuthorizationError::InvalidToken
95+
}
96+
}
97+
98+
pub fn get_auth_token(
99+
req_metadata: &tonic::metadata::MetadataMap,
100+
) -> Result<AuthorizationToken, AuthorizationError> {
101+
let authorization_header_value: &str = req_metadata
102+
.get(http::header::AUTHORIZATION.as_str())
103+
.ok_or(AuthorizationError::AuthorizationTokenMissing)?
104+
.to_str()
105+
.map_err(|_| AuthorizationError::InvalidToken)?;
106+
let authorization_token_str: &str = authorization_header_value
107+
.strip_prefix(AUTHORIZATION_VALUE_PREFIX)
108+
.ok_or(AuthorizationError::InvalidToken)?;
109+
let biscuit: Biscuit = Biscuit::from_base64(authorization_token_str, get_root_key_provider())?;
110+
Ok(biscuit)
111+
}
112+
113+
pub fn set_auth_token(
114+
auth_token: &AuthorizationToken,
115+
req_metadata: &mut tonic::metadata::MetadataMap,
116+
) {
117+
let authorization_header_value = format!("{AUTHORIZATION_VALUE_PREFIX}{auth_token}");
118+
req_metadata.insert(
119+
http::header::AUTHORIZATION.as_str(),
120+
authorization_header_value.parse().unwrap(),
121+
);
122+
}
123+
124+
pub fn authorize<R: Authorization>(
125+
req: &R,
126+
auth_token: &Biscuit,
127+
) -> Result<(), AuthorizationError> {
128+
let mut authorizer = req.authorizer(auth_token)?;
129+
authorizer.add_token(&auth_token)?;
130+
authorizer.authorize()?;
131+
Ok(())
132+
}
133+
134+
pub fn build_tonic_stream_request_with_auth_token<R>(
135+
req: R,
136+
) -> Result<tonic::Request<R>, AuthorizationError> {
137+
AUTHORIZATION_TOKEN
138+
.try_with(|token| {
139+
let mut request = tonic::Request::new(req);
140+
set_auth_token(token, request.metadata_mut());
141+
Ok(request)
142+
})
143+
.unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
144+
}
145+
146+
pub fn build_tonic_request_with_auth_token<R: Authorization>(
147+
req: R,
148+
) -> Result<tonic::Request<R>, AuthorizationError> {
149+
AUTHORIZATION_TOKEN
150+
.try_with(|token| {
151+
let mut request = tonic::Request::new(req);
152+
set_auth_token(token, request.metadata_mut());
153+
Ok(request)
154+
})
155+
.unwrap_or(Err(AuthorizationError::AuthorizationTokenMissing))
156+
}
157+
158+
pub fn authorize_stream<R: StreamAuthorization>(
159+
auth_token: &Biscuit,
160+
) -> Result<(), AuthorizationError> {
161+
let mut authorizer = R::authorizer(auth_token)?;
162+
authorizer.add_token(&auth_token)?;
163+
authorizer.authorize()?;
164+
Ok(())
165+
}
166+
167+
impl From<AuthorizationError> for tonic::Status {
168+
fn from(authorization_error: AuthorizationError) -> tonic::Status {
169+
match authorization_error {
170+
AuthorizationError::AuthorizationTokenMissing => {
171+
tonic::Status::unauthenticated("Authorization token missing")
172+
}
173+
AuthorizationError::InvalidToken => {
174+
tonic::Status::unauthenticated("Invalid authorization token")
175+
}
176+
AuthorizationError::PermissionDenied => {
177+
tonic::Status::permission_denied("Permission denied")
178+
}
179+
}
180+
}
181+
}
182+
183+
#[cfg(test)]
184+
mod tests {
185+
use super::*;
186+
187+
#[test]
188+
fn test_auth_token() {
189+
let mut req_metadata = tonic::metadata::MetadataMap::new();
190+
let auth_token = "test_token".to_string();
191+
set_auth_token(&auth_token, &mut req_metadata);
192+
let auth_token_retrieved = get_auth_token(&req_metadata).unwrap();
193+
assert_eq!(auth_token_retrieved, auth_token);
194+
}
195+
196+
#[test]
197+
fn test_auth_token_missing() {
198+
let req_metadata = tonic::metadata::MetadataMap::new();
199+
let missing_error = get_auth_token(&req_metadata).unwrap_err();
200+
assert!(matches!(
201+
missing_error,
202+
AuthorizationError::AuthorizationTokenMissing
203+
));
204+
}
205+
206+
#[test]
207+
fn test_auth_token_invalid() {
208+
let mut req_metadata = tonic::metadata::MetadataMap::new();
209+
req_metadata.insert(
210+
http::header::AUTHORIZATION.as_str(),
211+
"some_token".parse().unwrap(),
212+
);
213+
let missing_error = get_auth_token(&req_metadata).unwrap_err();
214+
assert!(matches!(missing_error, AuthorizationError::InvalidToken));
215+
}
216+
}
Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,46 @@
1-
use quickwit_auth::Authorization;
2-
use quickwit_auth::AuthorizationError;
3-
use quickwit_auth::AuthorizationToken;
4-
use quickwit_auth::StreamAuthorization;
1+
// The Quickwit Enterprise Edition (EE) license
2+
// Copyright (c) 2024-present Quickwit Inc.
3+
//
4+
// With regard to the Quickwit Software:
5+
//
6+
// This software and associated documentation files (the "Software") may only be
7+
// used in production, if you (and any entity that you represent) hold a valid
8+
// Quickwit Enterprise license corresponding to your usage.
9+
//
10+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
11+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
12+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
13+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
14+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
15+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
16+
// SOFTWARE.
517

6-
use crate::GoodbyeRequest;
7-
use crate::HelloRequest;
8-
use crate::PingRequest;
18+
use quickwit_auth::{Authorization, AuthorizationError, AuthorizationToken, StreamAuthorization};
19+
20+
use crate::{GoodbyeRequest, HelloRequest, PingRequest};
921

1022
impl Authorization for HelloRequest {
11-
fn attenuate(&self, auth_token: quickwit_auth::AuthorizationToken) -> Result<quickwit_auth::AuthorizationToken, AuthorizationError> {
23+
fn attenuate(
24+
&self,
25+
auth_token: quickwit_auth::AuthorizationToken,
26+
) -> Result<quickwit_auth::AuthorizationToken, AuthorizationError> {
1227
Ok(auth_token)
1328
}
1429
}
1530

1631
impl Authorization for GoodbyeRequest {
17-
fn attenuate(&self, auth_token: quickwit_auth::AuthorizationToken) -> Result<AuthorizationToken, AuthorizationError> {
32+
fn attenuate(
33+
&self,
34+
auth_token: quickwit_auth::AuthorizationToken,
35+
) -> Result<AuthorizationToken, AuthorizationError> {
1836
Ok(auth_token)
1937
}
2038
}
2139

2240
impl StreamAuthorization for PingRequest {
23-
fn attenuate(auth_token: quickwit_auth::AuthorizationToken) -> Result<AuthorizationToken, AuthorizationError> {
41+
fn attenuate(
42+
auth_token: quickwit_auth::AuthorizationToken,
43+
) -> Result<AuthorizationToken, AuthorizationError> {
2444
Ok(auth_token)
2545
}
2646
}

quickwit/quickwit-codegen/example/src/codegen/hello.rs

Lines changed: 8 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

quickwit/quickwit-codegen/example/src/error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use std::fmt;
2121

2222
use quickwit_actors::AskError;
23+
use quickwit_auth::AuthorizationError;
2324
use quickwit_proto::error::GrpcServiceError;
2425
pub use quickwit_proto::error::{grpc_error_to_grpc_status, grpc_status_to_service_error};
2526
use quickwit_proto::{ServiceError, ServiceErrorCode};
@@ -38,6 +39,8 @@ pub enum HelloError {
3839
TooManyRequests,
3940
#[error("service unavailable: {0}")]
4041
Unavailable(String),
42+
#[error("unauthorized: {0}")]
43+
Unauthorized(#[from] AuthorizationError),
4144
}
4245

4346
impl ServiceError for HelloError {
@@ -48,6 +51,7 @@ impl ServiceError for HelloError {
4851
Self::Timeout(_) => ServiceErrorCode::Timeout,
4952
Self::TooManyRequests => ServiceErrorCode::TooManyRequests,
5053
Self::Unavailable(_) => ServiceErrorCode::Unavailable,
54+
Self::Unauthorized(_) => ServiceErrorCode::Unauthorized,
5155
}
5256
}
5357
}

quickwit/quickwit-codegen/example/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919

2020
mod error;
2121

22+
mod authorization;
2223
#[path = "codegen/hello.rs"]
2324
mod hello;
24-
mod authorization;
2525

2626
use std::sync::atomic::{AtomicUsize, Ordering};
2727
use std::sync::Arc;

0 commit comments

Comments
 (0)