Skip to main content

auth/
permission.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Debug;
16use std::sync::Arc;
17
18use api::v1::RowInsertRequests;
19use api::v1::greptime_request::Request;
20use api::v1::query_request::Query;
21use common_telemetry::debug;
22use sql::statements::statement::Statement;
23
24use crate::error::{PermissionDeniedSnafu, Result};
25use crate::user_info::DefaultUserInfo;
26use crate::{PermissionCheckerRef, UserInfo, UserInfoRef};
27
28/// A user-visible table target for permission checks.
29///
30/// Use [`PermissionTableTargets::resolved`] to validate that all components are
31/// non-empty before passing targets to a permission checker.
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub struct PermissionTableTarget {
34    pub catalog: String,
35    pub schema: String,
36    pub table: String,
37}
38
39impl PermissionTableTarget {
40    /// Creates a table target candidate.
41    pub fn new(
42        catalog: impl Into<String>,
43        schema: impl Into<String>,
44        table: impl Into<String>,
45    ) -> Self {
46        Self {
47            catalog: catalog.into(),
48            schema: schema.into(),
49            table: table.into(),
50        }
51    }
52}
53
54/// The result of resolving every table target in a permission request.
55///
56/// `Resolved(vec![])` means the request contains no table targets. The operation
57/// privilege must still be checked, but there are no table ACLs to evaluate.
58/// [`PermissionTableTargets::Unresolved`] means the targets could not be
59/// determined safely.
60#[derive(Debug, Clone, PartialEq, Eq)]
61pub enum PermissionTableTargets {
62    /// Every target was resolved, including the valid empty-target case.
63    Resolved(Vec<PermissionTableTarget>),
64    /// One or more targets could not be determined safely.
65    Unresolved,
66}
67
68impl PermissionTableTargets {
69    /// Marks targets as resolved only when every component is non-empty.
70    pub fn resolved(targets: Vec<PermissionTableTarget>) -> Self {
71        if targets.iter().any(|target| {
72            target.catalog.is_empty() || target.schema.is_empty() || target.table.is_empty()
73        }) {
74            Self::Unresolved
75        } else {
76            Self::Resolved(targets)
77        }
78    }
79
80    /// Resolves logical table targets from normalized row insert requests.
81    pub fn from_row_insert_requests(
82        catalog: &str,
83        schema: &str,
84        requests: &RowInsertRequests,
85    ) -> Self {
86        Self::resolved(
87            requests
88                .inserts
89                .iter()
90                .map(|request| PermissionTableTarget::new(catalog, schema, &request.table_name))
91                .collect(),
92        )
93    }
94}
95
96/// The access mode of a named permission action.
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum AccessMode {
99    Read,
100    Write,
101}
102
103/// A named permission action.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct PermissionAction {
106    name: &'static str,
107    access_mode: AccessMode,
108}
109
110impl PermissionAction {
111    /// Creates a read action with a stable name.
112    pub const fn read(name: &'static str) -> Self {
113        Self {
114            name,
115            access_mode: AccessMode::Read,
116        }
117    }
118
119    /// Creates a write action with a stable name.
120    pub const fn write(name: &'static str) -> Self {
121        Self {
122            name,
123            access_mode: AccessMode::Write,
124        }
125    }
126
127    /// Returns the stable action name.
128    pub const fn name(self) -> &'static str {
129        self.name
130    }
131
132    /// Returns the action's access mode.
133    pub const fn access_mode(self) -> AccessMode {
134        self.access_mode
135    }
136}
137
138pub const PROMQL_QUERY: PermissionAction = PermissionAction::read("promql.query");
139pub const LOG_QUERY: PermissionAction = PermissionAction::read("log.query");
140pub const OPENTSDB_WRITE: PermissionAction = PermissionAction::write("opentsdb.write");
141pub const INFLUXDB_WRITE: PermissionAction = PermissionAction::write("influxdb.write");
142pub const PROM_STORE_WRITE: PermissionAction = PermissionAction::write("prom_store.write");
143pub const PROM_STORE_READ: PermissionAction = PermissionAction::read("prom_store.read");
144pub const OTLP_WRITE: PermissionAction = PermissionAction::write("otlp.write");
145pub const LOG_WRITE: PermissionAction = PermissionAction::write("log.write");
146pub const JAEGER_QUERY: PermissionAction = PermissionAction::read("jaeger.query");
147pub const PIPELINE_QUERY: PermissionAction = PermissionAction::read("pipeline.query");
148pub const PIPELINE_INSERT: PermissionAction = PermissionAction::write("pipeline.insert");
149pub const PIPELINE_DELETE: PermissionAction = PermissionAction::write("pipeline.delete");
150pub const DASHBOARD_QUERY: PermissionAction = PermissionAction::read("dashboard.query");
151pub const DASHBOARD_SAVE: PermissionAction = PermissionAction::write("dashboard.save");
152pub const DASHBOARD_DELETE: PermissionAction = PermissionAction::write("dashboard.delete");
153/// Reading a telemetry table through the entity-graph derivation
154/// (`greptime_private.semantic_entities` / `semantic_relationships`); checked
155/// once per candidate source table with that table as the target.
156pub const SEMANTIC_GRAPH_QUERY: PermissionAction = PermissionAction::read("semantic_graph.query");
157
158/// All permission actions built into GreptimeDB.
159///
160/// Downstream crates may define additional actions with [`PermissionAction::read`] and
161/// [`PermissionAction::write`].
162pub const ALL_ACTIONS: &[PermissionAction] = &[
163    PROMQL_QUERY,
164    LOG_QUERY,
165    OPENTSDB_WRITE,
166    INFLUXDB_WRITE,
167    PROM_STORE_WRITE,
168    PROM_STORE_READ,
169    OTLP_WRITE,
170    LOG_WRITE,
171    JAEGER_QUERY,
172    PIPELINE_QUERY,
173    PIPELINE_INSERT,
174    PIPELINE_DELETE,
175    DASHBOARD_QUERY,
176    DASHBOARD_SAVE,
177    DASHBOARD_DELETE,
178    SEMANTIC_GRAPH_QUERY,
179];
180
181#[derive(Debug, Clone)]
182pub enum PermissionReq<'a> {
183    GrpcRequest(&'a Request),
184    SqlStatement(&'a Statement),
185    Action(PermissionAction),
186    BulkInsert {
187        catalog: &'a str,
188        schema: &'a str,
189        table: &'a str,
190    },
191}
192
193impl<'a> PermissionReq<'a> {
194    /// Returns true if the permission request is for read operations.
195    pub fn is_readonly(&self) -> bool {
196        match self {
197            PermissionReq::GrpcRequest(Request::Query(query_request)) => {
198                !matches!(query_request.query, Some(Query::InsertIntoPlan(_)))
199            }
200            PermissionReq::SqlStatement(stmt) => stmt.is_readonly(),
201            PermissionReq::Action(action) => action.access_mode() == AccessMode::Read,
202
203            PermissionReq::GrpcRequest(_) | PermissionReq::BulkInsert { .. } => false,
204        }
205    }
206
207    /// Returns true if the permission request is for write operations.
208    pub fn is_write(&self) -> bool {
209        !self.is_readonly()
210    }
211}
212
213#[derive(Debug)]
214pub enum PermissionResp {
215    Allow,
216    Reject,
217}
218
219pub trait PermissionChecker: Send + Sync {
220    fn check_permission(
221        &self,
222        user_info: UserInfoRef,
223        req: PermissionReq,
224    ) -> Result<PermissionResp>;
225
226    fn check_permission_with_context(
227        &self,
228        user_info: UserInfoRef,
229        req: PermissionReq,
230        _current_schema: Option<&str>,
231    ) -> Result<PermissionResp> {
232        self.check_permission(user_info, req)
233    }
234
235    /// Returns whether authorization depends on resolved table targets.
236    ///
237    /// The conservative default keeps target resolution enabled. Implementations
238    /// may return `false` only when their authorization result is independent of
239    /// the `targets` passed to [`Self::check_permission_with_table_targets`].
240    fn uses_table_targets(&self) -> bool {
241        true
242    }
243
244    /// Checks the operation privilege and its resolved user-visible table targets.
245    ///
246    /// [`PermissionTableTargets::Resolved`] with an empty vector still requires
247    /// checking the operation privilege in `req`, but has no table ACLs to check.
248    ///
249    /// ACL-aware implementations must apply any admin bypass first, then reject
250    /// [`PermissionTableTargets::Unresolved`] for non-admin users.
251    fn check_permission_with_table_targets(
252        &self,
253        user_info: UserInfoRef,
254        req: PermissionReq,
255        targets: PermissionTableTargets,
256    ) -> Result<PermissionResp>;
257}
258
259fn check_permission_result(result: Result<PermissionResp>) -> Result<PermissionResp> {
260    match result {
261        Ok(PermissionResp::Reject) => PermissionDeniedSnafu.fail(),
262        Ok(PermissionResp::Allow) => Ok(PermissionResp::Allow),
263        Err(e) => Err(e),
264    }
265}
266
267impl PermissionChecker for Option<&PermissionCheckerRef> {
268    fn check_permission(
269        &self,
270        user_info: UserInfoRef,
271        req: PermissionReq,
272    ) -> Result<PermissionResp> {
273        self.check_permission_with_context(user_info, req, None)
274    }
275
276    fn check_permission_with_context(
277        &self,
278        user_info: UserInfoRef,
279        req: PermissionReq,
280        current_schema: Option<&str>,
281    ) -> Result<PermissionResp> {
282        match self {
283            Some(checker) => check_permission_result(checker.check_permission_with_context(
284                user_info,
285                req,
286                current_schema,
287            )),
288            None => Ok(PermissionResp::Allow),
289        }
290    }
291
292    fn uses_table_targets(&self) -> bool {
293        match self {
294            Some(checker) => checker.uses_table_targets(),
295            None => false,
296        }
297    }
298
299    fn check_permission_with_table_targets(
300        &self,
301        user_info: UserInfoRef,
302        req: PermissionReq,
303        targets: PermissionTableTargets,
304    ) -> Result<PermissionResp> {
305        match self {
306            Some(checker) => check_permission_result(
307                checker.check_permission_with_table_targets(user_info, req, targets),
308            ),
309            None => Ok(PermissionResp::Allow),
310        }
311    }
312}
313
314/// The default permission checker implementation.
315/// It checks the permission mode of [DefaultUserInfo].
316pub struct DefaultPermissionChecker;
317
318impl DefaultPermissionChecker {
319    /// Returns a new [PermissionCheckerRef] instance.
320    pub fn arc() -> PermissionCheckerRef {
321        Arc::new(DefaultPermissionChecker)
322    }
323}
324
325impl PermissionChecker for DefaultPermissionChecker {
326    fn check_permission(
327        &self,
328        user_info: UserInfoRef,
329        req: PermissionReq,
330    ) -> Result<PermissionResp> {
331        if let Some(default_user) = user_info.as_any().downcast_ref::<DefaultUserInfo>() {
332            let permission_mode = default_user.permission_mode();
333
334            if req.is_readonly() && !permission_mode.can_read() {
335                debug!(
336                    "Permission denied: read operation not allowed, user = {}, permission = {}",
337                    default_user.username(),
338                    permission_mode.as_str()
339                );
340                return Ok(PermissionResp::Reject);
341            }
342
343            if req.is_write() && !permission_mode.can_write() {
344                debug!(
345                    "Permission denied: write operation not allowed, user = {}, permission = {}",
346                    default_user.username(),
347                    permission_mode.as_str()
348                );
349                return Ok(PermissionResp::Reject);
350            }
351        }
352
353        // default allow all
354        Ok(PermissionResp::Allow)
355    }
356
357    fn uses_table_targets(&self) -> bool {
358        false
359    }
360
361    fn check_permission_with_table_targets(
362        &self,
363        user_info: UserInfoRef,
364        req: PermissionReq,
365        _targets: PermissionTableTargets,
366    ) -> Result<PermissionResp> {
367        self.check_permission(user_info, req)
368    }
369}
370#[cfg(test)]
371mod tests {
372    use std::collections::HashSet;
373
374    use super::*;
375    use crate::error::{Error, InternalStateSnafu};
376    use crate::user_info::PermissionMode;
377
378    struct TargetAwarePermissionChecker;
379
380    impl PermissionChecker for TargetAwarePermissionChecker {
381        fn check_permission(
382            &self,
383            _user_info: UserInfoRef,
384            _req: PermissionReq,
385        ) -> Result<PermissionResp> {
386            Ok(PermissionResp::Reject)
387        }
388
389        fn check_permission_with_table_targets(
390            &self,
391            _user_info: UserInfoRef,
392            req: PermissionReq,
393            targets: PermissionTableTargets,
394        ) -> Result<PermissionResp> {
395            if !matches!(req, PermissionReq::Action(PROM_STORE_READ)) {
396                return Ok(PermissionResp::Reject);
397            }
398            let PermissionTableTargets::Resolved(targets) = targets else {
399                return Ok(PermissionResp::Reject);
400            };
401            if targets.iter().any(|target| target.table == "error") {
402                return InternalStateSnafu {
403                    msg: "testing".to_string(),
404                }
405                .fail();
406            }
407            Ok(if targets.iter().all(|target| target.table == "allowed") {
408                PermissionResp::Allow
409            } else {
410                PermissionResp::Reject
411            })
412        }
413    }
414
415    fn resolved_targets(table: &str) -> PermissionTableTargets {
416        PermissionTableTargets::resolved(vec![PermissionTableTarget::new(
417            "greptime", "public", table,
418        )])
419    }
420
421    #[test]
422    fn test_resolve_permission_table_targets() {
423        assert_eq!(
424            PermissionTableTargets::Resolved(Vec::new()),
425            PermissionTableTargets::resolved(Vec::new())
426        );
427        assert_eq!(
428            resolved_targets("metrics"),
429            PermissionTableTargets::Resolved(vec![PermissionTableTarget::new(
430                "greptime", "public", "metrics"
431            )])
432        );
433
434        for target in [
435            PermissionTableTarget::new("", "public", "metrics"),
436            PermissionTableTarget::new("greptime", "", "metrics"),
437            PermissionTableTarget::new("greptime", "public", ""),
438        ] {
439            assert_eq!(
440                PermissionTableTargets::Unresolved,
441                PermissionTableTargets::resolved(vec![target])
442            );
443        }
444    }
445
446    #[test]
447    fn test_resolve_row_insert_request_targets() {
448        let requests = RowInsertRequests {
449            inserts: ["cpu", "mem"]
450                .into_iter()
451                .map(|table_name| api::v1::RowInsertRequest {
452                    table_name: table_name.to_string(),
453                    ..Default::default()
454                })
455                .collect(),
456        };
457
458        assert_eq!(
459            PermissionTableTargets::Resolved(vec![
460                PermissionTableTarget::new("greptime", "public", "cpu"),
461                PermissionTableTarget::new("greptime", "public", "mem"),
462            ]),
463            PermissionTableTargets::from_row_insert_requests("greptime", "public", &requests)
464        );
465        assert_eq!(
466            PermissionTableTargets::Unresolved,
467            PermissionTableTargets::from_row_insert_requests("", "public", &requests)
468        );
469        assert_eq!(
470            PermissionTableTargets::Unresolved,
471            PermissionTableTargets::from_row_insert_requests("greptime", "", &requests)
472        );
473
474        let unresolved = RowInsertRequests {
475            inserts: vec![api::v1::RowInsertRequest::default()],
476        };
477        assert_eq!(
478            PermissionTableTargets::Unresolved,
479            PermissionTableTargets::from_row_insert_requests("greptime", "public", &unresolved)
480        );
481
482        let empty = RowInsertRequests::default();
483        assert_eq!(
484            PermissionTableTargets::Resolved(Vec::new()),
485            PermissionTableTargets::from_row_insert_requests("greptime", "public", &empty)
486        );
487    }
488
489    #[test]
490    fn test_default_permission_checker_allow_all_operations() {
491        let checker = DefaultPermissionChecker;
492        let user_info =
493            DefaultUserInfo::with_name_and_permission("test_user", PermissionMode::ReadWrite);
494
495        let read_req = PermissionReq::Action(PROMQL_QUERY);
496        let write_req = PermissionReq::Action(PROM_STORE_WRITE);
497
498        let read_result = checker
499            .check_permission(user_info.clone(), read_req)
500            .unwrap();
501        let write_result = checker.check_permission(user_info, write_req).unwrap();
502
503        assert!(matches!(read_result, PermissionResp::Allow));
504        assert!(matches!(write_result, PermissionResp::Allow));
505    }
506
507    #[test]
508    fn test_default_permission_checker_readonly_user() {
509        let checker = DefaultPermissionChecker;
510        let user_info =
511            DefaultUserInfo::with_name_and_permission("readonly_user", PermissionMode::ReadOnly);
512
513        let read_req = PermissionReq::Action(PROMQL_QUERY);
514        let write_req = PermissionReq::Action(PROM_STORE_WRITE);
515
516        let read_result = checker
517            .check_permission(user_info.clone(), read_req)
518            .unwrap();
519        let write_result = checker.check_permission(user_info, write_req).unwrap();
520
521        assert!(matches!(read_result, PermissionResp::Allow));
522        assert!(matches!(write_result, PermissionResp::Reject));
523    }
524
525    #[test]
526    fn test_default_permission_checker_writeonly_user() {
527        let checker = DefaultPermissionChecker;
528        let user_info =
529            DefaultUserInfo::with_name_and_permission("writeonly_user", PermissionMode::WriteOnly);
530
531        let read_req = PermissionReq::Action(LOG_QUERY);
532        let write_req = PermissionReq::Action(LOG_WRITE);
533
534        let read_result = checker
535            .check_permission(user_info.clone(), read_req)
536            .unwrap();
537        let write_result = checker.check_permission(user_info, write_req).unwrap();
538
539        assert!(matches!(read_result, PermissionResp::Reject));
540        assert!(matches!(write_result, PermissionResp::Allow));
541    }
542
543    #[test]
544    fn test_grpc_insert_into_plan_is_write_request() {
545        let request = Request::Query(api::v1::QueryRequest {
546            query: Some(Query::InsertIntoPlan(api::v1::InsertIntoPlan::default())),
547        });
548        let req = PermissionReq::GrpcRequest(&request);
549
550        assert!(req.is_write());
551    }
552
553    #[test]
554    fn test_bulk_insert_is_write_request() {
555        let req = PermissionReq::BulkInsert {
556            catalog: "greptime",
557            schema: "public",
558            table: "metrics",
559        };
560
561        assert!(req.is_write());
562    }
563
564    #[test]
565    fn test_action_access_modes() {
566        let checker = DefaultPermissionChecker;
567        let mut names = HashSet::new();
568
569        for &action in ALL_ACTIONS {
570            assert!(
571                names.insert(action.name()),
572                "duplicate permission action: {}",
573                action.name()
574            );
575            let access_mode = action.access_mode();
576            let req = PermissionReq::Action(action);
577            assert_eq!(
578                access_mode == AccessMode::Read,
579                req.is_readonly(),
580                "{}",
581                action.name()
582            );
583
584            for (permission_mode, allowed) in [
585                (PermissionMode::ReadOnly, access_mode == AccessMode::Read),
586                (PermissionMode::WriteOnly, access_mode == AccessMode::Write),
587                (PermissionMode::ReadWrite, true),
588            ] {
589                let user = DefaultUserInfo::with_name_and_permission("test_user", permission_mode);
590                let result = checker.check_permission(user, req.clone()).unwrap();
591                assert_eq!(
592                    allowed,
593                    matches!(result, PermissionResp::Allow),
594                    "{permission_mode:?}: {}",
595                    action.name()
596                );
597            }
598        }
599    }
600
601    #[test]
602    fn test_table_target_permission_forwarding() {
603        let checker: PermissionCheckerRef = Arc::new(TargetAwarePermissionChecker);
604        let checker = Some(&checker);
605        assert!(checker.uses_table_targets());
606
607        let allowed = checker
608            .check_permission_with_table_targets(
609                crate::userinfo_by_name(None),
610                PermissionReq::Action(PROM_STORE_READ),
611                resolved_targets("allowed"),
612            )
613            .unwrap();
614        assert!(matches!(allowed, PermissionResp::Allow));
615
616        let empty = checker
617            .check_permission_with_table_targets(
618                crate::userinfo_by_name(None),
619                PermissionReq::Action(PROM_STORE_READ),
620                PermissionTableTargets::resolved(Vec::new()),
621            )
622            .unwrap();
623        assert!(matches!(empty, PermissionResp::Allow));
624
625        let rejected_operation = checker.check_permission_with_table_targets(
626            crate::userinfo_by_name(None),
627            PermissionReq::Action(PROM_STORE_WRITE),
628            PermissionTableTargets::resolved(Vec::new()),
629        );
630        assert!(matches!(
631            rejected_operation,
632            Err(Error::PermissionDenied { .. })
633        ));
634
635        let rejected = checker.check_permission_with_table_targets(
636            crate::userinfo_by_name(None),
637            PermissionReq::Action(PROM_STORE_READ),
638            resolved_targets("denied"),
639        );
640        assert!(matches!(rejected, Err(Error::PermissionDenied { .. })));
641
642        let mixed = checker.check_permission_with_table_targets(
643            crate::userinfo_by_name(None),
644            PermissionReq::Action(PROM_STORE_READ),
645            PermissionTableTargets::resolved(vec![
646                PermissionTableTarget::new("greptime", "public", "allowed"),
647                PermissionTableTarget::new("greptime", "public", "denied"),
648            ]),
649        );
650        assert!(matches!(mixed, Err(Error::PermissionDenied { .. })));
651
652        let error = checker.check_permission_with_table_targets(
653            crate::userinfo_by_name(None),
654            PermissionReq::Action(PROM_STORE_READ),
655            resolved_targets("error"),
656        );
657        assert!(matches!(error, Err(Error::InternalState { msg }) if msg == "testing"));
658
659        let no_checker: Option<&PermissionCheckerRef> = None;
660        assert!(!no_checker.uses_table_targets());
661        let allowed = no_checker
662            .check_permission_with_table_targets(
663                crate::userinfo_by_name(None),
664                PermissionReq::Action(PROM_STORE_READ),
665                PermissionTableTargets::Unresolved,
666            )
667            .unwrap();
668        assert!(matches!(allowed, PermissionResp::Allow));
669    }
670
671    #[test]
672    fn test_default_permission_checker_table_target_parity() {
673        let checker = DefaultPermissionChecker;
674        assert!(!checker.uses_table_targets());
675
676        for (permission, req) in [
677            (
678                PermissionMode::ReadOnly,
679                PermissionReq::Action(PROMQL_QUERY),
680            ),
681            (
682                PermissionMode::ReadOnly,
683                PermissionReq::Action(PROM_STORE_WRITE),
684            ),
685            (
686                PermissionMode::WriteOnly,
687                PermissionReq::Action(PROMQL_QUERY),
688            ),
689            (
690                PermissionMode::WriteOnly,
691                PermissionReq::Action(PROM_STORE_WRITE),
692            ),
693        ] {
694            let user = DefaultUserInfo::with_name_and_permission("test_user", permission);
695            let direct = checker.check_permission(user.clone(), req.clone()).unwrap();
696            let targeted = checker
697                .check_permission_with_table_targets(user, req, resolved_targets("metrics"))
698                .unwrap();
699
700            assert_eq!(
701                matches!(direct, PermissionResp::Allow),
702                matches!(targeted, PermissionResp::Allow)
703            );
704        }
705    }
706}