Skip to main content

host_identity/sources/
generic.rs

1//! Portable sources: overrides and custom callbacks.
2//!
3//! # Identity scope
4//!
5//! `EnvOverride`, `FileOverride`, and `FnSource` are **caller-scoped**:
6//! whoever sets the variable, writes the file, or supplies the closure
7//! picks the scope. Setting `HOST_IDENTITY` in a host-level systemd
8//! unit gives host scope to every container that inherits the
9//! environment; setting the same variable in a Kubernetes pod spec
10//! (via `valueFrom.fieldRef.fieldPath: metadata.uid`) gives per-pod
11//! scope. The crate cannot tell which you intended — pick
12//! intentionally.
13//!
14//! For per-pod identity sourced from a projected file, prefer the
15//! purpose-built [`crate::sources::KubernetesDownwardApi`] over
16//! wiring `FileOverride` at a downward-API path: the k8s source
17//! labels the probe with `SourceKind::KubernetesDownwardApi` so the
18//! resulting [`crate::HostId`] records the correct provenance.
19//! Reserve `FileOverride` for genuinely caller-pinned paths (a
20//! per-instance volume, an HSM-written file, a provisioning agent's
21//! output).
22//!
23//! See `docs/algorithm.md` → "Identity scope" for the host-vs-
24//! container trap these sources are most often used to work around.
25
26use std::path::{Path, PathBuf};
27
28use crate::error::Error;
29use crate::source::{Probe, Source, SourceKind};
30use crate::sources::util::{normalize, read_capped};
31
32/// Read the identifier from an environment variable.
33///
34/// Skipped (`Ok(None)`) when the variable is unset, empty, or holds only
35/// whitespace.
36#[derive(Debug, Clone)]
37pub struct EnvOverride {
38    var: String,
39}
40
41impl EnvOverride {
42    /// Read from the named environment variable.
43    #[must_use]
44    pub fn new(var: impl Into<String>) -> Self {
45        Self { var: var.into() }
46    }
47}
48
49impl Source for EnvOverride {
50    fn kind(&self) -> SourceKind {
51        SourceKind::EnvOverride
52    }
53
54    fn probe(&self) -> Result<Option<Probe>, Error> {
55        match std::env::var(&self.var) {
56            Ok(value) => Ok(normalize(&value).map(|v| Probe::new(SourceKind::EnvOverride, v))),
57            Err(_) => Ok(None),
58        }
59    }
60}
61
62/// Read the identifier from a single-line file.
63///
64/// Skipped when the file does not exist, is empty, or contains only
65/// whitespace. Returns an I/O error for permission problems or other
66/// unexpected read failures.
67#[derive(Debug, Clone)]
68pub struct FileOverride {
69    path: PathBuf,
70}
71
72impl FileOverride {
73    /// Read from the given file path.
74    #[must_use]
75    pub fn new(path: impl Into<PathBuf>) -> Self {
76        Self { path: path.into() }
77    }
78
79    /// The configured path.
80    #[must_use]
81    pub fn path(&self) -> &Path {
82        &self.path
83    }
84}
85
86impl Source for FileOverride {
87    fn kind(&self) -> SourceKind {
88        SourceKind::FileOverride
89    }
90
91    fn probe(&self) -> Result<Option<Probe>, Error> {
92        match read_capped(&self.path) {
93            Ok(content) => Ok(normalize(&content).map(|v| Probe::new(SourceKind::FileOverride, v))),
94            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
95            Err(source) => Err(Error::Io {
96                source_kind: SourceKind::FileOverride,
97                path: self.path.clone(),
98                source,
99            }),
100        }
101    }
102}
103
104/// A [`Source`] backed by a user-supplied closure.
105///
106/// Use to plug in arbitrary identity providers (cloud metadata, Kubernetes
107/// downward API, hardware security modules, …) without implementing the
108/// trait manually.
109pub struct FnSource<F> {
110    kind: SourceKind,
111    f: F,
112}
113
114impl<F> FnSource<F>
115where
116    F: Fn() -> Result<Option<String>, Error> + Send + Sync,
117{
118    /// Build a source from a closure that returns an optional raw identifier.
119    pub fn new(kind: SourceKind, f: F) -> Self {
120        Self { kind, f }
121    }
122}
123
124impl<F> std::fmt::Debug for FnSource<F> {
125    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126        f.debug_struct("FnSource")
127            .field("kind", &self.kind)
128            .finish_non_exhaustive()
129    }
130}
131
132impl<F> Source for FnSource<F>
133where
134    F: Fn() -> Result<Option<String>, Error> + Send + Sync,
135{
136    fn kind(&self) -> SourceKind {
137        self.kind
138    }
139
140    fn probe(&self) -> Result<Option<Probe>, Error> {
141        Ok((self.f)()?
142            .as_deref()
143            .and_then(normalize)
144            .map(|v| Probe::new(self.kind, v)))
145    }
146}