yo_resp/dispatch/acl.rs
1//! Users, the rules that describe what each of them may do, and the gate that
2//! holds every command to them.
3//!
4//! # What an ACL actually is
5//!
6//! A user is a name, a switch saying whether it is usable at all, a list of
7//! password hashes, and one or more selectors. A selector is the interesting
8//! part: a set of commands, a set of key patterns and a set of channel patterns.
9//! A command is allowed if any one selector allows the command and every key and
10//! channel it touches. Selectors are tried in order and the first that says yes
11//! wins, which is how a user can be given two unrelated jobs without either of
12//! them leaking into the other. Without them the only way to say "read anything
13//! under `cache:` and write anything under `job:`" is to allow reads and writes
14//! over both.
15//!
16//! Every user starts with one selector, called the root selector here and in
17//! Redis, which is the one the bare rules land in: `ACL SETUSER u +get ~k:*`
18//! writes into it, and `ACL SETUSER u (+get ~k:*)` adds another one beside it.
19//!
20//! # Why the rules are kept as text as well as bits
21//!
22//! Command permission is a bitmap, one bit a command, because the gate reads it
23//! on every command a guarded server runs and a set of names would mean a lookup
24//! there. But `ACL LIST` and `ACL GETUSER` have to hand back something that can
25//! be fed to `ACL SETUSER` and produce the same user, and a bitmap cannot say
26//! whether `+@read -get` or the eleven names that leaves was what the operator
27//! wrote. Worse, the two are not the same user: the first allows a command added
28//! to the `@read` category later and the second does not.
29//!
30//! So a selector keeps both. The bitmap is what the gate reads and the rule list
31//! is what describes it, and every rule that touches commands appends itself to
32//! the list after removing whatever it overrides. That is Redis's design and it
33//! is the reason `ACL GETUSER` on a user built with categories reads back in
34//! categories. The removal is the fiddly half and it is spelled out at
35//! [`Selector::note`].
36//!
37//! # The one thing here that is not Redis's
38//!
39//! Redis gives every subcommand of a container command its own identity and its
40//! own bit, so `+config|get` sets a bit and `+config|nope` is refused because
41//! there is no such command. There is no subcommand table here yet, which is
42//! D-114, so both go through the mechanism Redis keeps for the other case: a
43//! list of first arguments a command is allowed with. The gate then reaches the
44//! same answer for every rule an operator would actually write, and the
45//! difference is that `+config|nope` is accepted here and refused there. It is
46//! written down as D-137 rather than papered over, and it goes away with the
47//! subcommand table.
48//!
49//! # Where the gate sits
50//!
51//! Right after the password and the transaction refusal and right before the
52//! memory limit, which is where `processCommand` puts it. That ordering is
53//! visible: a command a user is not allowed to run is refused before the server
54//! decides whether it is out of memory, and a command with the wrong number of
55//! arguments is told that rather than told it is not allowed.
56
57use std::sync::Mutex;
58use std::sync::atomic::Ordering::{Acquire, Relaxed, Release};
59use std::sync::atomic::{AtomicBool, AtomicU64};
60
61use yo_common::{Code, Error, Result, glob_matches, sha256};
62
63use super::args::{self, Args, is};
64use super::keyspec::{self, Access};
65use super::table::{self, Spec};
66use super::{Server, Session};
67use crate::reply::Out;
68
69/// The user every connection is on until it says otherwise.
70pub(super) const DEFAULT: &[u8] = b"default";
71
72/// The categories `ACL CAT` lists, in the order it lists them.
73///
74/// The first twenty two are Redis's own list in Redis's own order, which is the
75/// order they are declared in `server.h` and not alphabetical. The nine after
76/// them are the module surfaces this engine has built in. A real server with
77/// those modules loaded reports them here too, so the shape is right even though
78/// a bare server answers twenty two.
79const CATEGORIES: [&str; 31] = [
80 "keyspace",
81 "read",
82 "write",
83 "set",
84 "sortedset",
85 "list",
86 "hash",
87 "string",
88 "array",
89 "bitmap",
90 "hyperloglog",
91 "geo",
92 "stream",
93 "pubsub",
94 "admin",
95 "fast",
96 "slow",
97 "blocking",
98 "dangerous",
99 "connection",
100 "transaction",
101 "scripting",
102 "bloom",
103 "cms",
104 "cuckoo",
105 "graph",
106 "json",
107 "search",
108 "tdigest",
109 "timeseries",
110 "topk",
111];
112
113/// How many words of bitmap it takes to give every command a bit.
114const WORDS: usize = table::count().div_ceil(64);
115
116/// The read bit on a key pattern, which is what `%R~` sets.
117const READ: u8 = 1;
118/// The write bit, which is what `%W~` sets.
119const WRITE: u8 = 2;
120
121/// A key pattern and what it may be used for.
122#[derive(Debug, Clone, PartialEq, Eq)]
123struct Pattern {
124 /// [`READ`], [`WRITE`] or both.
125 flags: u8,
126 /// The glob itself, without the sigil.
127 glob: Vec<u8>,
128}
129
130impl Pattern {
131 /// The pattern written the way `ACL LIST` writes it.
132 ///
133 /// A pattern good for both is written bare, because `%RW~x` and `~x` are the
134 /// same thing and the bare form is the one everybody types. A pattern good
135 /// for one keeps its sigil, because there is no other way to say it.
136 fn describe(&self, into: &mut Vec<u8>) {
137 match self.flags {
138 READ => into.extend_from_slice(b"%R~"),
139 WRITE => into.extend_from_slice(b"%W~"),
140 _ => into.push(b'~'),
141 }
142 into.extend_from_slice(&self.glob);
143 }
144}
145
146/// One set of commands, keys and channels, which is what a permission check runs
147/// against.
148#[derive(Debug, Clone)]
149struct Selector {
150 /// One bit a command, indexed by its row in the table.
151 allowed: [u64; WORDS],
152 /// Whether every command is allowed without looking, which `+@all` sets and
153 /// taking any command away clears.
154 all_commands: bool,
155 /// Whether a command that does not exist yet would be allowed.
156 ///
157 /// Redis keeps this as a reserved bit past the end of the bitmap, set by
158 /// `+@all` and cleared by `-@all` and by nothing else, so that `+@all -get`
159 /// and `-@all +everything-but-get` can be told apart. They describe back
160 /// differently and they behave differently the day a command is added.
161 future: bool,
162 /// Whether every key is allowed.
163 all_keys: bool,
164 /// Whether every channel is allowed.
165 all_channels: bool,
166 /// The command rules in the order they were given, space separated.
167 ///
168 /// Not the bitmap written out. See the module note for why the two are both
169 /// kept.
170 rules: Vec<u8>,
171 /// The first arguments a command is allowed with, when it is not allowed
172 /// outright. Sorted by command index.
173 firstargs: Vec<(u16, Vec<Vec<u8>>)>,
174 /// The first arguments a container command is refused with, when it is
175 /// allowed outright. Sorted by command index.
176 ///
177 /// This is how `+config -config|get` is held. A server with a row a
178 /// subcommand has does it by clearing that row's bit, and the day yo has one
179 /// too this goes away with the rest of D-114.
180 deniedfirst: Vec<(u16, Vec<Vec<u8>>)>,
181 /// The key patterns, in the order they were added.
182 patterns: Vec<Pattern>,
183 /// The channel patterns, in the order they were added.
184 channels: Vec<Vec<u8>>,
185}
186
187impl Selector {
188 /// A selector that allows nothing, which is where every user starts.
189 fn new() -> Selector {
190 Selector {
191 allowed: [0; WORDS],
192 all_commands: false,
193 future: false,
194 all_keys: false,
195 all_channels: false,
196 rules: Vec::new(),
197 firstargs: Vec::new(),
198 deniedfirst: Vec::new(),
199 patterns: Vec::new(),
200 channels: Vec::new(),
201 }
202 }
203
204 /// Whether the command at `i` is allowed outright.
205 fn bit(&self, i: usize) -> bool {
206 i < table::count() && self.allowed[i / 64] & (1 << (i % 64)) != 0
207 }
208
209 /// Allow or refuse the command at `i`, and forget any first arguments it had.
210 ///
211 /// Taking anything away clears `all_commands`, because that flag is only
212 /// there to let the gate skip the bitmap and it can no longer do that.
213 fn set(&mut self, i: usize, allow: bool) {
214 if i >= table::count() {
215 return;
216 }
217 if allow {
218 self.allowed[i / 64] |= 1 << (i % 64);
219 } else {
220 self.allowed[i / 64] &= !(1 << (i % 64));
221 self.all_commands = false;
222 }
223 self.firstargs.retain(|(at, _)| usize::from(*at) != i);
224 self.deniedfirst.retain(|(at, _)| usize::from(*at) != i);
225 }
226
227 /// Record a command rule, dropping whatever it overrides.
228 ///
229 /// The rule that goes on the end is the one that wins, so anything it makes
230 /// irrelevant has to come off first or the list would grow forever and
231 /// describe the user wrongly. Two things are irrelevant: the same rule
232 /// written before, and, when this rule names a whole command, any earlier
233 /// rule about one of its subcommands. `+get` after `-get|foo` leaves `+get`
234 /// alone, because the subcommand rule cannot survive its parent being
235 /// decided again.
236 ///
237 /// Note that a rule is matched on the name and not on the sign, so `+get`
238 /// removes an earlier `-get` rather than sitting after it.
239 fn note(&mut self, rule: &[u8], allow: bool) {
240 let mut kept: Vec<u8> = Vec::with_capacity(self.rules.len() + rule.len() + 2);
241 for old in self.rules.split(|b| *b == b' ') {
242 if old.is_empty() {
243 continue;
244 }
245 // The sign is not part of the name and is not compared.
246 let name = &old[1..];
247 let same = name == rule;
248 let child =
249 name.len() > rule.len() && name.starts_with(rule) && name[rule.len()] == b'|';
250 if same || child {
251 continue;
252 }
253 if !kept.is_empty() {
254 kept.push(b' ');
255 }
256 kept.extend_from_slice(old);
257 }
258 if !kept.is_empty() {
259 kept.push(b' ');
260 }
261 kept.push(if allow { b'+' } else { b'-' });
262 kept.extend_from_slice(rule);
263 self.rules = kept;
264 }
265
266 /// Allow this command only when its first argument is `first`.
267 fn allow_first(&mut self, i: u16, first: &[u8]) {
268 let lower = first.to_ascii_lowercase();
269 match self.firstargs.binary_search_by_key(&i, |(at, _)| *at) {
270 Ok(at) => {
271 let list = &mut self.firstargs[at].1;
272 if !list.contains(&lower) {
273 list.push(lower);
274 }
275 }
276 Err(at) => self.firstargs.insert(at, (i, vec![lower])),
277 }
278 }
279
280 /// The first arguments the command at `i` is allowed with.
281 fn firsts(&self, i: u16) -> &[Vec<u8>] {
282 match self.firstargs.binary_search_by_key(&i, |(at, _)| *at) {
283 Ok(at) => &self.firstargs[at].1,
284 Err(_) => &[],
285 }
286 }
287
288 /// Refuse this command when its first argument is `first`.
289 ///
290 /// This clears `all_commands` for the same reason [`Selector::set`] does:
291 /// the flag is only there so the gate can skip the bitmap, and once one
292 /// subcommand is spoken for it can no longer skip anything.
293 fn deny_first(&mut self, i: u16, first: &[u8]) {
294 let lower = first.to_ascii_lowercase();
295 self.all_commands = false;
296 match self.deniedfirst.binary_search_by_key(&i, |(at, _)| *at) {
297 Ok(at) => {
298 let list = &mut self.deniedfirst[at].1;
299 if !list.contains(&lower) {
300 list.push(lower);
301 }
302 }
303 Err(at) => self.deniedfirst.insert(at, (i, vec![lower])),
304 }
305 }
306
307 /// The first arguments the command at `i` is refused with.
308 fn denied(&self, i: u16) -> &[Vec<u8>] {
309 match self.deniedfirst.binary_search_by_key(&i, |(at, _)| *at) {
310 Ok(at) => &self.deniedfirst[at].1,
311 Err(_) => &[],
312 }
313 }
314
315 /// Everything a rule about commands could have set, back to nothing.
316 fn reset_commands(&mut self, all: bool) {
317 self.allowed = [if all { u64::MAX } else { 0 }; WORDS];
318 self.all_commands = all;
319 self.future = all;
320 self.rules.clear();
321 self.firstargs.clear();
322 self.deniedfirst.clear();
323 }
324
325 /// The command rules written the way `ACL SETUSER` would take them back.
326 ///
327 /// Always starts with `+@all` or `-@all` and then repeats the rule list,
328 /// which is exactly what was fed in after the last time it was cleared. That
329 /// is why this needs no cleverness: the list is already the answer.
330 fn describe_commands(&self) -> Vec<u8> {
331 let mut out = Vec::with_capacity(self.rules.len() + 8);
332 out.extend_from_slice(if self.future { b"+@all" } else { b"-@all" });
333 if !self.rules.is_empty() {
334 out.push(b' ');
335 out.extend_from_slice(&self.rules);
336 }
337 out
338 }
339
340 /// The key patterns, space separated, or `~*`.
341 fn describe_keys(&self) -> Vec<u8> {
342 let mut out = Vec::new();
343 if self.all_keys {
344 out.extend_from_slice(b"~*");
345 return out;
346 }
347 for pattern in &self.patterns {
348 if !out.is_empty() {
349 out.push(b' ');
350 }
351 pattern.describe(&mut out);
352 }
353 out
354 }
355
356 /// The channel patterns, space separated, or `&*`.
357 fn describe_channels(&self) -> Vec<u8> {
358 let mut out = Vec::new();
359 if self.all_channels {
360 out.extend_from_slice(b"&*");
361 return out;
362 }
363 for channel in &self.channels {
364 if !out.is_empty() {
365 out.push(b' ');
366 }
367 out.push(b'&');
368 out.extend_from_slice(channel);
369 }
370 out
371 }
372
373 /// The whole selector as `ACL LIST` writes it inside a user's line.
374 ///
375 /// Keys first, then channels, then commands. The `resetchannels` in front of
376 /// the channel list is not decoration: without it a line read back on a
377 /// server whose `acl-pubsub-default` is `allchannels` would start from every
378 /// channel allowed and the `&x` rules would add nothing.
379 fn describe(&self) -> Vec<u8> {
380 let mut out = self.describe_keys();
381 if !out.is_empty() {
382 out.push(b' ');
383 }
384 if self.all_channels {
385 out.extend_from_slice(b"&* ");
386 } else {
387 out.extend_from_slice(b"resetchannels ");
388 for channel in &self.channels {
389 out.push(b'&');
390 out.extend_from_slice(channel);
391 out.push(b' ');
392 }
393 }
394 out.extend_from_slice(&self.describe_commands());
395 out
396 }
397}
398
399/// What one of these is allowed to say went wrong with a rule.
400///
401/// The sentences are Redis's, word for word, because an operator reading one has
402/// almost certainly found it in Redis's documentation first.
403#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404enum Bad {
405 /// The rule is not one, or is malformed.
406 Syntax,
407 /// A command or category nobody has heard of.
408 Unknown,
409 /// A key pattern after `~*`.
410 AfterAllKeys,
411 /// A channel pattern after `&*`.
412 AfterAllChannels,
413 /// `<password` for a password the user has not got.
414 NoSuchPassword,
415 /// A `#` hash that is not sixty four hexadecimal characters.
416 Hash,
417 /// `+get|set|other`, which Redis has never supported.
418 NestedFirstArg,
419 /// A `(` with no `)`, which is reported differently from all the rest.
420 Unmatched,
421}
422
423impl Bad {
424 /// The sentence after `Error in ACL SETUSER modifier '<rule>': `.
425 fn text(self) -> &'static str {
426 match self {
427 Bad::Syntax | Bad::Unmatched => "Syntax error",
428 Bad::Unknown => "Unknown command or category name in ACL",
429 Bad::AfterAllKeys => {
430 "Adding a pattern after the * pattern (or the 'allkeys' flag) is not valid and does not have any effect. Try 'resetkeys' to start with an empty list of patterns"
431 }
432 Bad::AfterAllChannels => {
433 "Adding a pattern after the * pattern (or the 'allchannels' flag) is not valid and does not have any effect. Try 'resetchannels' to start with an empty list of channels"
434 }
435 Bad::NoSuchPassword => {
436 "The password you are trying to remove from the user does not exist"
437 }
438 Bad::Hash => {
439 "The password hash must be exactly 64 characters and contain only lowercase hexadecimal characters"
440 }
441 Bad::NestedFirstArg => "Allowing first-arg of a subcommand is not supported",
442 }
443 }
444}
445
446/// One account, and everything it is allowed to do.
447#[derive(Debug, Clone)]
448pub(crate) struct User {
449 /// The name, which is also the key it is filed under.
450 name: Vec<u8>,
451 /// Whether it may authenticate at all.
452 enabled: bool,
453 /// Whether any password gets in, which is what `nopass` means.
454 nopass: bool,
455 /// Whether `DEBUG` payload sanitising is skipped for this user.
456 ///
457 /// Nothing reads it here yet, and it is kept and reported because it is on
458 /// every `ACL LIST` line a real server writes and a config file round trip
459 /// that dropped it would quietly change the user.
460 skip_sanitize: bool,
461 /// The SHA-256 hashes of the passwords that get in, lower case hex.
462 passwords: Vec<[u8; 64]>,
463 /// The root selector first and any others after it.
464 selectors: Vec<Selector>,
465}
466
467impl User {
468 /// A new user, off, with no password and allowed nothing.
469 fn new(name: &[u8]) -> User {
470 User {
471 name: name.to_vec(),
472 enabled: false,
473 nopass: false,
474 skip_sanitize: false,
475 passwords: Vec::new(),
476 selectors: vec![Selector::new()],
477 }
478 }
479
480 /// The user a server with no `aclfile` starts with, which can do anything.
481 fn default_user() -> User {
482 let mut user = User::new(DEFAULT);
483 user.enabled = true;
484 user.nopass = true;
485 let root = &mut user.selectors[0];
486 root.reset_commands(true);
487 root.all_keys = true;
488 root.all_channels = true;
489 user
490 }
491
492 /// The flag words `ACL LIST` and `ACL GETUSER` print, in Redis's order.
493 fn flags(&self) -> Vec<&'static str> {
494 let mut out = Vec::with_capacity(3);
495 out.push(if self.enabled { "on" } else { "off" });
496 if self.nopass {
497 out.push("nopass");
498 }
499 // Exactly one of the two is always set, because a user is created with
500 // the sanitising one on and the rules only ever swap them.
501 out.push(if self.skip_sanitize {
502 "skip-sanitize-payload"
503 } else {
504 "sanitize-payload"
505 });
506 out
507 }
508
509 /// The whole user as one `ACL LIST` line, without the leading `user `.
510 fn describe(&self) -> Vec<u8> {
511 let mut out = Vec::with_capacity(64);
512 out.extend_from_slice(&self.name);
513 for flag in self.flags() {
514 out.push(b' ');
515 out.extend_from_slice(flag.as_bytes());
516 }
517 for hash in &self.passwords {
518 out.extend_from_slice(b" #");
519 out.extend_from_slice(hash);
520 }
521 for (at, selector) in self.selectors.iter().enumerate() {
522 out.push(b' ');
523 if at == 0 {
524 out.extend_from_slice(&selector.describe());
525 } else {
526 out.push(b'(');
527 out.extend_from_slice(&selector.describe());
528 out.push(b')');
529 }
530 }
531 out
532 }
533
534 /// Whether nothing this user could be asked to do would be refused.
535 ///
536 /// One selector that allows every command, every key and every channel, and
537 /// no other selectors, because a second one could only ever allow more and
538 /// the first already allows everything. That is the shape the default user
539 /// has on a server nobody has written an ACL for.
540 fn unrestricted(&self) -> bool {
541 self.selectors.len() == 1
542 && self.selectors[0].all_commands
543 && self.selectors[0].all_keys
544 && self.selectors[0].all_channels
545 }
546
547 /// Whether this password gets in.
548 ///
549 /// Every hash is compared whatever the first one said, so the number of
550 /// passwords a user has is not readable from how long a wrong guess took.
551 /// The hashing has already thrown away everything about the guess that a
552 /// timing difference could leak, so this is belt and braces, and it costs
553 /// one compare of thirty two bytes a password.
554 fn admits(&self, password: &[u8]) -> bool {
555 if !self.enabled {
556 return false;
557 }
558 if self.nopass {
559 return true;
560 }
561 let guess = sha256::hex(password);
562 let mut hit = false;
563 for hash in &self.passwords {
564 hit |= same(hash, &guess);
565 }
566 hit
567 }
568}
569
570/// Whether two byte strings are equal, in time that does not depend on where
571/// they stop being equal.
572fn same(a: &[u8], b: &[u8]) -> bool {
573 let mut diff = u8::from(a.len() != b.len());
574 for i in 0..a.len().max(b.len()) {
575 diff |= a.get(i).copied().unwrap_or(0) ^ b.get(i).copied().unwrap_or(0xff);
576 }
577 diff == 0
578}
579
580/// Every user on the server, and a counter saying when one last changed.
581///
582/// One lock over the lot, because every path that writes here is an operator
583/// typing a command and every path that reads is either the same or a
584/// connection authenticating. The command gate does not come through here at
585/// all: it holds a clone of the user it is running as, refreshed when the
586/// counter moves, which is what keeps a guarded server's hot path free of this
587/// lock. See [`Session::acl_user`].
588#[derive(Debug)]
589pub(crate) struct Users {
590 /// The users, sorted by name, which is the order `ACL LIST` reports.
591 ///
592 /// Redis keeps them in a radix tree and walks it in order, so the sorting is
593 /// not a nicety, it is the observable order of two commands.
594 table: Mutex<Vec<User>>,
595 /// Bumped whenever anything in the table changes.
596 generation: AtomicU64,
597 /// Whether a connection has to authenticate before it can do anything.
598 guarded: AtomicBool,
599 /// Whether any user here could be refused any command.
600 ///
601 /// The gate reads this and nothing else on a server nobody has written an
602 /// ACL for, which is every server that only ever set `requirepass` and every
603 /// server that did not even do that. Setting a password does not make it
604 /// true: the default user still has every permission, so no command can be
605 /// refused once a connection is past the password.
606 restricted: AtomicBool,
607}
608
609impl Default for Users {
610 fn default() -> Users {
611 Users {
612 table: Mutex::new(vec![User::default_user()]),
613 generation: AtomicU64::new(1),
614 guarded: AtomicBool::new(false),
615 restricted: AtomicBool::new(false),
616 }
617 }
618}
619
620impl Users {
621 /// Run `f` over the table, and say that it changed if `f` says so.
622 ///
623 /// The two summaries are recomputed here rather than at every call site,
624 /// which is the point of routing every write through one function: there is
625 /// exactly one place that can leave them disagreeing with the table.
626 fn with<T>(&self, f: impl FnOnce(&mut Vec<User>) -> (bool, T)) -> T {
627 let mut held = self.table.lock().unwrap_or_else(|e| e.into_inner());
628 let (changed, out) = f(&mut held);
629 if changed {
630 let guarded = held
631 .binary_search_by(|u| u.name.as_slice().cmp(DEFAULT))
632 .is_ok_and(|at| !held[at].nopass || !held[at].enabled);
633 self.guarded.store(guarded, Relaxed);
634 self.restricted
635 .store(held.iter().any(|u| !u.unrestricted()), Relaxed);
636 // Released after the change and acquired before a reader looks at
637 // its copy, so a session that sees a new number sees the table that
638 // goes with it.
639 self.generation.fetch_add(1, Release);
640 }
641 out
642 }
643
644 /// A copy of the user called `name`, if there is one.
645 fn get(&self, name: &[u8]) -> Option<User> {
646 self.with(|table| {
647 let found = table
648 .binary_search_by(|u| u.name.as_slice().cmp(name))
649 .ok()
650 .map(|at| table[at].clone());
651 (false, found)
652 })
653 }
654
655 /// The number the session's copy of a user is stamped with.
656 fn generation(&self) -> u64 {
657 self.generation.load(Acquire)
658 }
659}
660
661impl Server {
662 /// Whether this server asks connections for a password.
663 ///
664 /// True when the default user has one, which is exactly what `requirepass`
665 /// means: a server whose default user is `nopass` lets an unauthenticated
666 /// connection straight through as that user, and a server whose default user
667 /// has a password does not.
668 #[must_use]
669 pub(crate) fn guarded(&self) -> bool {
670 self.acl.guarded.load(Relaxed)
671 }
672
673 /// Whether the gate has to ask the ACL anything at all.
674 #[must_use]
675 pub(crate) fn restricted(&self) -> bool {
676 self.acl.restricted.load(Relaxed)
677 }
678
679 /// Set or clear the default user's password, where an empty one clears it.
680 ///
681 /// This is the whole of `CONFIG SET requirepass`, and on a real server it is
682 /// the whole of it too: the config option is a way of writing one rule on
683 /// one user. Setting it drops any password the default user already had,
684 /// which is what the reference does and is worth knowing, because it means
685 /// `requirepass` and `ACL SETUSER default >pw` fight rather than add up.
686 pub fn set_password(&self, password: &[u8]) {
687 self.acl.with(|table| {
688 let Ok(at) = table.binary_search_by(|u| u.name.as_slice().cmp(DEFAULT)) else {
689 return (false, ());
690 };
691 let user = &mut table[at];
692 user.passwords.clear();
693 if password.is_empty() {
694 user.nopass = true;
695 } else {
696 user.nopass = false;
697 user.passwords.push(sha256::hex(password));
698 }
699 (true, ())
700 });
701 self.plain.set(password);
702 }
703
704 /// Hand the plain `requirepass` to `f`, which is how `CONFIG GET` writes it.
705 pub(crate) fn with_password<T>(&self, f: impl FnOnce(&[u8]) -> T) -> T {
706 self.plain.with(f)
707 }
708
709 /// The users, for the `ACL` command and for authentication.
710 pub(crate) fn users(&self) -> &Users {
711 &self.acl
712 }
713}
714
715/// The plain `requirepass`, kept beside the hash because `CONFIG GET` reports it.
716///
717/// A real server does the same and it is worth being explicit about why, because
718/// it looks like the hashing was pointless. It is not: the hash is what an ACL
719/// user's password is, and the config file and `ACL LIST` and `ACL GETUSER` all
720/// name it rather than this. This copy exists for one command, `CONFIG GET
721/// requirepass`, which a real server answers with the password itself, and a
722/// server that answered a hash there would break every tool that reads its own
723/// config back.
724#[derive(Debug, Default)]
725pub(crate) struct Plain {
726 /// Empty when there is no `requirepass`.
727 secret: Mutex<Vec<u8>>,
728}
729
730impl Plain {
731 /// Remember the password `CONFIG SET requirepass` was given.
732 fn set(&self, password: &[u8]) {
733 let mut held = self.secret.lock().unwrap_or_else(|e| e.into_inner());
734 held.clear();
735 held.extend_from_slice(password);
736 }
737
738 /// Hand it to `f`, borrowed rather than copied so it lives in one place.
739 fn with<T>(&self, f: impl FnOnce(&[u8]) -> T) -> T {
740 let held = self.secret.lock().unwrap_or_else(|e| e.into_inner());
741 f(&held)
742 }
743}
744
745/// Let this connection in as `user` if the password is right, and say whether
746/// it did.
747///
748/// Answers the same thing for a user that does not exist and a user whose
749/// password was wrong, on purpose: the difference between the two is a list of
750/// user names.
751///
752/// The generation is read before the copy is taken rather than after. A write
753/// that lands in between then leaves the session stamped with a number that is
754/// too small, so its next command takes a fresh copy for nothing, and the other
755/// order would leave it stamped with a number that is too large and holding a
756/// user whose rules had already changed.
757pub(super) fn authenticate(
758 server: &Server,
759 session: &mut Session,
760 user: &[u8],
761 password: &[u8],
762) -> bool {
763 let stamp = server.acl.generation();
764 let Some(found) = server.acl.get(user) else {
765 return false;
766 };
767 if !found.admits(password) {
768 return false;
769 }
770 session.become_user(stamp, found);
771 session.admit(true);
772 true
773}
774
775// ------------------------------------------------------------------ the rules
776
777/// Apply one rule to `user`, which is `ACLSetUser` written out.
778///
779/// The user level rules are here and everything else falls through to the
780/// selector. That split is Redis's and it is why `ACL SETUSER u (on)` is a
781/// syntax error: `on` is a fact about the account and a selector is a set of
782/// permissions, so there is nowhere in a selector to put it.
783fn set_user(user: &mut User, rule: &[u8]) -> std::result::Result<(), Bad> {
784 if rule.is_empty() {
785 return Ok(());
786 }
787 if word(rule, b"on") {
788 user.enabled = true;
789 } else if word(rule, b"off") {
790 user.enabled = false;
791 } else if word(rule, b"skip-sanitize-payload") {
792 user.skip_sanitize = true;
793 } else if word(rule, b"sanitize-payload") {
794 user.skip_sanitize = false;
795 } else if word(rule, b"nopass") {
796 user.nopass = true;
797 user.passwords.clear();
798 } else if word(rule, b"resetpass") {
799 user.nopass = false;
800 user.passwords.clear();
801 } else if rule[0] == b'>' || rule[0] == b'#' {
802 let hash = hash_of(rule)?;
803 if !user.passwords.contains(&hash) {
804 user.passwords.push(hash);
805 }
806 // A user with a password is not a `nopass` user, whatever it was.
807 user.nopass = false;
808 } else if rule[0] == b'<' || rule[0] == b'!' {
809 let hash = hash_of(rule)?;
810 let before = user.passwords.len();
811 user.passwords.retain(|held| *held != hash);
812 if user.passwords.len() == before {
813 return Err(Bad::NoSuchPassword);
814 }
815 } else if rule[0] == b'(' && rule[rule.len() - 1] == b')' {
816 let mut selector = Selector::new();
817 for word in split(&rule[1..rule.len() - 1]) {
818 set_selector(&mut selector, &word)?;
819 }
820 user.selectors.push(selector);
821 } else if rule[0] == b'(' {
822 return Err(Bad::Unmatched);
823 } else if word(rule, b"clearselectors") {
824 user.selectors.truncate(1);
825 } else if word(rule, b"reset") {
826 let name = std::mem::take(&mut user.name);
827 *user = User::new(&name);
828 } else {
829 return set_selector(&mut user.selectors[0], rule);
830 }
831 Ok(())
832}
833
834/// The hash a `>`, `#`, `<` or `!` rule names.
835///
836/// The first form of each pair is a password to hash and the second is a hash
837/// already, and the only difference between them is whether the sixty four
838/// characters are checked or produced.
839fn hash_of(rule: &[u8]) -> std::result::Result<[u8; 64], Bad> {
840 let rest = &rule[1..];
841 if rule[0] == b'>' || rule[0] == b'<' {
842 return Ok(sha256::hex(rest));
843 }
844 let ok = rest.len() == 64
845 && rest
846 .iter()
847 .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(b));
848 if !ok {
849 return Err(Bad::Hash);
850 }
851 let mut hash = [0u8; 64];
852 hash.copy_from_slice(rest);
853 Ok(hash)
854}
855
856/// Apply one rule to a selector, which is `ACLSetSelector` written out.
857fn set_selector(selector: &mut Selector, rule: &[u8]) -> std::result::Result<(), Bad> {
858 if word(rule, b"allkeys") || rule == b"~*" {
859 selector.all_keys = true;
860 selector.patterns.clear();
861 } else if word(rule, b"resetkeys") {
862 selector.all_keys = false;
863 selector.patterns.clear();
864 } else if word(rule, b"allchannels") || rule == b"&*" {
865 selector.all_channels = true;
866 selector.channels.clear();
867 } else if word(rule, b"resetchannels") {
868 selector.all_channels = false;
869 selector.channels.clear();
870 } else if word(rule, b"allcommands") || rule == b"+@all" {
871 selector.reset_commands(true);
872 } else if word(rule, b"nocommands") || rule == b"-@all" {
873 selector.reset_commands(false);
874 } else if rule[0] == b'~' || rule[0] == b'%' {
875 add_pattern(selector, rule)?;
876 } else if rule[0] == b'&' {
877 if selector.all_channels {
878 return Err(Bad::AfterAllChannels);
879 }
880 let glob = &rule[1..];
881 if glob.contains(&b' ') {
882 return Err(Bad::Syntax);
883 }
884 if !selector.channels.iter().any(|held| held == glob) {
885 selector.channels.push(glob.to_vec());
886 }
887 } else if rule[0] == b'+' && rule.get(1) != Some(&b'@') {
888 add_command(selector, &rule[1..], true)?;
889 } else if rule[0] == b'-' && rule.get(1) != Some(&b'@') {
890 add_command(selector, &rule[1..], false)?;
891 } else if rule[0] == b'+' || rule[0] == b'-' {
892 let allow = rule[0] == b'+';
893 add_category(selector, &rule[2..], allow)?;
894 // Recorded with the sigil, because `@read` is what has to be written
895 // back and a bare `read` would read as a command name.
896 let mut name = Vec::with_capacity(rule.len() - 1);
897 name.push(b'@');
898 name.extend_from_slice(&rule[2..].to_ascii_lowercase());
899 selector.note(&name, allow);
900 } else {
901 return Err(Bad::Syntax);
902 }
903 Ok(())
904}
905
906/// A `~pattern` or `%RW~pattern` rule.
907fn add_pattern(selector: &mut Selector, rule: &[u8]) -> std::result::Result<(), Bad> {
908 if selector.all_keys {
909 return Err(Bad::AfterAllKeys);
910 }
911 let mut flags = 0u8;
912 let mut at = 1;
913 if rule[0] == b'%' {
914 // The letters run until the `~`, each may appear once, and there has to
915 // be at least one of them. `%~x` and `%RR~x` are both syntax errors. A
916 // rule that ends before the `~` is not: a bare `%R` is a pattern with an
917 // empty glob, which a real server takes and describes back as `%R~`.
918 let mut ok = true;
919 while at < rule.len() {
920 let letter = rule[at].to_ascii_uppercase();
921 if letter == b'R' && flags & READ == 0 {
922 flags |= READ;
923 } else if letter == b'W' && flags & WRITE == 0 {
924 flags |= WRITE;
925 } else if rule[at] == b'~' {
926 at += 1;
927 break;
928 } else {
929 ok = false;
930 break;
931 }
932 at += 1;
933 }
934 if flags == 0 || !ok {
935 return Err(Bad::Syntax);
936 }
937 } else {
938 flags = READ | WRITE;
939 }
940 let glob = &rule[at..];
941 if glob.contains(&b' ') {
942 return Err(Bad::Syntax);
943 }
944 // The same pattern given twice with different letters is one pattern good
945 // for both, which is why `%R~x %W~x` reads back as `~x`.
946 if let Some(held) = selector.patterns.iter_mut().find(|p| p.glob == glob) {
947 held.flags |= flags;
948 } else {
949 selector.patterns.push(Pattern {
950 flags,
951 glob: glob.to_vec(),
952 });
953 }
954 Ok(())
955}
956
957/// A `+command` or `+command|first` rule.
958fn add_command(selector: &mut Selector, name: &[u8], allow: bool) -> std::result::Result<(), Bad> {
959 let Some(bar) = name.iter().rposition(|b| *b == b'|') else {
960 let Some(spec) = table::lookup(name) else {
961 return Err(Bad::Unknown);
962 };
963 selector.set(table::index_of(spec), allow);
964 selector.note(&name.to_ascii_lowercase(), allow);
965 return Ok(());
966 };
967 let (head, first) = (&name[..bar], &name[bar + 1..]);
968 // The command has to exist even though the first argument cannot be checked,
969 // so `+nosuch|get` is refused and `+get|nosuch` is not.
970 let Some(spec) = table::lookup(head) else {
971 return Err(Bad::Unknown);
972 };
973 if head.contains(&b'|') {
974 return Err(Bad::NestedFirstArg);
975 }
976 if first.is_empty() {
977 return Err(Bad::Syntax);
978 }
979 let at = u16::try_from(table::index_of(spec)).unwrap_or(u16::MAX);
980 if allow {
981 // Nothing to do when the command is already allowed outright, which is
982 // what makes `+get +get|set` the same user as `+get`.
983 if !selector.bit(usize::from(at)) {
984 selector.allow_first(at, first);
985 }
986 } else {
987 // Taking one subcommand away only means anything for a command that has
988 // subcommands. A real server refuses `-get|nope` because it looks the
989 // whole name up in the table and GET has no such row, so this refuses it
990 // too, and for the same answer.
991 if !super::CONTAINERS.contains(&spec.name) {
992 return Err(Bad::Unknown);
993 }
994 selector.deny_first(at, first);
995 }
996 selector.note(&name.to_ascii_lowercase(), allow);
997 Ok(())
998}
999
1000/// A `+@category` or `-@category` rule.
1001fn add_category(selector: &mut Selector, name: &[u8], allow: bool) -> std::result::Result<(), Bad> {
1002 let Some(wanted) = category(name) else {
1003 return Err(Bad::Unknown);
1004 };
1005 for (at, spec) in table::COMMANDS.iter().enumerate() {
1006 if spec.acl.iter().any(|held| &held[1..] == wanted) {
1007 selector.set(at, allow);
1008 }
1009 }
1010 // And then the subcommands that are in the category without their container
1011 // being in it, one first argument at a time. `-@admin` has to reach CONFIG
1012 // GET and leave CONFIG HELP alone, and this is the only thing that knows the
1013 // two are different. See the note on `table::SUBCATS`.
1014 for (container, sub, cats) in table::SUBCATS {
1015 if !cats.iter().any(|held| &held[1..] == wanted) {
1016 continue;
1017 }
1018 let Some(spec) = table::lookup(container.as_bytes()) else {
1019 continue;
1020 };
1021 let at = u16::try_from(table::index_of(spec)).unwrap_or(u16::MAX);
1022 if !allow {
1023 selector.deny_first(at, sub.as_bytes());
1024 } else if !selector.bit(usize::from(at)) {
1025 selector.allow_first(at, sub.as_bytes());
1026 }
1027 }
1028 Ok(())
1029}
1030
1031/// The category called `name`, whatever case it was written in.
1032fn category(name: &[u8]) -> Option<&'static str> {
1033 CATEGORIES
1034 .iter()
1035 .find(|held| name.eq_ignore_ascii_case(held.as_bytes()))
1036 .copied()
1037}
1038
1039/// Whether `rule` is the keyword `word`, case insensitively.
1040fn word(rule: &[u8], keyword: &[u8]) -> bool {
1041 rule.eq_ignore_ascii_case(keyword)
1042}
1043
1044/// Split a selector's inside into rules on runs of spaces.
1045///
1046/// A selector arrives as one argument, `(+get ~k:*)`, so the words inside it
1047/// have to be taken apart here. Redis uses its config file splitter, which
1048/// understands quotes; this does not, because a key pattern with a space in it
1049/// is refused by the rule above anyway and a quoted rule has nowhere to be
1050/// useful.
1051fn split(inside: &[u8]) -> Vec<Vec<u8>> {
1052 inside
1053 .split(|b| *b == b' ')
1054 .filter(|part| !part.is_empty())
1055 .map(<[u8]>::to_vec)
1056 .collect()
1057}
1058
1059// ------------------------------------------------------------- the permissions
1060
1061/// Why a command was refused, ranked the way Redis ranks them.
1062///
1063/// The rank decides which of several selectors' complaints is reported: a user
1064/// with two selectors that both say no is told about the most specific refusal,
1065/// on the grounds that the selector which got as far as looking at a key was the
1066/// one the operator meant to use.
1067#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1068pub(crate) enum Denied {
1069 /// Not allowed to run the command at all.
1070 Command,
1071 /// Allowed the command, not the key at this argument.
1072 Key(usize),
1073 /// Allowed the command, not the channel at this argument.
1074 Channel(usize),
1075}
1076
1077impl Denied {
1078 /// Redis's numeric ranking, which is what two refusals are compared on.
1079 fn rank(self) -> u8 {
1080 match self {
1081 Denied::Command => 1,
1082 Denied::Key(_) => 2,
1083 Denied::Channel(_) => 4,
1084 }
1085 }
1086
1087 /// The argument the refusal is about, or nought for the command itself.
1088 fn at(self) -> usize {
1089 match self {
1090 Denied::Command => 0,
1091 Denied::Key(at) | Denied::Channel(at) => at,
1092 }
1093 }
1094}
1095
1096/// Where a command's channels are, since they are not in the key specs.
1097///
1098/// Eight commands and no more, which is Redis's own table. The flags in Redis
1099/// are four and only two of them are checked, so what is kept here is the one
1100/// bit that matters, whether the argument is a pattern being subscribed to, plus
1101/// whether the command is checked at all: unsubscribing is always allowed,
1102/// because a client that has lost permission to a channel still has to be able
1103/// to stop listening to it.
1104struct Channels {
1105 /// The first argument that is a channel.
1106 first: usize,
1107 /// How many there are, or `None` for all the rest.
1108 count: Option<usize>,
1109 /// Whether these are patterns, which are matched literally rather than as
1110 /// globs. `PSUBSCRIBE news.*` needs the ACL to hold `&news.*` exactly, not
1111 /// something that matches it, because otherwise `&news.sport` would let a
1112 /// client subscribe to `news.*` and hear everything.
1113 pattern: bool,
1114}
1115
1116/// The channel arguments of `name`, if it has any that are checked.
1117fn channels_of(name: &str) -> Option<Channels> {
1118 let spec = match name {
1119 "subscribe" | "ssubscribe" => Channels {
1120 first: 1,
1121 count: None,
1122 pattern: false,
1123 },
1124 "psubscribe" => Channels {
1125 first: 1,
1126 count: None,
1127 pattern: true,
1128 },
1129 "publish" | "spublish" => Channels {
1130 first: 1,
1131 count: Some(1),
1132 pattern: false,
1133 },
1134 _ => return None,
1135 };
1136 Some(spec)
1137}
1138
1139/// Whether `selector` may reach `key` for `need`.
1140fn key_ok(selector: &Selector, key: &[u8], need: Access) -> bool {
1141 if selector.all_keys {
1142 return true;
1143 }
1144 selector
1145 .patterns
1146 .iter()
1147 .any(|p| Access::from_bits(p.flags).covers(need) && glob_matches(&p.glob, key))
1148}
1149
1150/// Whether `selector` may reach `channel`.
1151fn channel_ok(selector: &Selector, channel: &[u8], pattern: bool) -> bool {
1152 if selector.all_channels {
1153 return true;
1154 }
1155 selector.channels.iter().any(|held| {
1156 if pattern {
1157 held == channel
1158 } else {
1159 glob_matches(held, channel)
1160 }
1161 })
1162}
1163
1164/// Whether one selector allows this whole command, and what it objected to.
1165fn selector_ok(
1166 selector: &Selector,
1167 spec: &'static Spec,
1168 args: Args<'_>,
1169 base: usize,
1170) -> std::result::Result<(), Denied> {
1171 if !selector.all_commands && !spec.flags.contains(&"no_auth") {
1172 let at = table::index_of(spec);
1173 let index = u16::try_from(at).unwrap_or(u16::MAX);
1174 let sub = (args.len() > base + 1).then(|| args.get(base + 1));
1175 let named = |list: &[Vec<u8>]| {
1176 sub.is_some_and(|word| list.iter().any(|held| word.eq_ignore_ascii_case(held)))
1177 };
1178 if selector.bit(at) {
1179 // Allowed outright, unless this is the one subcommand that was taken
1180 // back off it.
1181 if named(selector.denied(index)) {
1182 return Err(Denied::Command);
1183 }
1184 } else if !named(selector.firsts(index)) {
1185 return Err(Denied::Command);
1186 }
1187 }
1188
1189 if !selector.all_keys && keyspec::takes_keys(spec, args, base) {
1190 let mut refused = None;
1191 keyspec::find(spec, args, base, &mut |run| {
1192 if refused.is_some() {
1193 return;
1194 }
1195 let need = run.need();
1196 for i in 0..run.count {
1197 let at = run.first + i * run.step;
1198 if at < args.len() && !key_ok(selector, args.get(at), need) {
1199 refused = Some(Denied::Key(at));
1200 return;
1201 }
1202 }
1203 });
1204 if let Some(why) = refused {
1205 return Err(why);
1206 }
1207 }
1208
1209 if !selector.all_channels
1210 && let Some(where_) = channels_of(spec.name)
1211 {
1212 let first = base + where_.first;
1213 let stop = where_
1214 .count
1215 .map_or(args.len(), |n| (first + n).min(args.len()));
1216 for at in first..stop {
1217 if !channel_ok(selector, args.get(at), where_.pattern) {
1218 return Err(Denied::Channel(at));
1219 }
1220 }
1221 }
1222 Ok(())
1223}
1224
1225/// Whether `user` may run this command, and what it objected to.
1226///
1227/// Every selector is tried and the first that says yes wins. When none does, the
1228/// refusal reported is the highest ranked one, and on a tie the one about the
1229/// argument furthest along, which is Redis's rule and is what makes a two
1230/// selector user complain about the key rather than the command.
1231pub(crate) fn permits(
1232 user: &User,
1233 spec: &'static Spec,
1234 args: Args<'_>,
1235 base: usize,
1236) -> std::result::Result<(), Denied> {
1237 // The whole of the cost on a server whose users can do anything, which is
1238 // every server nobody has written an ACL for.
1239 if let Some(root) = user.selectors.first()
1240 && root.all_commands
1241 && root.all_keys
1242 && root.all_channels
1243 {
1244 return Ok(());
1245 }
1246 let mut worst = Denied::Command;
1247 for selector in &user.selectors {
1248 match selector_ok(selector, spec, args, base) {
1249 Ok(()) => return Ok(()),
1250 Err(why) => {
1251 if why.rank() > worst.rank()
1252 || (why.rank() == worst.rank() && why.at() > worst.at())
1253 {
1254 worst = why;
1255 }
1256 }
1257 }
1258 }
1259 Err(worst)
1260}
1261
1262/// The sentence a refusal is reported with.
1263///
1264/// `verbose` is the difference between the gate and `ACL DRYRUN`. The gate is
1265/// answering a client that has just been told no and naming the key it asked
1266/// about would tell it which keys exist, so the terse form says only that a key
1267/// was the problem. `ACL DRYRUN` is answering an operator who asked the
1268/// question on purpose and wants to know which key, so it names it.
1269pub(crate) fn refusal(
1270 why: Denied,
1271 user: &[u8],
1272 spec: &'static Spec,
1273 args: Args<'_>,
1274 base: usize,
1275 verbose: bool,
1276) -> String {
1277 let name = String::from_utf8_lossy(user);
1278 match why {
1279 Denied::Command => {
1280 let sub = container_name(spec, args, base);
1281 format!("User {name} has no permissions to run the '{sub}' command")
1282 }
1283 Denied::Key(at) if verbose => {
1284 let key = String::from_utf8_lossy(args.get(at));
1285 format!("User {name} has no permissions to access the '{key}' key")
1286 }
1287 Denied::Key(_) => "No permissions to access a key".to_string(),
1288 Denied::Channel(at) if verbose => {
1289 let channel = String::from_utf8_lossy(args.get(at));
1290 format!("User {name} has no permissions to access the '{channel}' channel")
1291 }
1292 Denied::Channel(_) => "No permissions to access a channel".to_string(),
1293 }
1294}
1295
1296/// The command's name as a refusal spells it, which is `acl|list` and not `acl`.
1297fn container_name(spec: &'static Spec, args: Args<'_>, base: usize) -> String {
1298 if super::CONTAINERS.contains(&spec.name) && args.len() > base + 1 {
1299 let sub = String::from_utf8_lossy(args.get(base + 1)).to_lowercase();
1300 return format!("{}|{sub}", spec.name);
1301 }
1302 spec.name.to_string()
1303}
1304
1305/// Who a connection is, and the copy of that user its commands are checked
1306/// against.
1307///
1308/// Boxed on the session, because it is three allocations and a connection on a
1309/// server with no ACL never reads any of them past the first command.
1310#[derive(Debug)]
1311pub(crate) struct Identity {
1312 /// The name, which is what `ACL WHOAMI` and `CLIENT INFO` report.
1313 name: Vec<u8>,
1314 /// The generation the copy below was taken at.
1315 stamp: u64,
1316 /// The copy itself.
1317 user: User,
1318}
1319
1320impl Default for Identity {
1321 /// A connection starts as the default user, with a copy stamped nought so
1322 /// that the first command it sends fetches the real one.
1323 fn default() -> Identity {
1324 Identity {
1325 name: DEFAULT.to_vec(),
1326 stamp: 0,
1327 user: User::default_user(),
1328 }
1329 }
1330}
1331
1332impl Session {
1333 /// The name this connection is authenticated as.
1334 pub(crate) fn acl_name(&self) -> &[u8] {
1335 &self.acl.name
1336 }
1337
1338 /// Say that this connection has authenticated as `user`.
1339 ///
1340 /// Written to the row as well as here, because `CLIENT LIST` reports the
1341 /// user of every connection and runs on whichever thread the client asking
1342 /// is on, which is very often not this one.
1343 pub(super) fn become_user(&mut self, stamp: u64, user: User) {
1344 yo_alloc::allow(|| {
1345 self.acl.name.clear();
1346 self.acl.name.extend_from_slice(&user.name);
1347 });
1348 self.acl.stamp = stamp;
1349 self.acl.user = user;
1350 self.sock.set_text(|text| &mut text.user, &self.acl.name);
1351 }
1352
1353 /// Put the connection back on the default user, which is what `RESET` does.
1354 pub(super) fn forget_user(&mut self) {
1355 *self.acl = Identity::default();
1356 // Empty rather than the name, which is how the row spells the default
1357 // user, so a connection that never authenticated costs nothing.
1358 self.sock.set_text(|text| &mut text.user, b"");
1359 }
1360
1361 /// The generation the copy was taken at.
1362 fn acl_stamp(&self) -> u64 {
1363 self.acl.stamp
1364 }
1365
1366 /// Take a fresh copy, or keep the one there is if the user has gone.
1367 fn acl_refresh(&mut self, now: u64, fresh: Option<User>) {
1368 self.acl.stamp = now;
1369 if let Some(user) = fresh {
1370 self.acl.user = user;
1371 }
1372 }
1373
1374 /// The copy itself.
1375 fn acl_cached(&self) -> &User {
1376 &self.acl.user
1377 }
1378}
1379
1380/// What the connection is running as, refreshed if the table has moved on.
1381///
1382/// A session keeps a copy of its user rather than a handle into the table,
1383/// because the alternative is taking the table's lock on every command on every
1384/// connection. The copy is stamped with the generation it was taken at and
1385/// replaced when that number moves, so a `SETUSER` that tightens a user's
1386/// permissions reaches every connection already authenticated as it, on that
1387/// connection's next command. That is what a real server does, and it is the
1388/// half of `SETUSER` that would be easy to get wrong: the point of tightening a
1389/// user is usually the connection that is already open.
1390///
1391/// A user deleted out from under a connection leaves the copy in place, which is
1392/// also Redis's behaviour: `ACL DELUSER` closes those connections rather than
1393/// leaving them running as a user that no longer exists.
1394fn current<'a>(server: &Server, session: &'a mut Session) -> &'a User {
1395 let now = server.acl.generation();
1396 if session.acl_stamp() != now {
1397 let fresh = server.acl.get(session.acl_name());
1398 session.acl_refresh(now, fresh);
1399 }
1400 session.acl_cached()
1401}
1402
1403/// The gate, which is every command on a server that has an ACL worth checking.
1404///
1405/// `None` means the command may run. The refusal is written by the caller
1406/// rather than here, because it is one of the errors that kills an open
1407/// transaction and the caller is what knows about transactions.
1408pub(super) fn gate(
1409 server: &Server,
1410 session: &mut Session,
1411 spec: &'static Spec,
1412 args: Args<'_>,
1413) -> Option<String> {
1414 let user = current(server, session);
1415 let why = permits(user, spec, args, 0).err()?;
1416 // The code is in the line rather than in front of it, because the line goes
1417 // two places: straight into the reply, and spliced into the `EXECABORT` an
1418 // `EXEC` gets. The same reason `NOAUTH` is a whole line.
1419 let said = refusal(why, &user.name, spec, args, 0, false);
1420 Some(yo_alloc::allow(|| format!("NOPERM {said}")))
1421}
1422
1423// ------------------------------------------------------------- the command
1424
1425/// The subcommands and how many arguments each takes, counting `ACL` itself.
1426///
1427/// A real server gives every one of these a row in the command table with its
1428/// own arity, and enforces it before the body is reached, which is why `ACL
1429/// WHOAMI x` is a wrong number of arguments and `ACL LOG 1 2` is an unknown
1430/// subcommand: the second one passes its own arity check and then falls off the
1431/// end of the parse. There is no subcommand table here yet, so the arities live
1432/// in this list and the same two sentences come out. It folds into D-114.
1433///
1434/// `LOAD`, `SAVE` and `LOG` are not here because they are not here at all yet,
1435/// and a row for one of them would mean answering a precise arity error for a
1436/// subcommand that is about to be called unknown.
1437const ARITIES: [(&[u8], i32); 9] = [
1438 (b"cat", -2),
1439 (b"deluser", -3),
1440 (b"dryrun", -4),
1441 (b"genpass", -2),
1442 (b"getuser", 3),
1443 (b"help", 2),
1444 (b"list", 2),
1445 (b"setuser", -3),
1446 (b"users", 2),
1447 // `whoami` is not here because its arity is exactly two, which is the same
1448 // check the fallthrough below makes, and a row for it would be dead weight.
1449];
1450
1451/// `ACL <subcommand> ...`.
1452pub(super) fn execute(
1453 server: &Server,
1454 session: &mut Session,
1455 args: Args<'_>,
1456 out: &mut Out,
1457) -> Result<()> {
1458 let sub = args.get(1);
1459 // The arity of the subcommand before anything else, which is where a real
1460 // server makes this decision: the row is found and checked in
1461 // `processCommand`, so a wrong count is refused before the body runs.
1462 if let Some((name, arity)) = ARITIES
1463 .iter()
1464 .find(|(name, _)| sub.eq_ignore_ascii_case(name))
1465 {
1466 let n = args.len() as i32;
1467 if (*arity > 0 && n != *arity) || (*arity < 0 && n < -*arity) {
1468 let name = std::str::from_utf8(name).unwrap_or("acl");
1469 return Err(args::wrong_arity_sub("acl", name));
1470 }
1471 }
1472
1473 if is(sub, b"whoami") {
1474 if args.len() != 2 {
1475 return Err(args::wrong_arity_sub("acl", "whoami"));
1476 }
1477 out.bulk(session.acl_name());
1478 } else if is(sub, b"cat") {
1479 cat(args, out)?;
1480 } else if is(sub, b"list") {
1481 yo_alloc::allow(|| {
1482 server.users().with(|table| {
1483 out.array(table.len());
1484 for user in table.iter() {
1485 let mut line = b"user ".to_vec();
1486 line.extend_from_slice(&user.describe());
1487 out.bulk(&line);
1488 }
1489 (false, ())
1490 });
1491 });
1492 } else if is(sub, b"users") {
1493 server.users().with(|table| {
1494 out.array(table.len());
1495 for user in table.iter() {
1496 out.bulk(&user.name);
1497 }
1498 (false, ())
1499 });
1500 } else if is(sub, b"getuser") {
1501 yo_alloc::allow(|| getuser(server, args, out));
1502 } else if is(sub, b"setuser") {
1503 return yo_alloc::allow(|| setuser(server, args, out));
1504 } else if is(sub, b"deluser") {
1505 return deluser(server, args, out);
1506 } else if is(sub, b"genpass") {
1507 genpass(args, out)?;
1508 } else if is(sub, b"dryrun") {
1509 return yo_alloc::allow(|| dryrun(server, args, out));
1510 } else if is(sub, b"help") {
1511 super::server::help(out, HELP);
1512 } else {
1513 return Err(args::unknown_subcommand(sub, "ACL"));
1514 }
1515 Ok(())
1516}
1517
1518/// `ACL CAT` and `ACL CAT <category>`.
1519///
1520/// With no argument this is the list of categories, and with one it is the
1521/// commands in it, in table order. Redis walks a hash table there and so reports
1522/// an order that is neither sorted nor stable across versions, so matching it
1523/// exactly is not a thing to aim at; what a client can rely on is the set, and
1524/// this reports the same set for every category the two servers share.
1525fn cat(args: Args<'_>, out: &mut Out) -> Result<()> {
1526 if args.len() == 2 {
1527 out.array(CATEGORIES.len());
1528 for name in CATEGORIES {
1529 out.bulk(name.as_bytes());
1530 }
1531 return Ok(());
1532 }
1533 if args.len() > 3 {
1534 return Err(args::subcommand_syntax(args.get(1), "ACL"));
1535 }
1536 let Some(wanted) = category(args.get(2)) else {
1537 return Err(yo_alloc::allow(|| {
1538 Error::fmt(
1539 Code::Invalid,
1540 format_args!(
1541 "Unknown category '{}'",
1542 String::from_utf8_lossy(args.get(2))
1543 ),
1544 )
1545 }));
1546 };
1547 // The header goes on afterwards, because how many commands are in a
1548 // category is not a thing the table can be asked without walking it.
1549 let start = out.len();
1550 let mut n = 0;
1551 for spec in table::COMMANDS {
1552 if spec.acl.iter().any(|held| &held[1..] == wanted) {
1553 out.bulk(spec.name.as_bytes());
1554 n += 1;
1555 }
1556 }
1557 out.close_array(start, n);
1558 Ok(())
1559}
1560
1561/// `ACL GETUSER <username>`.
1562fn getuser(server: &Server, args: Args<'_>, out: &mut Out) {
1563 let Some(user) = server.users().get(args.get(2)) else {
1564 out.nil();
1565 return;
1566 };
1567 // Six fields: the two about the account, the root selector's three repeated
1568 // at the top level for the clients that were written before selectors
1569 // existed, and the selectors themselves.
1570 out.map(6);
1571 out.bulk(b"flags");
1572 // Only the flags that belong to the account. The selector flags are named in
1573 // the same table and an older server did list them here, but 8.10.1 does not,
1574 // and a client that wants to know whether a user can reach every key reads
1575 // the keys field rather than counting words in this set.
1576 let flags = user.flags();
1577 let root = &user.selectors[0];
1578 out.set(flags.len());
1579 for flag in &flags {
1580 out.bulk(flag.as_bytes());
1581 }
1582 out.bulk(b"passwords");
1583 out.array(user.passwords.len());
1584 for hash in &user.passwords {
1585 out.bulk(hash);
1586 }
1587 describe_selector(root, out);
1588 out.bulk(b"selectors");
1589 out.array(user.selectors.len() - 1);
1590 for selector in &user.selectors[1..] {
1591 out.map(3);
1592 describe_selector(selector, out);
1593 }
1594}
1595
1596/// The three fields a selector contributes to `ACL GETUSER`.
1597fn describe_selector(selector: &Selector, out: &mut Out) {
1598 out.bulk(b"commands");
1599 out.bulk(&selector.describe_commands());
1600 out.bulk(b"keys");
1601 out.bulk(&selector.describe_keys());
1602 out.bulk(b"channels");
1603 out.bulk(&selector.describe_channels());
1604}
1605
1606/// `ACL SETUSER <username> [rule ...]`.
1607///
1608/// Every rule is applied to a copy and the copy replaces the user only if all of
1609/// them worked, so a `SETUSER` that fails halfway leaves nothing behind. That is
1610/// worth more than it sounds: the failure case is an operator tightening a
1611/// user's permissions and mistyping one rule, and a server that applied the
1612/// first half would have left the user with the new restrictions and none of the
1613/// new grants.
1614fn setuser(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1615 let name = args.get(2);
1616 if name.contains(&b' ') || name.contains(&0) {
1617 return Err(Error::new(
1618 Code::Invalid,
1619 "Usernames can't contain spaces or null characters",
1620 ));
1621 }
1622 let rules = merge(args, 3)?;
1623 let outcome = server.users().with(|table| {
1624 let at = table.binary_search_by(|u| u.name.as_slice().cmp(name));
1625 let mut staged = match at {
1626 Ok(at) => table[at].clone(),
1627 Err(_) => User::new(name),
1628 };
1629 for rule in &rules {
1630 if let Err(why) = set_user(&mut staged, rule) {
1631 return (false, Err((rule.clone(), why)));
1632 }
1633 }
1634 match at {
1635 Ok(at) => table[at] = staged,
1636 Err(at) => table.insert(at, staged),
1637 }
1638 (true, Ok(()))
1639 });
1640 match outcome {
1641 Ok(()) => {
1642 out.ok();
1643 Ok(())
1644 }
1645 Err((rule, why)) => Err(Error::fmt(
1646 Code::Invalid,
1647 format_args!(
1648 "Error in ACL SETUSER modifier '{}': {}",
1649 String::from_utf8_lossy(&rule),
1650 why.text()
1651 ),
1652 )),
1653 }
1654}
1655
1656/// Join the arguments from `from` on, gluing a selector back together.
1657///
1658/// A selector is one rule and a client sends it as several arguments, because
1659/// `(+get ~k:*)` has a space in it and the wire has no way to say that was meant
1660/// as one word. So a `(` that does not end in `)` swallows the arguments after
1661/// it until one does. An opening bracket that is never closed is the one rule
1662/// error reported in its own sentence rather than as a modifier error, because
1663/// there is no single modifier to blame.
1664fn merge(args: Args<'_>, from: usize) -> Result<Vec<Vec<u8>>> {
1665 let mut out: Vec<Vec<u8>> = Vec::with_capacity(args.len() - from);
1666 let mut open: Option<usize> = None;
1667 for i in from..args.len() {
1668 let word = args.get(i);
1669 if open.is_none() && word.first() == Some(&b'(') && word.last() != Some(&b')') {
1670 open = Some(i);
1671 out.push(word.to_vec());
1672 continue;
1673 }
1674 if open.is_some() {
1675 let held = out.last_mut().expect("an open bracket left a rule behind");
1676 held.push(b' ');
1677 held.extend_from_slice(word);
1678 if word.last() == Some(&b')') {
1679 open = None;
1680 }
1681 continue;
1682 }
1683 out.push(word.to_vec());
1684 }
1685 if let Some(at) = open {
1686 return Err(Error::fmt(
1687 Code::Invalid,
1688 format_args!(
1689 "Unmatched parenthesis in acl selector starting at '{}'.",
1690 String::from_utf8_lossy(args.get(at))
1691 ),
1692 ));
1693 }
1694 Ok(out)
1695}
1696
1697/// `ACL DELUSER <username> [<username> ...]`.
1698///
1699/// The default user is checked for over the whole list before anything is
1700/// deleted, so `ACL DELUSER alice default` deletes neither. Redis does the same
1701/// and the reason is the same as `SETUSER`'s: a half done change to who may
1702/// reach a server is worse than no change.
1703fn deluser(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1704 for i in 2..args.len() {
1705 if args.get(i) == DEFAULT {
1706 return Err(Error::new(
1707 Code::Invalid,
1708 "The 'default' user cannot be removed",
1709 ));
1710 }
1711 }
1712 let gone = server.users().with(|table| {
1713 let mut gone = 0;
1714 for i in 2..args.len() {
1715 if let Ok(at) = table.binary_search_by(|u| u.name.as_slice().cmp(args.get(i))) {
1716 table.remove(at);
1717 gone += 1;
1718 }
1719 }
1720 (gone > 0, gone)
1721 });
1722 out.int(gone);
1723 Ok(())
1724}
1725
1726/// `ACL GENPASS [<bits>]`.
1727///
1728/// The bytes come from the operating system rather than from the engine's own
1729/// generator, which is seeded and reproducible on purpose. See
1730/// [`yo_common::entropy`].
1731fn genpass(args: Args<'_>, out: &mut Out) -> Result<()> {
1732 if args.len() > 3 {
1733 return Err(args::subcommand_syntax(args.get(1), "ACL"));
1734 }
1735 let bits = if args.len() == 3 { args.int(2)? } else { 256 };
1736 if bits <= 0 || bits > 4096 {
1737 return Err(Error::new(
1738 Code::Invalid,
1739 "ACL GENPASS argument must be the number of bits for the output password, a positive number up to 4096",
1740 ));
1741 }
1742 // One hex character is four bits, rounded up, so `GENPASS 10` is three
1743 // characters and holds twelve bits rather than ten. That is the reference's
1744 // arithmetic and it errs towards more entropy than was asked for.
1745 let chars = ((bits + 3) / 4) as usize;
1746 let mut raw = [0u8; 4096 / 8 + 1];
1747 let bytes = chars.div_ceil(2);
1748 yo_common::entropy::fill(&mut raw[..bytes]);
1749 const DIGITS: &[u8; 16] = b"0123456789abcdef";
1750 let mut hex = [0u8; 1024];
1751 for (i, slot) in hex[..chars].iter_mut().enumerate() {
1752 let byte = raw[i / 2];
1753 *slot = DIGITS[usize::from(if i % 2 == 0 { byte >> 4 } else { byte & 0xf })];
1754 }
1755 out.bulk(&hex[..chars]);
1756 Ok(())
1757}
1758
1759/// `ACL DRYRUN <username> <command> [<arg> ...]`.
1760///
1761/// The same question the gate asks, asked out loud. The answer is a bulk string
1762/// rather than an error even when it is a refusal, because the command
1763/// succeeded: it was asked whether something would be allowed and it found out.
1764fn dryrun(server: &Server, args: Args<'_>, out: &mut Out) -> Result<()> {
1765 let Some(user) = server.users().get(args.get(2)) else {
1766 return Err(Error::fmt(
1767 Code::Invalid,
1768 format_args!("User '{}' not found", String::from_utf8_lossy(args.get(2))),
1769 ));
1770 };
1771 let Some(spec) = table::lookup(args.get(3)) else {
1772 return Err(Error::fmt(
1773 Code::Invalid,
1774 format_args!(
1775 "Command '{}' not found",
1776 String::from_utf8_lossy(args.get(3))
1777 ),
1778 ));
1779 };
1780 if !table::arity_ok(spec, args.len() - 3) {
1781 return Err(args::wrong_arity(spec.name));
1782 }
1783 match permits(&user, spec, args, 3) {
1784 Ok(()) => out.ok(),
1785 Err(why) => {
1786 let text = refusal(why, &user.name, spec, args, 3, true);
1787 out.bulk(text.as_bytes());
1788 }
1789 }
1790 Ok(())
1791}
1792
1793/// What `ACL HELP` says.
1794///
1795/// The subcommands that are here, which is all of Redis's but `LOAD`, `SAVE` and
1796/// `LOG`. Those three want an ACL file and a log of refusals, which is the next
1797/// change rather than this one, and a client reading this to find out what it
1798/// can send should not be told about them by a server that would answer them
1799/// with an unknown subcommand.
1800const HELP: &[&str] = &[
1801 "ACL <subcommand> [<arg> [value] [opt] ...]. Subcommands are:",
1802 "CAT [<category>]",
1803 " List all commands that belong to <category>, or all command categories",
1804 " when no category is specified.",
1805 "DELUSER <username> [<username> ...]",
1806 " Delete a list of users.",
1807 "DRYRUN <username> <command> [<arg> ...]",
1808 " Returns whether the user can execute the given command without executing the command.",
1809 "GETUSER <username>",
1810 " Get the user's details.",
1811 "GENPASS [<bits>]",
1812 " Generate a secure 256-bit user password. The optional `bits` argument can",
1813 " be used to specify a different size.",
1814 "LIST",
1815 " Show users details in config file format.",
1816 "SETUSER <username> <attribute> [<attribute> ...]",
1817 " Create or modify a user with the specified attributes.",
1818 "USERS",
1819 " List all the registered usernames.",
1820 "WHOAMI",
1821 " Return the current connection username.",
1822 "HELP",
1823 " Print this help.",
1824];
1825
1826#[cfg(test)]
1827mod tests {
1828 use super::*;
1829 use crate::proto::Limits;
1830 use crate::request::{Argv, Step};
1831
1832 /// A user built by applying rules in order, or the first rule that failed.
1833 fn built(rules: &[&str]) -> std::result::Result<User, Bad> {
1834 let mut user = User::new(b"u");
1835 for rule in rules {
1836 set_user(&mut user, rule.as_bytes())?;
1837 }
1838 Ok(user)
1839 }
1840
1841 /// The user's line the way `ACL LIST` writes it.
1842 fn listed(rules: &[&str]) -> String {
1843 let user = built(rules).expect("every rule here is a good one");
1844 String::from_utf8(user.describe()).expect("the description is text")
1845 }
1846
1847 /// Whether this user could run this command, said as a word so a failing
1848 /// assertion reads like the question it was asked.
1849 fn allows(rules: &[&str], words: &[&str]) -> bool {
1850 let user = built(rules).expect("every rule here is a good one");
1851 let mut buf = format!("*{}\r\n", words.len()).into_bytes();
1852 for word in words {
1853 buf.extend_from_slice(format!("${}\r\n{word}\r\n", word.len()).as_bytes());
1854 }
1855 let mut argv = Argv::new();
1856 let Ok(Step::Command { .. }) = argv.decode(&buf, &Limits::default()) else {
1857 panic!("the test wrote a command that does not decode");
1858 };
1859 let args = Args::new(&argv, &buf);
1860 let spec = table::lookup(args.name()).expect("a command this server has");
1861 permits(&user, spec, args, 0).is_ok()
1862 }
1863
1864 #[test]
1865 fn a_key_permission_with_no_pattern_after_it_is_an_empty_pattern() {
1866 // 8.10.1 takes a bare `%R` and writes it back with the `~` it never got.
1867 assert_eq!(
1868 listed(&["%R"]),
1869 "u off sanitize-payload %R~ resetchannels -@all"
1870 );
1871 assert_eq!(
1872 listed(&["%RW"]),
1873 "u off sanitize-payload ~ resetchannels -@all"
1874 );
1875 }
1876
1877 #[test]
1878 fn a_key_permission_that_is_not_r_or_w_once_each_is_a_syntax_error() {
1879 assert_eq!(built(&["%"]).err(), Some(Bad::Syntax));
1880 assert_eq!(built(&["%~k:*"]).err(), Some(Bad::Syntax));
1881 assert_eq!(built(&["%RR~k:*"]).err(), Some(Bad::Syntax));
1882 assert_eq!(built(&["%X~k:*"]).err(), Some(Bad::Syntax));
1883 }
1884
1885 #[test]
1886 fn a_subcommand_can_be_taken_back_off_a_container_that_was_allowed() {
1887 assert_eq!(
1888 listed(&["+config", "-config|get"]),
1889 "u off sanitize-payload resetchannels -@all +config -config|get"
1890 );
1891 assert!(allows(
1892 &["+config", "-config|get"],
1893 &["config", "set", "maxmemory", "0"]
1894 ));
1895 assert!(!allows(
1896 &["+config", "-config|get"],
1897 &["config", "get", "maxmemory"]
1898 ));
1899 // And allowing the container again forgets the exception, the same way
1900 // allowing it forgets a first argument it was limited to.
1901 assert_eq!(
1902 listed(&["+config", "-config|get", "+config"]),
1903 "u off sanitize-payload resetchannels -@all +config"
1904 );
1905 assert!(allows(
1906 &["+config", "-config|get", "+config"],
1907 &["config", "get", "maxmemory"]
1908 ));
1909 }
1910
1911 #[test]
1912 fn a_first_argument_can_only_be_taken_off_a_command_that_has_subcommands() {
1913 // GET has no subcommands, so a real server looks up `get|nope`, finds
1914 // nothing, and says so. Allowing one is a different mechanism and works.
1915 assert_eq!(built(&["-get|nope"]).err(), Some(Bad::Unknown));
1916 assert_eq!(built(&["-select|0"]).err(), Some(Bad::Unknown));
1917 assert!(built(&["+get|nope"]).is_ok());
1918 }
1919
1920 #[test]
1921 fn a_category_a_subcommand_holds_reaches_that_subcommand_and_no_further() {
1922 // CONFIG is only `@slow` in the table because 8.10.1 puts `@admin` on
1923 // `config|get` and `config|set` rather than on `config`, so a rule about
1924 // `@admin` has to take CONFIG GET away and leave CONFIG HELP behind.
1925 let deny = ["~*", "+@all", "-@admin"];
1926 assert!(!allows(&deny, &["config", "get", "maxmemory"]));
1927 assert!(allows(&deny, &["config", "help"]));
1928 assert!(!allows(&deny, &["client", "kill", "id", "4"]));
1929 assert!(allows(&deny, &["client", "setname", "x"]));
1930 assert!(allows(&deny, &["acl", "whoami"]));
1931 assert!(!allows(&deny, &["acl", "setuser", "u"]));
1932 // And the other way round, from a user that starts with nothing.
1933 let grant = ["~*", "-@all", "+@admin"];
1934 assert!(allows(&grant, &["config", "get", "maxmemory"]));
1935 assert!(!allows(&grant, &["config", "help"]));
1936 assert!(!allows(&grant, &["get", "k"]));
1937 // None of which changes what the container is listed as being in, or
1938 // `COMMAND INFO config` and `ACL CAT admin` would both start lying.
1939 let config = table::lookup(b"config").expect("a command this server has");
1940 assert_eq!(config.acl, ["@slow"]);
1941 }
1942
1943 #[test]
1944 fn the_flags_a_user_reports_are_the_ones_about_the_account() {
1945 // Not `allkeys`, which an older server did report here and 8.10.1 does
1946 // not. A client that wants to know reads the keys field.
1947 let user = built(&["on", "~*", "&*", "+@all"]).expect("good rules");
1948 assert_eq!(user.flags(), ["on", "sanitize-payload"]);
1949 }
1950}