diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-07-18 00:08:39 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-07-18 01:21:42 -0400 |
| commit | dc240ca270f86552e999c81d864b4cb0c687a88e (patch) | |
| tree | 191b55b0213219d9a3499afc8de8a84d2ef9816d /src/cli.rs | |
| parent | 9ad439d16e797d04804b5d80706fd0e86041b161 (diff) | |
Add a `--umask` option to determine what permissions new files/databases get.
The new `--umask` option takes one of three values:
* `--umask masked`, the default, takes the inherited umask and forces o+rwx on.
* `--umask inherit` takes the inherited umask as-is.
* `--umask OCTAL` sets the umask to exactly `OCTAL` and is broadly equivalent to `umask OCTAL && pilcrow --umask inherit`.
This fell out of a conversation with @wlonk, who is working on notifications. Since notifications may require [VAPID] keys, the server will need a way to store those keys. That would generally be "in the pilcrow database," which lead me to the observation that Pilcrow creates that database as world-readable by default. "World-readable" and "encryption/signing keys" are not things that belong in the same sentence.
[VAPID]: https://datatracker.ietf.org/doc/html/rfc8292
The most "obvious" solution would be to set the permissions used for the sqlite database when it's created. That's harder than it sounds: sqlite has no built-in facility for doing this. The closest thing that exists today is the [`modeof`] query parameter, which copies the permissions (and ownership) from some other file. We also can't reliably set the permissions ourselves, as sqlite may - depending on build options and configuration - [create multiple files][wal].
[`modeof`]: https://www.sqlite.org/uri.html
[wal]: https://www.sqlite.org/wal.html
Using `umask` is a whole-process solution to this. As Pilcrow doesn't attempt to create other files, there's little issue with doing it this way, but this is a design risk for future work if it creates files that are _intended_ to be readable by more than just the Pilcrow daemon user.
Diffstat (limited to 'src/cli.rs')
| -rw-r--r-- | src/cli.rs | 17 |
1 files changed, 15 insertions, 2 deletions
@@ -3,7 +3,7 @@ //! This module supports running `pilcrow` as a freestanding program, via the //! [`Args`] struct. -use std::{future, io}; +use std::{future, io, str}; use axum::{ http::header, @@ -14,7 +14,11 @@ use clap::{CommandFactory, Parser}; use sqlx::sqlite::SqlitePool; use tokio::net; -use crate::{app::App, clock, db, routes}; +use crate::{ + app::App, + clock, db, routes, + umask::{self, Umask}, +}; /// Command-line entry point for running the `pilcrow` server. /// @@ -51,6 +55,10 @@ pub struct Args { #[arg(short, long, env, default_value_t = 64209)] port: u16, + /// The umask pilcrow should run under (octal, `inherit`, or `masked`) + #[arg(short = 'U', long, default_value_t = Umask::Masked)] + umask: Umask, + /// Sqlite URL or path for the `pilcrow` database #[arg(short, long, env, default_value = "sqlite://pilcrow.db")] database_url: String, @@ -66,6 +74,7 @@ impl Args { /// /// This will perform the following tasks: /// + /// * Set the process' umask (as specified by `--umask`). /// * Migrate the `pilcrow` database (at `--database-url`). /// * Start an HTTP server (on the interface and port controlled by /// `--address` and `--port`). @@ -78,6 +87,8 @@ impl Args { /// prematurely. The specific [`Error`] variant will expose the cause /// of the failure. pub async fn run(self) -> Result<(), Error> { + self.umask.set(); + let pool = self.pool().await?; let app = App::from(pool); @@ -135,4 +146,6 @@ pub enum Error { Io(#[from] io::Error), /// Failure due to a database initialization error. See [`db::Error`]. Database(#[from] db::Error), + /// Failure due to invalid umask-related options. + Umask(#[from] umask::Error), } |
