Repository navigation
feat(kerberos): honour DNS SRV port and fail over across multiple KDCs #698
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,16 +41,29 @@ pub(crate) fn detect_kdc_hosts_from_system(domain: &str) -> Vec<String> { | |
| for krb5_conf_path in krb5_conf_paths { | ||
| if krb5_conf_path.exists() | ||
| && let Some(krb5_conf) = Krb5Conf::new_from_file(krb5_conf_path) | ||
| && let Some(kdc) = krb5_conf.get_value(vec!["realms", domain, "kdc"]) | ||
| { | ||
| let kdc_url = format!("tcp://{}", kdc.as_str()); | ||
| return vec![kdc_url]; | ||
| let kdcs = krb5_conf.get_all_values(vec!["realms", domain, "kdc"]); | ||
| if !kdcs.is_empty() { | ||
| return krb5_kdc_values_to_hosts(&kdcs); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Vec::new() | ||
| } | ||
|
|
||
| /// Maps krb5.conf `kdc = ` values to an ordered list of `tcp://` host URLs. | ||
| /// | ||
| /// Per [krb5.conf] § "\[realms\]", each `kdc = ` line names exactly one host (optionally `host:port`); | ||
| /// multiple KDCs for a realm are expressed as multiple `kdc = ` lines, which [`get_all_values`] returns in order. | ||
| /// | ||
| /// [krb5.conf]: https://web.mit.edu/kerberos/krb5-current/doc/admin/conf_files/krb5_conf.html | ||
| /// [`get_all_values`]: crate::krb::Krb5Conf::get_all_values | ||
| #[cfg(not(target_os = "windows"))] | ||
| fn krb5_kdc_values_to_hosts(kdcs: &[&str]) -> Vec<String> { | ||
| kdcs.iter().map(|host| format!("tcp://{host}")).collect() | ||
| } | ||
|
|
||
| #[instrument(ret, level = "debug")] | ||
| pub(crate) fn detect_kdc_hosts(domain: &str) -> Vec<String> { | ||
| if let Ok(kdc_url) = env::var(format!("SSPI_KDC_URL_{domain}")) { | ||
|
|
@@ -80,8 +93,18 @@ pub fn detect_kdc_host(domain: &str) -> Option<String> { | |
| } | ||
|
|
||
| pub fn detect_kdc_url(domain: &str) -> Option<Url> { | ||
| let kdc_host = detect_kdc_host(domain)?; | ||
| Url::from_str(&kdc_host).ok() | ||
| detect_kdc_urls(domain).into_iter().next() | ||
| } | ||
|
Comment on lines
95
to
+97
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: Looks like an improvement over the previous code which was returning None on the first KDC host even if unparseable.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed; an unparseable entry is now skipped instead of ending resolution, and the helper is unchanged in a239385 apart from taking Note Human-tuned, LLM-assisted content. |
||
|
|
||
| /// Resolves the ordered list of candidate KDC URLs for `domain`. | ||
| /// | ||
| /// Same resolution order as [`detect_kdc_host`] (`SSPI_KDC_URL_<DOMAIN>`, then the system store, then DNS SRV), but | ||
| /// returns every candidate rather than just the first, so callers can fail over to the next KDC when one is unreachable. | ||
| pub fn detect_kdc_urls(domain: &str) -> Vec<Url> { | ||
| detect_kdc_hosts(domain) | ||
| .iter() | ||
| .filter_map(|host| Url::from_str(host).ok()) | ||
| .collect() | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
|
|
@@ -99,4 +122,37 @@ mod tests { | |
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(not(target_os = "windows"))] | ||
| #[test] | ||
| fn krb5_kdc_values_expand_to_one_host_each() { | ||
| use super::krb5_kdc_values_to_hosts; | ||
|
|
||
| // Single host. | ||
| assert_eq!( | ||
| krb5_kdc_values_to_hosts(&["dc1.example.com"]), | ||
| ["tcp://dc1.example.com"] | ||
| ); | ||
|
|
||
| // Multiple `kdc = ` lines preserve order. | ||
| assert_eq!( | ||
| krb5_kdc_values_to_hosts(&["dc1.example.com", "dc2.example.com"]), | ||
| ["tcp://dc1.example.com", "tcp://dc2.example.com"] | ||
| ); | ||
|
|
||
| // One host per line (MIT spec): a whitespace-separated line is not split. | ||
| assert_eq!( | ||
| krb5_kdc_values_to_hosts(&["dc1.example.com dc2.example.com"]), | ||
| ["tcp://dc1.example.com dc2.example.com"] | ||
| ); | ||
|
|
||
| // An explicit port is preserved. | ||
| assert_eq!( | ||
| krb5_kdc_values_to_hosts(&["dc1.example.com:8888"]), | ||
| ["tcp://dc1.example.com:8888"] | ||
| ); | ||
|
|
||
| // Nothing in, nothing out. | ||
| assert!(krb5_kdc_values_to_hosts(&[]).is_empty()); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.