Skip to main content

cts_common/
service.rs

1use crate::{Region, RegionError};
2use regex::Regex;
3use std::sync::LazyLock;
4use url::Url;
5
6/// Regex to extract the region from a host FQDN or a URL.
7static REGION_HOST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
8    Regex::new(r"^(?:https://){0,1}([^\.]+)\.([^\.]+)\.viturhosted\.net/?$").expect("Invalid regex")
9});
10
11/// Regex to extract the region from a CTS host FQDN or URL.
12static CTS_REGION_HOST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
13    Regex::new(r"^(?:https://){0,1}([^\.]+)\.([^\.]+)\.cts\.cipherstashmanaged\.net/?$")
14        .expect("Invalid regex")
15});
16
17/// Domain name for ZeroKMS service discovery.
18static DOMAIN_NAME: &str = "viturhosted.net";
19
20/// Domain name for CTS service discovery.
21static CTS_DOMAIN_NAME: &str = "cts.cipherstashmanaged.net";
22
23/// Simple service discover trait that defines the name of the service and the endpoint for a given region.
24pub trait ServiceDiscovery {
25    fn name(&self) -> &'static str;
26    fn fqdn(region: Region) -> String;
27    fn endpoint(region: Region) -> Result<Url, RegionError>;
28}
29
30/// ZeroKms service.
31pub struct ZeroKmsServiceDiscovery;
32
33impl ServiceDiscovery for ZeroKmsServiceDiscovery {
34    fn name(&self) -> &'static str {
35        "zerokms"
36    }
37
38    /// Returns the FQDN for the service in the given region.
39    /// The FQDN is in the format `<region>.<provider>.viturhosted.net`.
40    fn fqdn(region: Region) -> String {
41        format!("{}.{DOMAIN_NAME}", region.identifier())
42    }
43
44    /// Returns the URL for the service in the given region.
45    /// The URL is in the format `https://<region>.<provider>.viturhosted.net/`.
46    ///
47    /// When the `test_utils` feature is enabled, the `ZEROKMS_DISCOVERY_OVERRIDE` environment
48    /// variable can be set to override the endpoint URL.
49    fn endpoint(region: Region) -> Result<Url, RegionError> {
50        #[cfg(feature = "test_utils")]
51        if let Ok(override_url) = std::env::var("ZEROKMS_DISCOVERY_OVERRIDE") {
52            return Url::parse(&override_url).map_err(|e| {
53                RegionError::InvalidRegion(format!(
54                    "Invalid ZEROKMS_DISCOVERY_OVERRIDE URL '{}': {}",
55                    override_url, e
56                ))
57            });
58        }
59
60        Url::parse(&format!("https://{}.{DOMAIN_NAME}/", region.identifier())).map_err(|e| {
61            RegionError::InvalidRegion(format!(
62                "Invalid service URL for ZeroKMS in region {}: {}",
63                region.identifier(),
64                e
65            ))
66        })
67    }
68}
69
70impl ZeroKmsServiceDiscovery {
71    /// Attempt to extract the region from a host FQDN or URL.
72    /// The FQDN must be in the format `<region>.<provider>.viturhosted.net`.
73    /// The URL must be in the format `https://<region>.<provider>.viturhosted.net/`.
74    pub fn region_from_host_fqdn(host_fqdn: &str) -> Result<Region, RegionError> {
75        REGION_HOST_REGEX
76            .captures(host_fqdn)
77            .and_then(|caps| Some((caps.get(1)?, caps.get(2)?)))
78            .map(|(r, p)| format!("{}.{}", r.as_str(), p.as_str()))
79            .ok_or_else(|| RegionError::InvalidHostFqdn(host_fqdn.to_string()))
80            .and_then(|ident| Region::new(&ident))
81    }
82}
83
84/// CTS service.
85pub struct CtsServiceDiscovery;
86
87impl ServiceDiscovery for CtsServiceDiscovery {
88    fn name(&self) -> &'static str {
89        "cts"
90    }
91
92    fn fqdn(region: Region) -> String {
93        format!("{}.{CTS_DOMAIN_NAME}", region.identifier())
94    }
95
96    fn endpoint(region: Region) -> Result<Url, RegionError> {
97        Url::parse(&format!(
98            "https://{}.{CTS_DOMAIN_NAME}/",
99            region.identifier()
100        ))
101        .map_err(|e| {
102            RegionError::InvalidRegion(format!(
103                "Invalid service URL for CTS in region {}: {}",
104                region.identifier(),
105                e
106            ))
107        })
108    }
109}
110
111impl CtsServiceDiscovery {
112    /// Attempt to extract the region from a CTS host FQDN or URL.
113    /// The FQDN must be in the format `<region>.<provider>.cts.cipherstashmanaged.net`.
114    /// The URL must be in the format `https://<region>.<provider>.cts.cipherstashmanaged.net/`.
115    pub fn region_from_host_fqdn(host_fqdn: &str) -> Result<Region, RegionError> {
116        CTS_REGION_HOST_REGEX
117            .captures(host_fqdn)
118            .and_then(|caps| Some((caps.get(1)?, caps.get(2)?)))
119            .map(|(r, p)| format!("{}.{}", r.as_str(), p.as_str()))
120            .ok_or_else(|| RegionError::InvalidHostFqdn(host_fqdn.to_string()))
121            .and_then(|ident| Region::new(&ident))
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    #[test]
130    fn test_region_from_host_fqdn() -> anyhow::Result<()> {
131        let host_fqdn = "us-west-1.aws.viturhosted.net";
132        let region = ZeroKmsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
133        assert_eq!(region.identifier(), "us-west-1.aws");
134
135        Ok(())
136    }
137
138    #[test]
139    fn test_region_from_host_endpoint() -> anyhow::Result<()> {
140        let host_fqdn = "https://us-west-1.aws.viturhosted.net";
141        let region = ZeroKmsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
142        assert_eq!(region.identifier(), "us-west-1.aws");
143        Ok(())
144    }
145
146    #[test]
147    fn test_region_from_host_endpoint_with_trailing_slash() -> anyhow::Result<()> {
148        let host_fqdn = "https://us-west-1.aws.viturhosted.net/";
149        let region = ZeroKmsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
150        assert_eq!(region.identifier(), "us-west-1.aws");
151        Ok(())
152    }
153
154    #[test]
155    fn test_cts_endpoint_ap_southeast_2() -> anyhow::Result<()> {
156        let region = Region::new("ap-southeast-2.aws")?;
157        let url = CtsServiceDiscovery::endpoint(region)?;
158        assert_eq!(
159            url.as_str(),
160            "https://ap-southeast-2.aws.cts.cipherstashmanaged.net/"
161        );
162        Ok(())
163    }
164
165    #[test]
166    fn test_cts_endpoint_us_east_1() -> anyhow::Result<()> {
167        let region = Region::new("us-east-1.aws")?;
168        let url = CtsServiceDiscovery::endpoint(region)?;
169        assert_eq!(
170            url.as_str(),
171            "https://us-east-1.aws.cts.cipherstashmanaged.net/"
172        );
173        Ok(())
174    }
175
176    #[test]
177    fn test_cts_region_from_host_fqdn() -> anyhow::Result<()> {
178        let host_fqdn = "us-east-1.aws.cts.cipherstashmanaged.net";
179        let region = CtsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
180        assert_eq!(region.identifier(), "us-east-1.aws");
181        Ok(())
182    }
183
184    #[test]
185    fn test_cts_region_from_host_endpoint() -> anyhow::Result<()> {
186        let host_fqdn = "https://ap-southeast-2.aws.cts.cipherstashmanaged.net";
187        let region = CtsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
188        assert_eq!(region.identifier(), "ap-southeast-2.aws");
189        Ok(())
190    }
191
192    #[test]
193    fn test_cts_region_from_host_endpoint_with_trailing_slash() -> anyhow::Result<()> {
194        let host_fqdn = "https://us-west-2.aws.cts.cipherstashmanaged.net/";
195        let region = CtsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
196        assert_eq!(region.identifier(), "us-west-2.aws");
197        Ok(())
198    }
199
200    #[test]
201    fn test_cts_fqdn_us_west_2() -> anyhow::Result<()> {
202        let region = Region::new("us-west-2.aws")?;
203        assert_eq!(
204            CtsServiceDiscovery::fqdn(region),
205            "us-west-2.aws.cts.cipherstashmanaged.net"
206        );
207        Ok(())
208    }
209
210    #[test]
211    fn test_zerokms_region_from_invalid_host() {
212        let result = ZeroKmsServiceDiscovery::region_from_host_fqdn("not-a-valid-host");
213        assert!(result.is_err());
214    }
215
216    #[test]
217    fn test_zerokms_region_from_wrong_domain() {
218        let result =
219            ZeroKmsServiceDiscovery::region_from_host_fqdn("us-east-1.aws.wrongdomain.net");
220        assert!(result.is_err());
221    }
222
223    #[test]
224    fn test_zerokms_region_from_missing_region_component() {
225        let result = ZeroKmsServiceDiscovery::region_from_host_fqdn("viturhosted.net");
226        assert!(result.is_err());
227    }
228
229    #[test]
230    fn test_cts_region_from_invalid_host() {
231        let result = CtsServiceDiscovery::region_from_host_fqdn("not-a-valid-host");
232        assert!(result.is_err());
233    }
234
235    #[test]
236    fn test_cts_region_from_wrong_domain() {
237        let result = CtsServiceDiscovery::region_from_host_fqdn("us-east-1.aws.wrongdomain.net");
238        assert!(result.is_err());
239    }
240
241    #[test]
242    fn test_cts_region_from_missing_region_component() {
243        let result = CtsServiceDiscovery::region_from_host_fqdn("cts.cipherstashmanaged.net");
244        assert!(result.is_err());
245    }
246}