snap_control/server/
identity_registry.rs1use std::{
17 collections::{BTreeMap, HashSet},
18 sync::Arc,
19 time::{Duration, Instant},
20};
21
22use snap_tun::server::SnapTunAuthorization;
23
24use crate::crpc_api::api_service::model::SnapTunIdentityRegistry;
25
26type Identity = [u8; 32];
27
28#[derive(Default, Clone)]
29struct IdentityRegistryState {
30 pub associations: BTreeMap<Arc<str>, Arc<Identity>>,
34 pub expiry: BTreeMap<Arc<Identity>, Instant>,
35}
36
37impl IdentityRegistryState {
38 pub(crate) fn is_authorized(&self, now: Instant, ident: &Identity) -> bool {
39 self.expiry
40 .get(ident)
41 .map(|expiry| *expiry > now)
42 .unwrap_or(false)
43 }
44
45 pub(crate) fn add_identity<S: AsRef<str>>(
47 &mut self,
48 key: S,
49 identity: Identity,
50 expiry: Instant,
51 ) -> bool {
52 let key = Arc::<str>::from(key.as_ref().to_string());
53 let ident = Arc::new(identity);
54 if let Some(prev_ident) = self.associations.insert(key.clone(), ident.clone())
55 && prev_ident != ident
56 {
57 self.expiry.remove(&prev_ident);
58 }
59 self.expiry.insert(ident, expiry).is_none()
60 }
61
62 pub(crate) fn clean_expired(&mut self, now: Instant) {
64 let mut removed: HashSet<Arc<Identity>> = Default::default();
65 self.expiry.retain(|ident, expiry| {
66 if *expiry <= now {
67 removed.insert(ident.clone());
68 return false;
69 }
70 true
71 });
72 self.associations
73 .retain(|_, ident| !removed.contains(ident));
74 }
75}
76
77pub struct IdentityRegistry {
79 state: arc_swap::ArcSwap<IdentityRegistryState>,
86}
87
88impl IdentityRegistry {
89 #[allow(clippy::new_without_default)]
91 pub fn new() -> Self {
92 Self {
93 state: Default::default(),
94 }
95 }
96
97 pub fn is_authorized(&self, now: Instant, identity: &Identity) -> bool {
103 self.state.load().is_authorized(now, identity)
104 }
105
106 pub fn register<S: AsRef<str>>(
114 &self,
115 now: Instant,
116 key: S,
117 ident: Identity,
118 lifetime: Duration,
119 ) -> bool {
120 let mut res = false;
121 self.update_state(|state| {
122 res = state.add_identity(key, ident, now + lifetime);
123 });
124 res
125 }
126
127 pub fn remove_expired(&self, now: Instant) {
129 self.update_state(|state| state.clean_expired(now));
130 }
131
132 fn update_state<F>(&self, modifier: F)
133 where
134 F: FnOnce(&mut IdentityRegistryState),
135 {
136 let mut state: IdentityRegistryState = (**self.state.load()).clone();
139 (modifier)(&mut state);
140 self.state.store(Arc::new(state))
141 }
142
143 #[cfg(test)]
144 pub(crate) fn ident_exist(&self, ident: &Identity) -> bool {
145 self.state
146 .load()
147 .associations
148 .values()
149 .any(|v| v.as_ref() == ident)
150 || self.state.load().expiry.keys().any(|k| k.as_ref() == ident)
151 }
152}
153
154impl SnapTunIdentityRegistry for IdentityRegistry {
155 fn register(
156 &self,
157 now: Instant,
158 key: &str,
159 identity: Identity,
160 _psk_share: Option<[u8; 32]>,
161 lifetime: Duration,
162 ) -> bool {
163 self.register(now, key, identity, lifetime)
164 }
165}
166
167impl SnapTunAuthorization for IdentityRegistry {
168 fn is_authorized(&self, now: Instant, identity: &Identity) -> bool {
169 self.is_authorized(now, identity)
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use x25519_dalek::PublicKey;
176
177 use super::*;
178
179 fn create_test_identity(seed: u8) -> PublicKey {
180 let mut bytes = [0u8; 32];
181 bytes[0] = seed;
182 PublicKey::from(bytes)
183 }
184
185 #[test]
186 fn test_identity_not_registered() {
187 let registry = IdentityRegistry::new();
188 let now = Instant::now();
189 let identity = create_test_identity(1);
190
191 assert!(!registry.is_authorized(now, identity.as_bytes()));
192 }
193
194 #[test]
195 fn test_identity_is_authorized_before_expires() {
196 let registry = IdentityRegistry::new();
197 let now = Instant::now();
198 let identity = create_test_identity(1);
199
200 registry.register(now, "", *identity.as_bytes(), Duration::from_secs(30));
201
202 assert!(registry.is_authorized(now, identity.as_bytes()));
203 }
204
205 #[test]
206 fn test_reregistering_identity_returns_false_but_succeeds() {
207 let registry = IdentityRegistry::new();
208 let now = Instant::now();
209 let identity = create_test_identity(1);
210 let delta_t = Duration::from_secs(10);
211
212 registry.register(now, "", *identity.as_bytes(), delta_t);
213 assert!(!registry.is_authorized(now + delta_t, identity.as_bytes()));
214 assert!(!registry.register(now, "", *identity.as_bytes(), 2 * delta_t));
215 assert!(registry.is_authorized(now + delta_t, identity.as_bytes()));
216 }
217
218 #[test]
219 fn test_identity_is_unauthorized_at_expiry() {
220 let registry = IdentityRegistry::new();
221 let now = Instant::now();
222 let identity = create_test_identity(1);
223 let delta_t = Duration::from_secs(30);
224
225 registry.register(now, "", *identity.as_bytes(), delta_t);
226
227 assert!(!registry.is_authorized(now + delta_t, identity.as_bytes()));
228 }
229
230 #[test]
231 fn test_identity_is_removed_after_expiry() {
232 let registry = IdentityRegistry::new();
233 let now = Instant::now();
234 let identity = create_test_identity(1);
235 let delta_t = Duration::from_secs(30);
236
237 registry.register(now, "", *identity.as_bytes(), delta_t);
238 assert!(registry.ident_exist(identity.as_bytes()));
239 registry.remove_expired(now + delta_t);
240 assert!(!registry.ident_exist(identity.as_bytes()));
241 }
242}