hive-priv: redact password-bearing lines before logging forgejo admin output
This commit is contained in:
parent
c149963917
commit
7a3614552b
1 changed files with 34 additions and 3 deletions
|
|
@ -1366,6 +1366,21 @@ fn validate_forge_admin_arg(arg: &str) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Redact a line before it hits the (root-readable, but still
|
||||||
|
/// unnecessarily exposed) host journal. `forgejo admin user create
|
||||||
|
/// --random-password` prints the generated password straight to stdout —
|
||||||
|
/// case-insensitive substring match on "password" is deliberately broad
|
||||||
|
/// (not pinned to forgejo's exact wording, which can change across
|
||||||
|
/// versions) so any password-bearing line gets caught rather than relying
|
||||||
|
/// on a phrase that could silently drift out of sync.
|
||||||
|
fn redact_password_line(line: &str) -> std::borrow::Cow<'_, str> {
|
||||||
|
if line.to_ascii_lowercase().contains("password") {
|
||||||
|
std::borrow::Cow::Borrowed("[redacted: line mentions a password]")
|
||||||
|
} else {
|
||||||
|
std::borrow::Cow::Borrowed(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Run `forgejo admin <args>` inside the `hive-forge` container as the
|
/// Run `forgejo admin <args>` inside the `hive-forge` container as the
|
||||||
/// `forgejo` unix user. Requires root (for nsenter into the container's
|
/// `forgejo` unix user. Requires root (for nsenter into the container's
|
||||||
/// namespaces). Returns `(stdout, stderr)`.
|
/// namespaces). Returns `(stdout, stderr)`.
|
||||||
|
|
@ -1394,10 +1409,10 @@ async fn run_forge_admin(args: &[String]) -> Result<(String, String)> {
|
||||||
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
|
let stdout = String::from_utf8_lossy(&out.stdout).into_owned();
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
|
let stderr = String::from_utf8_lossy(&out.stderr).into_owned();
|
||||||
for line in stdout.lines() {
|
for line in stdout.lines() {
|
||||||
tracing::info!(target: "forgejo-admin", "{line}");
|
tracing::info!(target: "forgejo-admin", "{}", redact_password_line(line));
|
||||||
}
|
}
|
||||||
for line in stderr.lines() {
|
for line in stderr.lines() {
|
||||||
tracing::warn!(target: "forgejo-admin", "{line}");
|
tracing::warn!(target: "forgejo-admin", "{}", redact_password_line(line));
|
||||||
}
|
}
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
bail!(
|
bail!(
|
||||||
|
|
@ -1986,10 +2001,26 @@ async fn sync_agent_tmpfiles(agents: &[String]) -> Result<(String, String)> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::write_state_file_nofollow;
|
use super::{redact_password_line, write_state_file_nofollow};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn redacts_lines_mentioning_password_case_insensitively() {
|
||||||
|
assert_eq!(
|
||||||
|
redact_password_line("New password: hunter2"),
|
||||||
|
"[redacted: line mentions a password]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
redact_password_line("PASSWORD=hunter2"),
|
||||||
|
"[redacted: line mentions a password]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
redact_password_line("User \"foo\" was successfully created."),
|
||||||
|
"User \"foo\" was successfully created."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Unique scratch dir per test, no external tempfile dep.
|
/// Unique scratch dir per test, no external tempfile dep.
|
||||||
fn scratch() -> PathBuf {
|
fn scratch() -> PathBuf {
|
||||||
static CTR: AtomicU32 = AtomicU32::new(0);
|
static CTR: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue