mod digest;
pub(crate) mod gssapi;
#[cfg(feature = "kms")]
pub(crate) mod kms;
pub mod sasl;
pub mod user;
use std::sync::Arc;
use crate::Result;
#[derive(Clone, Default)]
pub(crate) struct KerberosCredentials {
pub principal: Option<String>,
pub keytab: Option<String>,
pub cache: Option<String>,
}
impl KerberosCredentials {
pub(crate) fn new(
principal: Option<String>,
keytab: Option<String>,
cache: Option<String>,
) -> Result<Option<Self>> {
if principal.is_none() && keytab.is_none() && cache.is_none() {
return Ok(None);
}
if principal.is_none() {
return Err(crate::HdfsError::InvalidArgument(
"Kerberos principal is required when keytab or cache credentials are supplied"
.to_string(),
));
}
if principal.as_deref().is_some_and(str::is_empty) {
return Err(crate::HdfsError::InvalidArgument(
"Kerberos principal must not be empty".to_string(),
));
}
let cache = match (&keytab, cache) {
(Some(_), None) => Some(format!("MEMORY:hdfs-native-{}", uuid::Uuid::new_v4())),
(_, cache) => cache,
};
Ok(Some(Self {
principal,
keytab,
cache,
}))
}
}
#[derive(Debug)]
pub(crate) struct ClientAuth {
pub(crate) kerberos: KerberosCredentials,
}
impl ClientAuth {
pub(crate) fn new(kerberos: KerberosCredentials) -> Arc<Self> {
Arc::new(Self { kerberos })
}
pub(crate) fn credentials(&self) -> Option<&KerberosCredentials> {
Some(&self.kerberos)
}
}
impl std::fmt::Debug for KerberosCredentials {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("KerberosCredentials")
.field("principal", &self.principal)
.field("keytab", &self.keytab.as_ref().map(|_| "[REDACTED]"))
.field("cache", &self.cache.as_ref().map(|_| "[REDACTED]"))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::KerberosCredentials;
#[test]
fn kerberos_credentials_are_composable_and_redacted() {
let credentials = KerberosCredentials::new(
Some("alice@EXAMPLE.COM".to_string()),
Some("/run/secrets/alice.keytab".to_string()),
Some("FILE:/run/krb5/alice.ccache".to_string()),
)
.unwrap()
.unwrap();
let debug = format!("{credentials:?}");
assert!(debug.contains("alice@EXAMPLE.COM"));
assert!(!debug.contains("/run/secrets/alice.keytab"));
assert!(!debug.contains("/run/krb5/alice.ccache"));
let resolved = credentials;
assert_eq!(
resolved.keytab.as_deref(),
Some("/run/secrets/alice.keytab")
);
assert_eq!(
resolved.cache.as_deref(),
Some("FILE:/run/krb5/alice.ccache")
);
}
#[test]
fn keytab_without_cache_gets_private_memory_cache() {
let resolved = KerberosCredentials::new(
Some("alice@EXAMPLE.COM".to_string()),
Some("/run/secrets/alice.keytab".to_string()),
None,
)
.unwrap()
.unwrap();
assert!(resolved.cache.unwrap().starts_with("MEMORY:hdfs-native-"));
}
#[test]
fn empty_credentials_use_default_path() {
assert!(
KerberosCredentials::new(None, None, None)
.unwrap()
.is_none()
);
}
#[test]
fn explicit_credentials_require_a_principal() {
let error =
KerberosCredentials::new(None, Some("/run/secrets/alice.keytab".to_string()), None)
.unwrap_err();
assert!(error.to_string().contains("principal is required"));
}
}