Skip to main content

xianyun_pki/
constraints.rs

1//! Name Constraint computation for the dev CA.
2//!
3//! Ported from `internal/pki/constraints.go`. Not yet implemented — see
4//! `PORT_NOTES.md`.
5
6/// Mirrors `gitlab.com/gitlab-org/caproni/internal/config/types.NetworkConfig`
7/// only for the fields these tests touch. The real config crate hasn't been
8/// ported yet, so this is a narrow stand-in, not the eventual shared type.
9#[derive(Debug, Clone, Default)]
10pub struct NetworkConfig {
11    pub hostname: String,
12    pub cluster_domain: String,
13}
14
15/// Mirrors `types.ClusterConfig` only for the `Network` field these tests touch.
16#[derive(Debug, Clone, Default)]
17pub struct ClusterConfig {
18    pub network: Option<NetworkConfig>,
19}
20
21impl ClusterConfig {
22    /// Stands in for `types.ClusterConfig.GetAllHostnames`, which expands
23    /// `${CAPRONI_CLUSTER_NAME}` in the configured hostname and returns the
24    /// resulting list. Only the single-hostname, single-substitution case
25    /// exercised by `TestPermittedDNSFor` is modeled.
26    #[must_use]
27    pub fn get_all_hostnames(&self, cluster_name: &str) -> Vec<String> {
28        let hostname = self
29            .network
30            .as_ref()
31            .map(|network| {
32                network
33                    .hostname
34                    .replace("${CAPRONI_CLUSTER_NAME}", cluster_name)
35            })
36            .unwrap_or_default();
37        vec![hostname]
38    }
39}
40
41/// Ported from `DefaultPermittedTLDs`.
42#[must_use]
43pub fn default_permitted_tlds() -> Vec<String> {
44    vec![
45        ".local".to_string(),
46        ".test".to_string(),
47        ".svc".to_string(),
48    ]
49}
50
51/// Ported from `PermittedDNSFor`.
52#[must_use]
53pub fn permitted_dns_for(cluster: &ClusterConfig, cluster_name: &str) -> Vec<String> {
54    match &cluster.network {
55        None => build_permitted_dns("", ""),
56        Some(network) => build_permitted_dns(
57            &cluster.get_all_hostnames(cluster_name)[0],
58            &network.cluster_domain,
59        ),
60    }
61}
62
63/// Ported from `BuildPermittedDNS`.
64#[must_use]
65pub fn build_permitted_dns(hostname: &str, cluster_domain: &str) -> Vec<String> {
66    let defaults = default_permitted_tlds();
67    let mut permitted = defaults.clone();
68
69    let subtree = if !cluster_domain.is_empty() && !covered(cluster_domain, &defaults) {
70        format!(".{cluster_domain}")
71    } else {
72        String::new()
73    };
74
75    if !hostname.is_empty() {
76        let mut with_subtree = defaults.clone();
77        if !subtree.is_empty() {
78            with_subtree.push(subtree.clone());
79        }
80        if !covered(hostname, &with_subtree) {
81            permitted.push(hostname.to_string());
82        }
83    }
84
85    if !subtree.is_empty() {
86        permitted.push(subtree);
87    }
88
89    permitted
90}
91
92/// Ported from `covered`.
93fn covered(name: &str, subtrees: &[String]) -> bool {
94    subtrees
95        .iter()
96        .any(|s| !s.is_empty() && name.ends_with(s.as_str()))
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    /// Ported from `TestBuildPermittedDNS`.
104    #[test]
105    fn build_permitted_dns_computes_name_constraints() {
106        let defaults = default_permitted_tlds();
107
108        struct Case {
109            name: &'static str,
110            hostname: &'static str,
111            cluster_domain: &'static str,
112            want: Vec<String>,
113        }
114
115        let mut with_hostname = defaults.clone();
116        with_hostname.push("gitlab.dev".to_string());
117
118        let mut with_deep_subdomain = defaults.clone();
119        with_deep_subdomain.push("my.custom.domain".to_string());
120
121        let mut with_custom_cluster_domain = defaults.clone();
122        with_custom_cluster_domain.push(".corp.example".to_string());
123
124        let mut with_hostname_and_cluster_domain = defaults.clone();
125        with_hostname_and_cluster_domain.push("gitlab.dev".to_string());
126        with_hostname_and_cluster_domain.push(".corp.example".to_string());
127
128        let mut with_hostname_inside_cluster_domain = defaults.clone();
129        with_hostname_inside_cluster_domain.push(".corp.example".to_string());
130
131        let cases = vec![
132            Case {
133                name: "empty inputs",
134                hostname: "",
135                cluster_domain: "",
136                want: defaults.clone(),
137            },
138            Case {
139                name: "hostname covered by .test",
140                hostname: "gitlab.test",
141                cluster_domain: "",
142                want: defaults.clone(),
143            },
144            Case {
145                name: "hostname covered by .local",
146                hostname: "app.local",
147                cluster_domain: "",
148                want: defaults.clone(),
149            },
150            Case {
151                name: "hostname not covered",
152                hostname: "gitlab.dev",
153                cluster_domain: "",
154                want: with_hostname,
155            },
156            Case {
157                name: "deep subdomain not covered",
158                hostname: "my.custom.domain",
159                cluster_domain: "",
160                want: with_deep_subdomain,
161            },
162            Case {
163                name: "bare TLD match",
164                hostname: "something.test",
165                cluster_domain: "",
166                want: defaults.clone(),
167            },
168            Case {
169                name: "cluster.local is covered",
170                hostname: "",
171                cluster_domain: "cluster.local",
172                want: defaults.clone(),
173            },
174            Case {
175                name: "custom cluster domain is added as a subtree",
176                hostname: "",
177                cluster_domain: "corp.example",
178                want: with_custom_cluster_domain,
179            },
180            Case {
181                name: "hostname and cluster domain",
182                hostname: "gitlab.dev",
183                cluster_domain: "corp.example",
184                want: with_hostname_and_cluster_domain,
185            },
186            Case {
187                name: "hostname inside the cluster domain",
188                hostname: "gitlab.corp.example",
189                cluster_domain: "corp.example",
190                want: with_hostname_inside_cluster_domain,
191            },
192        ];
193
194        for case in cases {
195            let got = build_permitted_dns(case.hostname, case.cluster_domain);
196            assert_eq!(got, case.want, "case: {}", case.name);
197        }
198    }
199
200    /// Ported from `TestPermittedDNSFor`.
201    #[test]
202    fn permitted_dns_for_derives_from_cluster_config() {
203        struct Case {
204            name: &'static str,
205            network: Option<NetworkConfig>,
206            want: Vec<&'static str>,
207        }
208
209        let cases = vec![
210            Case {
211                name: "no network",
212                network: None,
213                want: vec![".local", ".test", ".svc"],
214            },
215            Case {
216                name: "covered hostname",
217                network: Some(NetworkConfig {
218                    hostname: "gitlab.test".into(),
219                    cluster_domain: String::new(),
220                }),
221                want: vec![".local", ".test", ".svc"],
222            },
223            Case {
224                name: "hostname and cluster domain",
225                network: Some(NetworkConfig {
226                    hostname: "gitlab.example.com".into(),
227                    cluster_domain: "corp.example".into(),
228                }),
229                want: vec![
230                    ".local",
231                    ".test",
232                    ".svc",
233                    "gitlab.example.com",
234                    ".corp.example",
235                ],
236            },
237            Case {
238                name: "cluster name is expanded",
239                network: Some(NetworkConfig {
240                    hostname: "gitlab.${CAPRONI_CLUSTER_NAME}.example.com".into(),
241                    cluster_domain: String::new(),
242                }),
243                want: vec![".local", ".test", ".svc", "gitlab.dev.example.com"],
244            },
245        ];
246
247        for case in cases {
248            let cluster = ClusterConfig {
249                network: case.network,
250            };
251            let got = permitted_dns_for(&cluster, "dev");
252            let want: Vec<String> = case.want.iter().map(|s| s.to_string()).collect();
253            assert_eq!(got, want, "case: {}", case.name);
254        }
255    }
256}