edge_schema/schema/
tokens.rs

1use std::collections::HashSet;
2
3use serde::{Deserialize, Serialize};
4use time::OffsetDateTime;
5
6/// Node API permission scopes.
7#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum NodeApiPermission {
9    /// Superuser permission granting all other permissions.
10    #[serde(rename = "all")]
11    All,
12
13    /// Check node health.
14    #[serde(rename = "node.health_check")]
15    NodeHealthCheck,
16
17    /// Read information about workloads.
18    #[serde(rename = "workload.read")]
19    WorkloadRead,
20
21    /// Modify workloads. (EG: terminate)
22    #[serde(rename = "workload.write")]
23    WorkloadWrite,
24
25    // Confdb
26    #[serde(rename = "confdb.entity.read")]
27    ConfDbEntityRead,
28    #[serde(rename = "confdb.entity.delete")]
29    ConfDbEntityDelete,
30
31    // TLS
32    #[serde(rename = "tls_cert.read")]
33    TlsCertRead,
34    #[serde(rename = "tls_cert.delete")]
35    TlsCertDelete,
36
37    #[serde(rename = "dns_cache.read")]
38    DnsCacheRead,
39    #[serde(rename = "dns_cache.write")]
40    DnsCacheWrite,
41
42    #[serde(rename = "platform_config.read")]
43    PlatformConfigRead,
44
45    /// Generate new API tokens.
46    #[serde(rename = "api_token.generate")]
47    ApiTokenGenerate,
48
49    // (Cron)job
50    #[serde(rename = "job.read")]
51    JobRead,
52
53    #[serde(rename = "cluster_health.read")]
54    ClusterHealthRead,
55
56    #[serde(rename = "module_cache.read")]
57    ModuleCacheRead,
58    #[serde(rename = "module_cache.write")]
59    ModuleCacheWrite,
60}
61
62impl NodeApiPermission {
63    fn as_str(self) -> &'static str {
64        match self {
65            Self::All => "all",
66            Self::NodeHealthCheck => "node.health_check",
67            Self::WorkloadRead => "workload.read",
68            Self::WorkloadWrite => "workload.write",
69            Self::ConfDbEntityRead => "confdb.entity.read",
70            Self::ConfDbEntityDelete => "confdb.entity.delete",
71            Self::TlsCertRead => "tls_cert.read",
72            Self::TlsCertDelete => "tls_cert.delete",
73            Self::DnsCacheRead => "dns_cache.read",
74            Self::DnsCacheWrite => "dns_cache.write",
75            Self::PlatformConfigRead => "platform_config.read",
76            Self::ApiTokenGenerate => "api_token.generate",
77            Self::JobRead => "job.read",
78            Self::ClusterHealthRead => "cluster_health.read",
79            Self::ModuleCacheRead => "module_cache.read",
80            Self::ModuleCacheWrite => "module_cache.write",
81        }
82    }
83}
84
85impl std::str::FromStr for NodeApiPermission {
86    type Err = anyhow::Error;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        match s {
90            "all" => Ok(NodeApiPermission::All),
91            "node.health_check" => Ok(NodeApiPermission::NodeHealthCheck),
92            "workload.read" => Ok(NodeApiPermission::WorkloadRead),
93            "workload.write" => Ok(NodeApiPermission::WorkloadWrite),
94            "confdb.entity.read" => Ok(NodeApiPermission::ConfDbEntityRead),
95            "confdb.entity.delete" => Ok(NodeApiPermission::ConfDbEntityDelete),
96            "tls_cert.read" => Ok(NodeApiPermission::TlsCertRead),
97            "tls_cert.delete" => Ok(NodeApiPermission::TlsCertDelete),
98            "dns_cache.read" => Ok(NodeApiPermission::DnsCacheRead),
99            "dns_cache.write" => Ok(NodeApiPermission::DnsCacheWrite),
100            "platform_config.read" => Ok(NodeApiPermission::PlatformConfigRead),
101            "api_token.generate" => Ok(NodeApiPermission::ApiTokenGenerate),
102            "job.read" => Ok(NodeApiPermission::JobRead),
103            "cluster_health.read" => Ok(NodeApiPermission::ClusterHealthRead),
104            "module_cache.read" => Ok(NodeApiPermission::ModuleCacheRead),
105            "module_cache.write" => Ok(NodeApiPermission::ModuleCacheWrite),
106            _ => Err(anyhow::anyhow!("invalid node api permission: {}", s)),
107        }
108    }
109}
110
111impl std::fmt::Display for NodeApiPermission {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        f.write_str(self.as_str())
114    }
115}
116
117/// Basic JWT claims.
118///
119/// Only default fields.
120#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
121pub struct BaseClaims {
122    /// Expiration time.
123    #[serde(with = "time::serde::timestamp")]
124    pub exp: OffsetDateTime,
125
126    /// Issued at.
127    #[serde(with = "time::serde::timestamp")]
128    pub iat: OffsetDateTime,
129
130    /// Subject (aka user id)
131    pub sub: String,
132
133    /// Permissions for the node API.
134    ///
135    /// NOTE: for legacy reasons, `None` implies [`NodeApiPermission::All`],
136    /// meaning a superuser token which grants all other permissions.
137    /// This is needed because this field was added after the initial implementation.
138    /// This will be a required field in the future.
139    pub node_api_permissions: Option<HashSet<NodeApiPermission>>,
140}
141
142impl BaseClaims {
143    pub fn has_node_api_permission(&self, perm: NodeApiPermission) -> bool {
144        match &self.node_api_permissions {
145            Some(perms) => perms.contains(&perm) || perms.contains(&NodeApiPermission::All),
146            None => true,
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_base_claims_has_permission() {
157        // Specific set of permissions.
158        let c = BaseClaims {
159            exp: OffsetDateTime::now_utc(),
160            iat: OffsetDateTime::now_utc(),
161            sub: "test".to_string(),
162            node_api_permissions: Some(
163                vec![
164                    NodeApiPermission::WorkloadRead,
165                    NodeApiPermission::ConfDbEntityRead,
166                ]
167                .into_iter()
168                .collect(),
169            ),
170        };
171
172        assert!(c.has_node_api_permission(NodeApiPermission::WorkloadRead));
173        assert!(c.has_node_api_permission(NodeApiPermission::ConfDbEntityRead));
174        assert!(!c.has_node_api_permission(NodeApiPermission::WorkloadWrite));
175        assert!(!c.has_node_api_permission(NodeApiPermission::All));
176
177        // All permissions.
178        let c = BaseClaims {
179            node_api_permissions: Some(vec![NodeApiPermission::All].into_iter().collect()),
180            ..c
181        };
182        assert!(c.has_node_api_permission(NodeApiPermission::WorkloadRead));
183        assert!(c.has_node_api_permission(NodeApiPermission::ConfDbEntityRead));
184        assert!(c.has_node_api_permission(NodeApiPermission::All));
185
186        // Legacy all permissions (`None`).
187        let c = BaseClaims {
188            node_api_permissions: None,
189            ..c
190        };
191        assert!(c.has_node_api_permission(NodeApiPermission::WorkloadRead));
192        assert!(c.has_node_api_permission(NodeApiPermission::ConfDbEntityRead));
193        assert!(c.has_node_api_permission(NodeApiPermission::All));
194    }
195}