Skip to content

Commit f8fa500

Browse files
authored
feat: Enrich Authorizer for Namespaces and Warehouses (lakekeeper#1480)
1 parent 61b03a8 commit f8fa500

35 files changed

Lines changed: 899 additions & 553 deletions

.sqlx/query-8818aabd4a0acdb79ff4ce5c3c64b85dd9bb7b2a4facd301be48ce088f39cb02.json renamed to .sqlx/query-5bd88e2962ade07ae621b69644240f5c5e48aefc4990b3804bf23c8ee6c9df53.json

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

.sqlx/query-699149ae3d1c7432aae5ad504fa8170521a648955229029d9b79da336aa82f2e.json renamed to .sqlx/query-763b066e0be1435303b875e81fe228e2aa7e421e9c65624695c8078f83d6b011.json

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

crates/authz-openfga/src/api.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1704,7 +1704,7 @@ mod tests {
17041704

17051705
mod openfga_integration_tests {
17061706
use lakekeeper::{
1707-
service::{authn::UserId, authz::Authorizer},
1707+
service::{authn::UserId, authz::Authorizer, ResolvedWarehouse},
17081708
tokio,
17091709
};
17101710
use openfga_client::client::TupleKey;
@@ -1822,6 +1822,7 @@ mod tests {
18221822
let res = authorizer
18231823
.are_allowed_namespace_actions_impl(
18241824
&RequestMetadata::random_human(user_id_assignee.clone()),
1825+
&ResolvedWarehouse::new_random(),
18251826
&namespaces
18261827
.iter()
18271828
.map(|id| (id, AllNamespaceRelations::CanDelete))
@@ -1843,6 +1844,7 @@ mod tests {
18431844
let res = authorizer
18441845
.are_allowed_namespace_actions_impl(
18451846
&RequestMetadata::random_human(user_id_assignee.clone()),
1847+
&ResolvedWarehouse::new_random(),
18461848
&namespaces
18471849
.iter()
18481850
.map(|id| (id, AllNamespaceRelations::CanDelete))

crates/authz-openfga/src/authorizer.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ use lakekeeper::{
1212
},
1313
health::Health,
1414
Actor, AuthZTableInfo, AuthZViewInfo, CatalogStore, ErrorModel, NamespaceHierarchy,
15-
NamespaceId, RoleId, SecretStore, ServerId, State, TableId, UserId, ViewId,
15+
NamespaceId, ResolvedWarehouse, RoleId, SecretStore, ServerId, State, TableId, UserId,
16+
ViewId,
1617
},
1718
tokio::sync::RwLock,
1819
ProjectId, WarehouseId,
@@ -292,13 +293,13 @@ impl Authorizer for OpenFGAAuthorizer {
292293
async fn is_allowed_warehouse_action_impl(
293294
&self,
294295
metadata: &RequestMetadata,
295-
warehouse_id: WarehouseId,
296+
warehouse: &ResolvedWarehouse,
296297
action: Self::WarehouseAction,
297298
) -> Result<bool, AuthorizationBackendUnavailable> {
298299
self.check(CheckRequestTupleKey {
299300
user: metadata.actor().to_openfga(),
300301
relation: action.to_string(),
301-
object: warehouse_id.to_openfga(),
302+
object: warehouse.warehouse_id.to_openfga(),
302303
})
303304
.await
304305
.map_err(Into::into)
@@ -307,14 +308,14 @@ impl Authorizer for OpenFGAAuthorizer {
307308
async fn are_allowed_warehouse_actions_impl(
308309
&self,
309310
metadata: &RequestMetadata,
310-
warehouses_with_actions: &[(WarehouseId, Self::WarehouseAction)],
311+
warehouses_with_actions: &[(&ResolvedWarehouse, Self::WarehouseAction)],
311312
) -> std::result::Result<Vec<bool>, AuthorizationBackendUnavailable> {
312313
let items: Vec<_> = warehouses_with_actions
313314
.iter()
314-
.map(|(id, a)| CheckRequestTupleKey {
315+
.map(|(wh, a)| CheckRequestTupleKey {
315316
user: metadata.actor().to_openfga(),
316317
relation: a.to_string(),
317-
object: id.to_openfga(),
318+
object: wh.warehouse_id.to_openfga(),
318319
})
319320
.collect();
320321
self.batch_check(items).await.map_err(Into::into)
@@ -323,6 +324,7 @@ impl Authorizer for OpenFGAAuthorizer {
323324
async fn is_allowed_namespace_action_impl(
324325
&self,
325326
metadata: &RequestMetadata,
327+
_warehouse: &ResolvedWarehouse,
326328
namespace: &NamespaceHierarchy,
327329
action: Self::NamespaceAction,
328330
) -> Result<bool, AuthorizationBackendUnavailable> {
@@ -338,6 +340,7 @@ impl Authorizer for OpenFGAAuthorizer {
338340
async fn are_allowed_namespace_actions_impl(
339341
&self,
340342
metadata: &RequestMetadata,
343+
_warehouse: &ResolvedWarehouse,
341344
actions: &[(&NamespaceHierarchy, Self::NamespaceAction)],
342345
) -> Result<Vec<bool>, AuthorizationBackendUnavailable> {
343346
let items: Vec<_> = actions

crates/authz-openfga/src/check.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ use lakekeeper::{
44
axum::{extract::State as AxumState, Extension, Json},
55
iceberg::{NamespaceIdent, TableIdent},
66
service::{
7-
authz::{AuthZTableOps, AuthZViewOps, Authorizer, AuthzNamespaceOps as _},
7+
authz::{
8+
AuthZTableOps, AuthZViewOps, Authorizer, AuthzNamespaceOps as _, AuthzWarehouseOps,
9+
},
810
AuthZTableInfo, AuthZViewInfo as _, CatalogNamespaceOps, CatalogStore, CatalogTabularOps,
9-
NamespaceId, NamespaceIdentOrId, Result, SecretStore, State, TableId, TableIdentOrId,
10-
TabularListFlags, ViewId, ViewIdentOrId,
11+
CatalogWarehouseOps, NamespaceId, NamespaceIdentOrId, Result, SecretStore, State, TableId,
12+
TableIdentOrId, TabularListFlags, ViewId, ViewIdentOrId,
1113
},
12-
ProjectId, WarehouseId,
14+
tokio, ProjectId, WarehouseId,
1315
};
1416
use openfga_client::client::CheckRequestTupleKey;
1517
use serde::{Deserialize, Serialize};
@@ -217,14 +219,17 @@ async fn check_namespace<C: CatalogStore, S: SecretStore>(
217219
warehouse_id,
218220
} => (*warehouse_id, NamespaceIdentOrId::from(namespace.clone())),
219221
};
220-
let namespace = C::get_namespace(
221-
warehouse_id,
222-
user_provided_ns.clone(),
223-
api_context.v1_state.catalog,
224-
)
225-
.await;
222+
let (warehouse, namespace) = tokio::join!(
223+
C::get_active_warehouse_by_id(warehouse_id, api_context.v1_state.catalog.clone(),),
224+
C::get_namespace(
225+
warehouse_id,
226+
user_provided_ns.clone(),
227+
api_context.v1_state.catalog,
228+
)
229+
);
230+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
226231
let namespace = authorizer
227-
.require_namespace_action(metadata, warehouse_id, user_provided_ns, namespace, action)
232+
.require_namespace_action(metadata, &warehouse, user_provided_ns, namespace, action)
228233
.await?;
229234

230235
Ok(namespace.namespace_id().to_openfga())

crates/lakekeeper/src/api/management/v1/namespace.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use super::{ApiServer, ProtectionResponse};
44
use crate::{
55
api::{ApiContext, RequestMetadata, Result},
66
service::{
7-
authz::{Authorizer, AuthzNamespaceOps, CatalogNamespaceAction},
8-
CatalogNamespaceOps, CatalogStore, NamespaceId, SecretStore, State, Transaction,
7+
authz::{Authorizer, AuthzNamespaceOps, AuthzWarehouseOps, CatalogNamespaceAction},
8+
CatalogNamespaceOps, CatalogStore, CatalogWarehouseOps, NamespaceId, SecretStore, State,
9+
Transaction,
910
},
1011
WarehouseId,
1112
};
@@ -30,13 +31,16 @@ where
3031
// ------------------- AUTHZ -------------------
3132
let authorizer = state.v1_state.authz.clone();
3233

34+
let warehouse =
35+
C::get_active_warehouse_by_id(warehouse_id, state.v1_state.catalog.clone()).await;
36+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
3337
let namespace =
3438
C::get_namespace(warehouse_id, namespace_id, state.v1_state.catalog.clone()).await;
3539

3640
authorizer
3741
.require_namespace_action(
3842
&request_metadata,
39-
warehouse_id,
43+
&warehouse,
4044
namespace_id,
4145
namespace,
4246
CatalogNamespaceAction::CanDelete,
@@ -86,13 +90,16 @@ where
8690
// ------------------- AUTHZ -------------------
8791
let authorizer = state.v1_state.authz.clone();
8892

93+
let warehouse =
94+
C::get_active_warehouse_by_id(warehouse_id, state.v1_state.catalog.clone()).await;
95+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
8996
let namespace =
9097
C::get_namespace(warehouse_id, namespace_id, state.v1_state.catalog.clone()).await;
9198

9299
let namespace = authorizer
93100
.require_namespace_action(
94101
&request_metadata,
95-
warehouse_id,
102+
&warehouse,
96103
namespace_id,
97104
namespace,
98105
CatalogNamespaceAction::CanGetMetadata,

crates/lakekeeper/src/api/management/v1/project.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ use crate::{
2020
ListProjectsResponse as AuthZListProjectsResponse,
2121
},
2222
secrets::SecretStore,
23-
CatalogStore, State, Transaction,
23+
CatalogStore, CatalogWarehouseOps, State, Transaction,
2424
},
25-
ProjectId,
25+
ProjectId, WarehouseId,
2626
};
2727

2828
#[derive(Debug, Clone, Serialize)]
@@ -244,10 +244,17 @@ pub trait Service<C: CatalogStore, A: Authorizer, S: SecretStore> {
244244

245245
match request.warehouse {
246246
WarehouseFilter::WarehouseId { id } => {
247+
let warehouse = C::get_active_warehouse_by_id(
248+
WarehouseId::from(id),
249+
context.v1_state.catalog.clone(),
250+
)
251+
.await;
252+
247253
authorizer
248254
.require_warehouse_action(
249255
&request_metadata,
250256
id.into(),
257+
warehouse,
251258
CatalogWarehouseAction::CanGetMetadata,
252259
)
253260
.await?;

crates/lakekeeper/src/api/management/v1/tabular.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
AuthZCannotUseWarehouseId, AuthZTableOps, Authorizer, AuthzWarehouseOps,
1010
CatalogTableAction, CatalogViewAction, CatalogWarehouseAction,
1111
},
12-
CatalogStore, CatalogTabularOps, SecretStore, State, TabularId,
12+
CatalogStore, CatalogTabularOps, CatalogWarehouseOps, SecretStore, State, TabularId,
1313
},
1414
WarehouseId,
1515
};
@@ -33,12 +33,16 @@ where
3333
// -------------------- AUTHZ --------------------
3434
let authorizer = context.v1_state.authz;
3535

36+
let warehouse =
37+
C::get_active_warehouse_by_id(warehouse_id, context.v1_state.catalog.clone()).await;
38+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
39+
3640
let [authz_can_use, authz_list_all] = authorizer
3741
.are_allowed_warehouse_actions_arr(
3842
&request_metadata,
3943
&[
40-
(warehouse_id, CatalogWarehouseAction::CanUse),
41-
(warehouse_id, CatalogWarehouseAction::CanListEverything),
44+
(&warehouse, CatalogWarehouseAction::CanUse),
45+
(&warehouse, CatalogWarehouseAction::CanListEverything),
4246
],
4347
)
4448
.await?

crates/lakekeeper/src/api/management/v1/tasks.rs

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ use crate::{
2121
TaskEntityNamed, TaskFilter, TaskId, TaskOutcome as TQTaskOutcome, TaskQueueName,
2222
TaskStatus as TQTaskStatus,
2323
},
24-
CatalogStore, CatalogTabularOps, CatalogTaskOps, InvalidTabularIdentifier, ResolvedTask,
25-
Result, SecretStore, State, TableNamed, TabularId, TabularListFlags, Transaction,
26-
ViewNamed, ViewOrTableInfo,
24+
CatalogStore, CatalogTabularOps, CatalogTaskOps, CatalogWarehouseOps,
25+
InvalidTabularIdentifier, ResolvedTask, ResolvedWarehouse, Result, SecretStore, State,
26+
TableNamed, TabularId, TabularListFlags, Transaction, ViewNamed, ViewOrTableInfo,
2727
},
2828
WarehouseId,
2929
};
@@ -350,11 +350,15 @@ pub(crate) trait Service<C: CatalogStore, A: Authorizer, S: SecretStore> {
350350
// -------------------- AUTHZ --------------------
351351
let authorizer = context.v1_state.authz;
352352

353+
let warehouse =
354+
C::get_active_warehouse_by_id(warehouse_id, context.v1_state.catalog.clone()).await;
355+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
356+
353357
authorize_list_tasks::<A, C>(
354358
&authorizer,
355359
context.v1_state.catalog.clone(),
356360
&request_metadata,
357-
warehouse_id,
361+
&warehouse,
358362
query.entities.as_ref(),
359363
)
360364
.await?;
@@ -377,12 +381,16 @@ pub(crate) trait Service<C: CatalogStore, A: Authorizer, S: SecretStore> {
377381
// -------------------- AUTHZ --------------------
378382
let authorizer = context.v1_state.authz;
379383

384+
let warehouse =
385+
C::get_active_warehouse_by_id(warehouse_id, context.v1_state.catalog.clone()).await;
386+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
387+
380388
let [authz_can_use, authz_get_all_warehouse] = authorizer
381389
.are_allowed_warehouse_actions_arr(
382390
&request_metadata,
383391
&[
384-
(warehouse_id, CatalogWarehouseAction::CanUse),
385-
(warehouse_id, CAN_GET_ALL_TASKS_DETAILS_WAREHOUSE_PERMISSION),
392+
(&warehouse, CatalogWarehouseAction::CanUse),
393+
(&warehouse, CAN_GET_ALL_TASKS_DETAILS_WAREHOUSE_PERMISSION),
386394
],
387395
)
388396
.await?
@@ -437,12 +445,16 @@ pub(crate) trait Service<C: CatalogStore, A: Authorizer, S: SecretStore> {
437445
// -------------------- AUTHZ --------------------
438446
let authorizer = context.v1_state.authz;
439447

448+
let warehouse =
449+
C::get_active_warehouse_by_id(warehouse_id, context.v1_state.catalog.clone()).await;
450+
let warehouse = authorizer.require_warehouse_presence(warehouse_id, warehouse)?;
451+
440452
let [authz_can_use, authz_control_all] = authorizer
441453
.are_allowed_warehouse_actions_arr(
442454
&request_metadata,
443455
&[
444-
(warehouse_id, CatalogWarehouseAction::CanUse),
445-
(warehouse_id, CONTROL_TASK_WAREHOUSE_PERMISSION),
456+
(&warehouse, CatalogWarehouseAction::CanUse),
457+
(&warehouse, CONTROL_TASK_WAREHOUSE_PERMISSION),
446458
],
447459
)
448460
.await?
@@ -518,15 +530,17 @@ async fn authorize_list_tasks<A: Authorizer, C: CatalogStore>(
518530
authorizer: &A,
519531
catalog_state: C::State,
520532
request_metadata: &RequestMetadata,
521-
warehouse_id: WarehouseId,
533+
warehouse: &ResolvedWarehouse,
522534
entities: Option<&Vec<TaskEntity>>,
523535
) -> Result<()> {
536+
let warehouse_id = warehouse.warehouse_id;
537+
524538
let [can_use, can_list_everything] = authorizer
525539
.are_allowed_warehouse_actions_arr(
526540
request_metadata,
527541
&[
528-
(warehouse_id, CatalogWarehouseAction::CanUse),
529-
(warehouse_id, CatalogWarehouseAction::CanListEverything),
542+
(warehouse, CatalogWarehouseAction::CanUse),
543+
(warehouse, CatalogWarehouseAction::CanListEverything),
530544
],
531545
)
532546
.await?

0 commit comments

Comments
 (0)