use crate::error::FaucetError;
use std::sync::OnceLock;
tokio::task_local! {
static SCOPED: bool;
}
fn unscoped_denied() -> &'static OnceLock<()> {
static DENIED: OnceLock<()> = OnceLock::new();
&DENIED
}
pub fn deny_unscoped_host_credentials() -> bool {
unscoped_denied().set(()).is_ok()
}
pub fn unscoped_host_credentials_denied() -> bool {
unscoped_denied().get().is_some()
}
pub async fn with_host_credentials<F: std::future::Future>(allowed: bool, fut: F) -> F::Output {
SCOPED.scope(allowed, fut).await
}
pub fn scoped_host_credentials() -> Option<bool> {
SCOPED.try_with(|allowed| *allowed).ok()
}
pub fn host_credentials_allowed() -> bool {
scoped_host_credentials().unwrap_or_else(|| !unscoped_host_credentials_denied())
}
pub fn require_host_credentials(connector: &str, field: &str) -> Result<(), FaucetError> {
if host_credentials_allowed() {
return Ok(());
}
Err(FaucetError::Config(format!(
"{connector}: `{field}` authenticates with the server's own identity (its environment, \
instance or workload role, or local credential files), which this config may not use; \
name credentials in the config, or start `faucet serve` with \
`--allow-host-credentials` (never available to a tenant)"
)))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn the_scope_decides_and_the_innermost_scope_wins() {
assert_eq!(scoped_host_credentials(), None);
with_host_credentials(false, async {
assert!(!host_credentials_allowed());
let err = require_host_credentials("s3", "credentials").unwrap_err();
let text = err.to_string();
assert!(text.contains("s3: `credentials`"), "{text}");
assert!(text.contains("--allow-host-credentials"), "{text}");
with_host_credentials(true, async {
assert!(host_credentials_allowed());
assert!(require_host_credentials("s3", "credentials").is_ok());
})
.await;
assert_eq!(scoped_host_credentials(), Some(false));
})
.await;
}
#[tokio::test]
async fn a_reference_policy_scope_carries_its_answer() {
use crate::resolve::ReferencePolicy;
use std::sync::Arc;
let deny = Arc::new(ReferencePolicy::deny_all());
crate::http::with_reference_policy(deny.clone(), async {
assert!(!host_credentials_allowed());
let spawned = tokio::spawn(crate::http::inherit_reference_policy(async {
host_credentials_allowed()
}));
assert!(!spawned.await.unwrap(), "a spawned task keeps the scope");
with_host_credentials(true, async {
let spawned = tokio::spawn(crate::http::inherit_reference_policy(async {
(
host_credentials_allowed(),
crate::http::current_reference_policy().is_some(),
)
}));
assert_eq!(spawned.await.unwrap(), (true, true));
})
.await;
})
.await;
let open = Arc::new(ReferencePolicy::deny_all().with_host_credentials(true));
crate::http::with_reference_policy(open, async {
assert!(host_credentials_allowed());
})
.await;
let only_grant = tokio::spawn(with_host_credentials(
false,
crate::http::inherit_reference_policy(async { host_credentials_allowed() }),
));
assert!(!only_grant.await.unwrap());
}
#[test]
fn denying_unscoped_tasks_is_process_wide_and_first_wins() {
assert!(!unscoped_host_credentials_denied());
assert!(host_credentials_allowed());
assert!(require_host_credentials("kinesis", "credentials").is_ok());
assert!(deny_unscoped_host_credentials());
assert!(!deny_unscoped_host_credentials());
assert!(unscoped_host_credentials_denied());
assert!(!host_credentials_allowed());
assert!(require_host_credentials("kinesis", "credentials").is_err());
}
}