179 lines
6.5 KiB
Rust
179 lines
6.5 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use crank_config::{ConfigSource, ProcessKind, parse_process};
|
|
|
|
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");
|
|
let second = config("CANARY_TWO");
|
|
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", "master"),
|
|
("CRANK_SESSION_SECRET", "session"),
|
|
("CRANK_PASSWORD_PEPPER", "pepper"),
|
|
("CRANK_BOOTSTRAP_ADMIN_EMAIL", "owner@example.test"),
|
|
("CRANK_BOOTSTRAP_ADMIN_PASSWORD", "password"),
|
|
("CRANK_TRUST_FORWARDED_HEADERS", "true"),
|
|
]
|
|
.into_iter()
|
|
.map(|(key, value)| (key.to_owned(), value.to_owned()))
|
|
.collect::<BTreeMap<_, _>>();
|
|
let mut compatibility = canonical.clone();
|
|
compatibility.insert("CRANK_TRUST_FORWARDED_HEADERS".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";
|
|
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", "master"),
|
|
("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", "master"),
|
|
("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", "master"),
|
|
("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());
|
|
}
|