build.rs 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. //! Construction and cross-field validation of [`Config`] from raw input.
  2. //!
  3. //! Kept in a submodule so the root `config.rs` stays focused on the public
  4. //! types and loader surface.
  5. use std::{net::SocketAddr, path::Path, str::FromStr};
  6. use secrecy::{ExposeSecret, SecretString};
  7. use super::{
  8. Config, ConfigError, TlsConfig,
  9. db::DbConfig,
  10. policy::FailurePolicy,
  11. raw::{RawConfig, RawTls},
  12. read_secret_file,
  13. redis::RedisConfig,
  14. scope::{ScopeDefinition, TokenExchangeRule},
  15. types::{Argon2Params, RateLimitConfig, TtlConfig},
  16. };
  17. use crate::keys::{KeyId, KeyRing, KeyRingError};
  18. const DEFAULT_BIND_ADDR: &str = "0.0.0.0:8080";
  19. const DEFAULT_HEALTH_ADDR: &str = "0.0.0.0:8081";
  20. const DEFAULT_KEY_ID: &str = "v1";
  21. impl Config {
  22. pub(crate) fn build(raw: RawConfig) -> Result<Self, ConfigError> {
  23. let environment = raw.environment;
  24. let issuer = parse_issuer(raw.issuer.as_deref())?;
  25. let bind_addr = parse_socket_addr(raw.bind_addr.as_deref(), DEFAULT_BIND_ADDR, |value| {
  26. ConfigError::InvalidBindAddr { value }
  27. })?;
  28. let health_addr =
  29. parse_socket_addr(raw.health_addr.as_deref(), DEFAULT_HEALTH_ADDR, |value| {
  30. ConfigError::InvalidHealthAddr { value }
  31. })?;
  32. let tls = TlsConfig::from_raw(&raw.tls)?;
  33. let keys = build_key_ring(&raw)?;
  34. let database = DbConfig::from_raw(raw.database, environment)?;
  35. let redis = RedisConfig::from_raw(raw.redis)?;
  36. let failure_policy = FailurePolicy::from_raw(raw.failure_policy)?;
  37. validate_timeout_ladder(&database, &failure_policy)?;
  38. let ttl = TtlConfig::from_raw(raw.ttl)?;
  39. let argon2 = Argon2Params::from_raw(raw.argon2)?;
  40. let rate_limit = RateLimitConfig::from_raw(raw.rate_limit)?;
  41. let scope_registry = raw
  42. .scope_registry
  43. .into_iter()
  44. .map(ScopeDefinition::from_raw)
  45. .collect::<Result<Vec<_>, _>>()?;
  46. let token_exchange_allowlist = raw
  47. .token_exchange_allowlist
  48. .into_iter()
  49. .map(TokenExchangeRule::from_raw)
  50. .collect::<Result<Vec<_>, _>>()?;
  51. Ok(Self {
  52. issuer,
  53. bind_addr,
  54. health_addr,
  55. environment,
  56. tls,
  57. database,
  58. redis,
  59. keys,
  60. failure_policy,
  61. ttl,
  62. argon2,
  63. rate_limit,
  64. cookie_domain: raw.cookie_domain,
  65. cookie_secure: raw.cookie_secure.unwrap_or(true),
  66. allow_open_registration: raw.allow_open_registration,
  67. pii_dual_write: raw.pii_dual_write,
  68. dcr_redirect_policy: raw.dcr_redirect_policy,
  69. cors_allowed_origins: raw.cors_allowed_origins,
  70. scope_registry,
  71. allow_token_exchange: raw.allow_token_exchange,
  72. token_exchange_allowlist,
  73. })
  74. }
  75. }
  76. impl TlsConfig {
  77. fn from_raw(raw: &RawTls) -> Result<Self, ConfigError> {
  78. Ok(Self {
  79. cert_path: raw.cert_path.clone().ok_or(ConfigError::MissingTlsCert)?,
  80. key_path: raw.key_path.clone().ok_or(ConfigError::MissingTlsKey)?,
  81. client_ca_path: raw.client_ca_path.clone(),
  82. })
  83. }
  84. }
  85. fn parse_issuer(raw: Option<&str>) -> Result<String, ConfigError> {
  86. let issuer = raw
  87. .map(str::trim)
  88. .filter(|value| !value.is_empty())
  89. .ok_or(ConfigError::MissingIssuer)?;
  90. if !issuer.starts_with("https://") || issuer.ends_with('/') {
  91. return Err(ConfigError::InsecureIssuer {
  92. issuer: issuer.to_owned(),
  93. });
  94. }
  95. let authority = issuer.get("https://".len()..).unwrap_or_default();
  96. if authority.is_empty() || authority.contains(char::is_whitespace) {
  97. return Err(ConfigError::InsecureIssuer {
  98. issuer: issuer.to_owned(),
  99. });
  100. }
  101. Ok(issuer.to_owned())
  102. }
  103. fn parse_socket_addr<F>(
  104. raw: Option<&str>,
  105. default: &str,
  106. invalid: F,
  107. ) -> Result<SocketAddr, ConfigError>
  108. where
  109. F: FnOnce(String) -> ConfigError,
  110. {
  111. let value = raw
  112. .map(str::trim)
  113. .filter(|value| !value.is_empty())
  114. .unwrap_or(default);
  115. SocketAddr::from_str(value).map_err(|_| invalid(value.to_owned()))
  116. }
  117. /// Master keys must carry at least 256 bits of material.
  118. const MIN_KEY_BYTES: usize = 32;
  119. /// Reject a resolved key shorter than [`MIN_KEY_BYTES`]. `None` (no key
  120. /// configured) passes through: the key-ring chain builder reports a missing
  121. /// active key as its own error.
  122. fn require_min_key_bytes(
  123. secret: Option<SecretString>,
  124. key: &'static str,
  125. ) -> Result<Option<SecretString>, ConfigError> {
  126. match secret {
  127. Some(secret) if secret.expose_secret().len() < MIN_KEY_BYTES => {
  128. Err(ConfigError::WeakKeyMaterial { key })
  129. }
  130. other => Ok(other),
  131. }
  132. }
  133. fn build_key_ring(raw: &RawConfig) -> Result<KeyRing, ConfigError> {
  134. let pepper = require_min_key_bytes(
  135. resolve_secret(
  136. raw.token_pepper.as_deref(),
  137. raw.token_pepper_file.as_deref(),
  138. )?,
  139. "token_pepper",
  140. )?;
  141. let totp = require_min_key_bytes(
  142. resolve_secret(
  143. raw.totp_encryption_key.as_deref(),
  144. raw.totp_encryption_key_file.as_deref(),
  145. )?,
  146. "totp_encryption_key",
  147. )?;
  148. let pii = require_min_key_bytes(
  149. non_blank(resolve_secret(
  150. raw.pii_encryption_key.as_deref(),
  151. raw.pii_encryption_key_file.as_deref(),
  152. )?),
  153. "pii_encryption_key",
  154. )?;
  155. let totp_previous = require_min_key_bytes(
  156. non_blank(resolve_secret(
  157. raw.totp_previous_key.as_deref(),
  158. raw.totp_previous_key_file.as_deref(),
  159. )?),
  160. "totp_previous_key",
  161. )?;
  162. let pii_previous = require_min_key_bytes(
  163. non_blank(resolve_secret(
  164. raw.pii_previous_key.as_deref(),
  165. raw.pii_previous_key_file.as_deref(),
  166. )?),
  167. "pii_previous_key",
  168. )?;
  169. let pepper_id = key_id_or_default(raw.token_pepper_id.as_deref())?;
  170. let totp_id = key_id_or_default(raw.totp_key_id.as_deref())?;
  171. let pii_id = key_id_or_default(raw.pii_key_id.as_deref())?;
  172. let peppers = pepper
  173. .map(|secret| vec![(pepper_id, secret)])
  174. .unwrap_or_default();
  175. let totp_keys = active_first_chain(
  176. totp.map(|secret| (totp_id, secret)),
  177. raw.totp_previous_key_id.as_deref(),
  178. totp_previous,
  179. KeyRingError::MissingTotpKey,
  180. )?;
  181. let pii_keys = active_first_chain(
  182. pii.map(|secret| (pii_id, secret)),
  183. raw.pii_previous_key_id.as_deref(),
  184. pii_previous,
  185. KeyRingError::MissingPiiKey,
  186. )?;
  187. KeyRing::from_parts(peppers, totp_keys, pii_keys).map_err(ConfigError::from)
  188. }
  189. /// Build an active-first chain from the active key plus its retained previous
  190. /// key. A missing active key with no previous key yields an empty chain (the
  191. /// caller maps it to `missing_active`); a previous key without its active key
  192. /// (R2) or without an explicit distinct id (R1) is rejected here so a retained
  193. /// key can never silently become active.
  194. fn active_first_chain(
  195. active: Option<(KeyId, SecretString)>,
  196. previous_id_raw: Option<&str>,
  197. previous: Option<SecretString>,
  198. missing_active: KeyRingError,
  199. ) -> Result<Vec<(KeyId, SecretString)>, ConfigError> {
  200. match (active, previous) {
  201. (None, None) => Ok(Vec::new()),
  202. (Some(pair), None) => Ok(vec![pair]),
  203. (None, Some(_)) => Err(ConfigError::KeyRing(missing_active)),
  204. (Some(active_pair), Some(previous_secret)) => {
  205. let id_raw = previous_id_raw
  206. .map(str::trim)
  207. .filter(|value| !value.is_empty())
  208. .ok_or(ConfigError::MissingPreviousKeyId)?;
  209. let id = validate_key_id(id_raw)?;
  210. if id == active_pair.0 {
  211. return Err(ConfigError::MissingPreviousKeyId);
  212. }
  213. Ok(vec![active_pair, (id, previous_secret)])
  214. }
  215. }
  216. }
  217. /// Treat empty/whitespace-only resolved material as absent. The inline path in
  218. /// `resolve_secret` already drops blanks, but a mounted file can be zero-byte
  219. /// or whitespace-only; such a value must not become a chain element.
  220. fn non_blank(secret: Option<SecretString>) -> Option<SecretString> {
  221. secret.filter(|value| !value.expose_secret().trim().is_empty())
  222. }
  223. fn resolve_secret(
  224. inline: Option<&str>,
  225. file: Option<&Path>,
  226. ) -> Result<Option<SecretString>, ConfigError> {
  227. match inline.map(str::trim).filter(|value| !value.is_empty()) {
  228. Some(value) => Ok(Some(SecretString::from(value.to_owned()))),
  229. None => read_secret_file(file),
  230. }
  231. }
  232. fn key_id_or_default(raw: Option<&str>) -> Result<KeyId, ConfigError> {
  233. match raw.map(str::trim).filter(|value| !value.is_empty()) {
  234. Some(value) => validate_key_id(value),
  235. None => Ok(KeyId::new(DEFAULT_KEY_ID)),
  236. }
  237. }
  238. /// Reject a configured key id outside `[A-Za-z0-9_-]+`: `.` would collide
  239. /// with the `<kid>.<digest>` storage format, and empty/whitespace values are
  240. /// never valid ids (absent ids fall back to the default above).
  241. fn validate_key_id(value: &str) -> Result<KeyId, ConfigError> {
  242. if crate::keys::is_valid_key_id(value) {
  243. Ok(KeyId::new(value))
  244. } else {
  245. Err(ConfigError::InvalidKeyId {
  246. id: value.to_owned(),
  247. })
  248. }
  249. }
  250. fn validate_timeout_ladder(db: &DbConfig, policy: &FailurePolicy) -> Result<(), ConfigError> {
  251. let lock = db.lock_timeout;
  252. let statement = db.statement_timeout;
  253. let acquire = db.acquire_timeout;
  254. let request = policy.request_timeout;
  255. if lock < statement && statement <= acquire && acquire < request {
  256. Ok(())
  257. } else {
  258. Err(ConfigError::InvalidTimeoutLadder)
  259. }
  260. }