Repository navigation
Expand file tree
/
Copy pathkdc.rs
More file actions
158 lines (134 loc) · 5.11 KB
/
Copy pathkdc.rs
File metadata and controls
158 lines (134 loc) · 5.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
cfg_if::cfg_if! {
if #[cfg(windows)] {
use windows_registry::LOCAL_MACHINE;
}
}
use std::env;
#[cfg(not(target_os = "windows"))]
use std::path::Path;
use std::str::FromStr;
use url::Url;
use crate::dns::detect_kdc_hosts_from_dns;
#[cfg(not(target_os = "windows"))]
use crate::krb::Krb5Conf;
#[cfg(target_os = "windows")]
#[instrument(level = "debug", ret)]
pub(crate) fn detect_kdc_hosts_from_system(domain: &str) -> Vec<String> {
let domain_upper = domain.to_uppercase();
let hklm = LOCAL_MACHINE;
let domains_key_path = "SYSTEM\\CurrentControlSet\\Control\\Lsa\\Kerberos\\Domains";
let domain_key_path = format!("{}\\{}", domains_key_path, domain_upper);
if let Ok(domain_key) = hklm.open(domain_key_path) {
let kdc_names: Vec<String> = domain_key.get_multi_string("KdcNames").unwrap_or_default();
kdc_names.iter().map(|x| format!("tcp://{x}:88")).collect()
} else {
Vec::new()
}
}
#[cfg(not(target_os = "windows"))]
#[instrument(level = "debug", ret)]
pub(crate) fn detect_kdc_hosts_from_system(domain: &str) -> Vec<String> {
// https://web.mit.edu/kerberos/krb5-current/doc/user/user_config/kerberos.html#environment-variables
let krb5_config = env::var("KRB5_CONFIG").unwrap_or_else(|_| "/etc/krb5.conf:/usr/local/etc/krb5.conf".to_string());
let krb5_conf_paths = krb5_config.split(':').map(Path::new).collect::<Vec<&Path>>();
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 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}")) {
return vec![kdc_url];
}
if let Ok(kdc_url) = env::var("SSPI_KDC_URL") {
return vec![kdc_url];
}
let kdc_hosts = detect_kdc_hosts_from_system(domain);
if !kdc_hosts.is_empty() {
return kdc_hosts;
}
detect_kdc_hosts_from_dns(domain)
}
pub fn detect_kdc_host(domain: &str) -> Option<String> {
let kdc_hosts = detect_kdc_hosts(domain);
if !kdc_hosts.is_empty() {
Some(kdc_hosts.first().unwrap().to_string())
} else {
None
}
}
pub fn detect_kdc_url(domain: &str) -> Option<Url> {
detect_kdc_urls(domain).into_iter().next()
}
/// 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)]
mod tests {
use super::detect_kdc_hosts;
#[test]
fn test_detect_kdc() {
if let Ok(domain) = std::env::var("TEST_KERBEROS_REALM") {
println!("Finding KDC for {} domain", domain);
let kdc_hosts = detect_kdc_hosts(&domain);
if let Some(kdc_host) = kdc_hosts.first() {
println!("KDC server: {kdc_host}");
} else {
println!("No KDC server found!");
}
}
}
#[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());
}
}