Skip to main content

zebra_rpc/server/
cookie.rs

1//! Cookie-based authentication for the RPC server.
2
3use base64::{engine::general_purpose::STANDARD, Engine as _};
4use color_eyre::Result;
5use rand::RngCore;
6
7use std::{
8    fs::{remove_file, File},
9    io::Write,
10    path::Path,
11};
12
13#[cfg(unix)]
14use std::os::unix::fs::OpenOptionsExt;
15
16/// The name of the cookie file on the disk
17const FILE: &str = ".cookie";
18
19/// If the RPC authentication is enabled, all requests must contain this cookie.
20#[derive(Clone, Debug)]
21pub struct Cookie(String);
22
23impl Cookie {
24    /// Checks if the given passwd matches the contents of the cookie.
25    pub fn authenticate(&self, passwd: String) -> bool {
26        *passwd == self.0
27    }
28}
29
30impl Default for Cookie {
31    fn default() -> Self {
32        let mut bytes = [0u8; 32];
33        rand::thread_rng().fill_bytes(&mut bytes);
34
35        Self(STANDARD.encode(bytes))
36    }
37}
38
39/// Writes the given cookie to the given dir.
40///
41/// Uses restrictive file permissions (0600 on Unix) to prevent other
42/// local users from reading the cookie secret.
43pub fn write_to_disk(cookie: &Cookie, dir: &Path) -> Result<()> {
44    std::fs::create_dir_all(dir)?;
45
46    let cookie_path = dir.join(FILE);
47
48    if cookie_path
49        .symlink_metadata()
50        .map(|m| m.file_type().is_symlink())
51        .unwrap_or(false)
52    {
53        return Err(color_eyre::eyre::eyre!(
54            "cookie path {cookie_path:?} is a symlink, refusing to write"
55        ));
56    }
57
58    let mut file = create_owner_only_file(&cookie_path)?;
59    file.write_all(format!("__cookie__:{}", cookie.0).as_bytes())?;
60
61    tracing::info!("RPC auth cookie written to disk");
62
63    Ok(())
64}
65
66/// Creates a file readable and writable only by the owner.
67///
68/// On Unix, this sets mode 0600 regardless of umask.
69/// On Windows, default ACLs already restrict access to the creating user,
70/// so no explicit hardening is needed.
71fn create_owner_only_file(path: &Path) -> Result<File> {
72    let mut opts = std::fs::OpenOptions::new();
73    opts.write(true).create(true).truncate(true);
74
75    #[cfg(unix)]
76    opts.mode(0o600);
77
78    Ok(opts.open(path)?)
79}
80
81/// Removes a cookie from the given dir.
82pub fn remove_from_disk(dir: &Path) -> Result<()> {
83    remove_file(dir.join(FILE))?;
84
85    tracing::info!("RPC auth cookie removed from disk");
86
87    Ok(())
88}