Each task's tests were run against a reference at its end state; the end states were replayed from master in order with the gate at each step (650 to 762 tests); each skeleton compiles against its tests and fails them. The reference is kept off this machine. Lessons T27 (every wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
84 lines
2.7 KiB
Rust
84 lines
2.7 KiB
Rust
//! The `SecretStore`: a secret from a systemd credential, an environment variable, or an owner-only
|
|
//! file (M4a spec, section 4; the brief after P15). A `Secret` cannot be printed.
|
|
|
|
use std::ffi::OsString;
|
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use zeroize::Zeroizing;
|
|
|
|
use crate::config::SecretSource;
|
|
|
|
pub const RUNBOOK: &str = "see docs/runbook.md#secret-unavailable";
|
|
pub const RUNBOOK_FILE: &str = "see docs/runbook.md#secret-in-a-file";
|
|
|
|
/// A secret's text. No `Display`; `Debug` shows nothing of it; wiped when dropped.
|
|
pub struct Secret(Zeroizing<String>);
|
|
|
|
impl Secret {
|
|
pub fn new(text: String) -> Secret {
|
|
Secret(Zeroizing::new(text))
|
|
}
|
|
|
|
/// The text, for the one place that must send it.
|
|
pub fn expose(&self) -> &str {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for Secret {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str("Secret(…)")
|
|
}
|
|
}
|
|
|
|
/// A loaded secret, and the warning to print for it, if any.
|
|
#[derive(Debug)]
|
|
pub struct Loaded {
|
|
pub secret: Secret,
|
|
pub warning: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SecretError {
|
|
pub name: String,
|
|
pub why: String,
|
|
}
|
|
|
|
impl std::fmt::Display for SecretError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "secret {}: {}\n{RUNBOOK}", self.name, self.why)
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for SecretError {}
|
|
|
|
/// Load secret `name` from `source`. `env` reads an environment variable (in `gatewayd`,
|
|
/// `std::env::var_os`); tests pass their own.
|
|
pub fn load(
|
|
name: &str,
|
|
source: &SecretSource,
|
|
env: &dyn Fn(&str) -> Option<OsString>,
|
|
) -> Result<Loaded, SecretError> {
|
|
// By source (spec section 4). Credential: read $CREDENTIALS_DIRECTORY/<name> (through `env`,
|
|
// not std::env); an unset variable, or a file that cannot be read, is an error. Env: the
|
|
// variable, UTF-8; unset is an error. File: `check_file` first, then read it, and set `warning`
|
|
// to the exact text in the task. Every value goes through `value`. Every error is a
|
|
// `SecretError` naming the secret, never the value.
|
|
todo!()
|
|
}
|
|
|
|
/// An owner-only regular file, not a link.
|
|
fn check_file(path: &Path) -> Result<(), String> {
|
|
// In this order, each its own error: not absolute; `symlink_metadata` fails; a symbolic link;
|
|
// not a regular file; owner uid differs from the uid of /proc/self; mode & 0o077 != 0.
|
|
todo!()
|
|
}
|
|
|
|
/// The text without one trailing newline; not empty; UTF-8.
|
|
fn value(bytes: Vec<u8>) -> Result<Secret, String> {
|
|
// UTF-8 (else an error), one trailing newline removed, not empty. Keep it in `Zeroizing`
|
|
// throughout.
|
|
todo!()
|
|
}
|