Files

183 lines
6.8 KiB
Rust

use std::collections::BTreeMap;
use crank_config::{ConfigSource, ProcessKind, parse_process};
const TEST_MASTER_KEY: &str = "test-master-key-00000000000000000000000000000000";
const CANARY_ONE_MASTER_KEY: &str = "CANARY_ONE-000000000000000000000000000000000";
const CANARY_TWO_MASTER_KEY: &str = "CANARY_TWO-000000000000000000000000000000000";
fn config(secret: &str) -> crank_config::EffectiveConfig {
let vars = [
("CRANK_MASTER_KEY", secret),
("CRANK_SESSION_SECRET", secret),
("CRANK_PASSWORD_PEPPER", secret),
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"),
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", secret),
]
.into_iter()
.map(|(key, value)| (key.to_owned(), value.to_owned()))
.collect::<BTreeMap<_, _>>();
parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap()
}
#[test]
fn secrets_are_absent_from_debug_display_and_fingerprint() {
let first = config(CANARY_ONE_MASTER_KEY);
let second = config(CANARY_TWO_MASTER_KEY);
let rendered = format!("{first:?}");
assert!(!rendered.contains("CANARY_ONE"));
assert_eq!(first.fingerprint(), second.fingerprint());
assert_eq!(first.fingerprint().len(), 64);
assert!(
first
.fingerprint()
.bytes()
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
);
}
#[test]
fn effective_semantics_not_input_spelling_drive_fingerprint() {
let mut canonical = [
("CRANK_MASTER_KEY", TEST_MASTER_KEY),
("CRANK_SESSION_SECRET", "session"),
("CRANK_PASSWORD_PEPPER", "pepper"),
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"),
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", "password"),
("CRANK_DEMO_SEED", "true"),
]
.into_iter()
.map(|(key, value)| (key.to_owned(), value.to_owned()))
.collect::<BTreeMap<_, _>>();
let mut compatibility = canonical.clone();
compatibility.insert("CRANK_DEMO_SEED".into(), "yes".into());
let canonical_config = parse_process(
ProcessKind::AdminApi,
ConfigSource::from_utf8(canonical.clone()),
)
.unwrap();
let compatibility_config = parse_process(
ProcessKind::AdminApi,
ConfigSource::from_utf8(compatibility),
)
.unwrap();
assert_eq!(
canonical_config.fingerprint(),
compatibility_config.fingerprint()
);
assert_eq!(compatibility_config.deprecations().len(), 1);
canonical.insert("CRANK_SESSION_TTL_HOURS".into(), "48".into());
let changed = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(canonical)).unwrap();
assert_ne!(changed.fingerprint(), compatibility_config.fingerprint());
}
#[test]
fn diagnostics_are_bounded_json_and_never_echo_secret_canaries() {
let canary = "CANARY_SECRET_VALUE-0000000000000000000000";
let vars = [
("CRANK_MASTER_KEY", canary),
("CRANK_SESSION_SECRET", canary),
("CRANK_PASSWORD_PEPPER", canary),
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"),
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", canary),
(
"CRANK_DATABASE_URL",
"postgres://owner:CANARY_SECRET_VALUE@db/crank",
),
("POSTGRES_PASSWORD", canary),
("CRANK_CACHE_BACKEND", "memory"),
("CRANK_CACHE_URL", "redis://:CANARY_SECRET_VALUE@cache:6379"),
(
"CRANK_SENTRY_DSN",
"https://CANARY_SECRET_VALUE@sentry.test/1",
),
("CRANK_METRICS_BEARER_TOKEN", canary),
(
"OTEL_EXPORTER_OTLP_HEADERS",
"authorization=CANARY_SECRET_VALUE",
),
]
.into_iter()
.map(|(key, value)| (key.to_owned(), value.to_owned()))
.collect::<BTreeMap<_, _>>();
let error = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap_err();
let display = error.to_string();
let json = error.to_json();
assert!(json.len() <= 65_536);
assert!(serde_json::from_str::<serde_json::Value>(&json).is_ok());
assert!(!display.contains(canary));
assert!(!json.contains(canary));
assert!(error.diagnostics().len() <= 100);
}
#[test]
fn public_projection_debug_omits_urls_hosts_paths_and_identity_values() {
let mut vars = [
("CRANK_MASTER_KEY", TEST_MASTER_KEY),
("CRANK_SESSION_SECRET", "session"),
("CRANK_PASSWORD_PEPPER", "pepper"),
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@CANARY.test"),
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", "password"),
("CRANK_STORAGE_ROOT", "/CANARY/private/storage"),
("POSTGRES_HOST", "CANARY-db.internal"),
("CRANK_OUTBOUND_ALLOWED_HOSTS", "CANARY-api.internal"),
]
.into_iter()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect::<BTreeMap<_, _>>();
vars.insert(
"CRANK_BASE_URL".into(),
"https://CANARY.example.test".into(),
);
let config = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(vars)).unwrap();
let rendered = format!("{:?}", config.admin().unwrap());
assert!(!rendered.contains("CANARY"), "{rendered}");
}
#[test]
fn normalized_database_and_admin_default_urls_drive_fingerprint() {
let base = [
("CRANK_MASTER_KEY", TEST_MASTER_KEY),
("CRANK_SESSION_SECRET", "session"),
("CRANK_PASSWORD_PEPPER", "pepper"),
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"),
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", "password"),
]
.into_iter()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect::<BTreeMap<_, _>>();
let implicit =
parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(base.clone())).unwrap();
let mut explicit = base.clone();
explicit.insert("CRANK_BASE_URL".into(), "http://localhost:3000".into());
let explicit = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(explicit)).unwrap();
assert_eq!(implicit.fingerprint(), explicit.fingerprint());
let mut url = base;
url.insert(
"CRANK_DATABASE_URL".into(),
"postgres://crank:rotated@postgres/crank".into(),
);
let url = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(url)).unwrap();
assert_eq!(implicit.fingerprint(), url.fingerprint());
let tls = [
("CRANK_MASTER_KEY", TEST_MASTER_KEY),
("CRANK_SESSION_SECRET", "session"),
("CRANK_PASSWORD_PEPPER", "pepper"),
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"),
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", "password"),
(
"CRANK_DATABASE_URL",
"postgres://crank:rotated@postgres/crank?sslmode=require",
),
]
.into_iter()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect::<BTreeMap<_, _>>();
let tls = parse_process(ProcessKind::AdminApi, ConfigSource::from_utf8(tls)).unwrap();
assert_ne!(implicit.fingerprint(), tls.fingerprint());
}