fix(bootstrap): token persists on disk with weak file permissions #59
Some checks failed
ci/woodpecker/pr/server-audit Pipeline was successful
ci/woodpecker/pr/server-vet Pipeline failed
ci/woodpecker/pr/server-lint Pipeline failed
ci/woodpecker/pr/server-test Pipeline failed

This commit is contained in:
CleverWild
2026-06-18 20:59:24 +02:00
parent 32ceb27d77
commit 85f27d8f09

View File

@@ -6,22 +6,36 @@ use diesel_async::RunQueryDsl;
use kameo::{Actor, messages}; use kameo::{Actor, messages};
use rand::{RngExt, distr::Alphanumeric, rngs::SysRng}; use rand::{RngExt, distr::Alphanumeric, rngs::SysRng};
use rand_core::UnwrapErr; use rand_core::UnwrapErr;
use std::path::{Path, PathBuf};
use subtle::ConstantTimeEq as _; use subtle::ConstantTimeEq as _;
use thiserror::Error; use thiserror::Error;
use tracing::warn;
use zeroize::Zeroizing; use zeroize::Zeroizing;
const TOKEN_LENGTH: usize = 64; const TOKEN_LENGTH: usize = 64;
pub async fn generate_token() -> Result<String, std::io::Error> { async fn write_token_file(path: &Path, content: &str) -> Result<(), std::io::Error> {
let token = UnwrapErr(SysRng).sample_iter(Alphanumeric).take(TOKEN_LENGTH).fold( tokio::fs::write(path, content.as_bytes()).await?;
String::default(),
|mut accum, char| { #[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt as _;
tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)).await?;
}
Ok(())
}
async fn generate_token(path: &Path) -> Result<String, std::io::Error> {
let token = UnwrapErr(SysRng)
.sample_iter(Alphanumeric)
.take(TOKEN_LENGTH)
.fold(String::default(), |mut accum, char| {
accum += char.to_string().as_str(); accum += char.to_string().as_str();
accum accum
}, });
);
tokio::fs::write(home_path()?.join(BOOTSTRAP_PATH), token.as_str()).await?; write_token_file(path, &token).await?;
Ok(token) Ok(token)
} }
@@ -41,6 +55,7 @@ pub enum Error {
#[derive(Actor)] #[derive(Actor)]
pub struct Bootstrapper { pub struct Bootstrapper {
token: Option<Zeroizing<String>>, token: Option<Zeroizing<String>>,
token_path: Option<PathBuf>,
} }
impl Bootstrapper { impl Bootstrapper {
@@ -54,14 +69,15 @@ impl Bootstrapper {
.await? .await?
}; };
let token = if row_count == 0 { let (token, token_path) = if row_count == 0 {
let token = generate_token().await?; let path = home_path()?.join(BOOTSTRAP_PATH);
Some(Zeroizing::new(token)) let token = generate_token(&path).await?;
(Some(Zeroizing::new(token)), Some(path))
} else { } else {
None (None, None)
}; };
Ok(Self { token }) Ok(Self { token, token_path })
} }
} }
@@ -80,9 +96,14 @@ impl Bootstrapper {
#[messages] #[messages]
impl Bootstrapper { impl Bootstrapper {
#[message] #[message]
pub fn consume_token(&mut self, token: Zeroizing<String>) -> bool { pub async fn consume_token(&mut self, token: Zeroizing<String>) -> bool {
if self.is_correct_token(&token) { if self.is_correct_token(&token) {
self.token = None; self.token = None;
if let Some(path) = self.token_path.take() {
if let Err(e) = tokio::fs::remove_file(&path).await {
warn!(error = ?e, path = ?path, "Failed to delete bootstrap token file after consumption");
}
}
true true
} else { } else {
false false