use crate::{Region, RegionError};
use regex::Regex;
use std::sync::LazyLock;
use url::Url;
static REGION_HOST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?:https://){0,1}([^\.]+)\.([^\.]+)\.viturhosted\.net/?$").expect("Invalid regex")
});
static CTS_REGION_HOST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"^(?:https://){0,1}([^\.]+)\.([^\.]+)\.cts\.cipherstashmanaged\.net/?$")
.expect("Invalid regex")
});
static DOMAIN_NAME: &str = "viturhosted.net";
static CTS_DOMAIN_NAME: &str = "cts.cipherstashmanaged.net";
pub trait ServiceDiscovery {
fn name(&self) -> &'static str;
fn fqdn(region: Region) -> String;
fn endpoint(region: Region) -> Result<Url, RegionError>;
}
pub struct ZeroKmsServiceDiscovery;
impl ServiceDiscovery for ZeroKmsServiceDiscovery {
fn name(&self) -> &'static str {
"zerokms"
}
fn fqdn(region: Region) -> String {
format!("{}.{DOMAIN_NAME}", region.identifier())
}
fn endpoint(region: Region) -> Result<Url, RegionError> {
#[cfg(feature = "test_utils")]
if let Ok(override_url) = std::env::var("ZEROKMS_DISCOVERY_OVERRIDE") {
return Url::parse(&override_url).map_err(|e| {
RegionError::InvalidRegion(format!(
"Invalid ZEROKMS_DISCOVERY_OVERRIDE URL '{}': {}",
override_url, e
))
});
}
Url::parse(&format!("https://{}.{DOMAIN_NAME}/", region.identifier())).map_err(|e| {
RegionError::InvalidRegion(format!(
"Invalid service URL for ZeroKMS in region {}: {}",
region.identifier(),
e
))
})
}
}
impl ZeroKmsServiceDiscovery {
pub fn region_from_host_fqdn(host_fqdn: &str) -> Result<Region, RegionError> {
REGION_HOST_REGEX
.captures(host_fqdn)
.and_then(|caps| Some((caps.get(1)?, caps.get(2)?)))
.map(|(r, p)| format!("{}.{}", r.as_str(), p.as_str()))
.ok_or_else(|| RegionError::InvalidHostFqdn(host_fqdn.to_string()))
.and_then(|ident| Region::new(&ident))
}
}
static SECRETS_DOMAIN_NAME: &str = "dashboard.cipherstash.com";
pub struct SecretsServiceDiscovery;
impl ServiceDiscovery for SecretsServiceDiscovery {
fn name(&self) -> &'static str {
"secrets"
}
fn fqdn(_region: Region) -> String {
SECRETS_DOMAIN_NAME.to_string()
}
fn endpoint(_region: Region) -> Result<Url, RegionError> {
Url::parse(&format!("https://{SECRETS_DOMAIN_NAME}/"))
.map_err(|e| RegionError::InvalidRegion(format!("Invalid Secrets service URL: {e}")))
}
}
pub struct CtsServiceDiscovery;
impl ServiceDiscovery for CtsServiceDiscovery {
fn name(&self) -> &'static str {
"cts"
}
fn fqdn(region: Region) -> String {
format!("{}.{CTS_DOMAIN_NAME}", region.identifier())
}
fn endpoint(region: Region) -> Result<Url, RegionError> {
Url::parse(&format!(
"https://{}.{CTS_DOMAIN_NAME}/",
region.identifier()
))
.map_err(|e| {
RegionError::InvalidRegion(format!(
"Invalid service URL for CTS in region {}: {}",
region.identifier(),
e
))
})
}
}
impl CtsServiceDiscovery {
pub fn region_from_host_fqdn(host_fqdn: &str) -> Result<Region, RegionError> {
CTS_REGION_HOST_REGEX
.captures(host_fqdn)
.and_then(|caps| Some((caps.get(1)?, caps.get(2)?)))
.map(|(r, p)| format!("{}.{}", r.as_str(), p.as_str()))
.ok_or_else(|| RegionError::InvalidHostFqdn(host_fqdn.to_string()))
.and_then(|ident| Region::new(&ident))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_region_from_host_fqdn() -> anyhow::Result<()> {
let host_fqdn = "us-west-1.aws.viturhosted.net";
let region = ZeroKmsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
assert_eq!(region.identifier(), "us-west-1.aws");
Ok(())
}
#[test]
fn test_region_from_host_endpoint() -> anyhow::Result<()> {
let host_fqdn = "https://us-west-1.aws.viturhosted.net";
let region = ZeroKmsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
assert_eq!(region.identifier(), "us-west-1.aws");
Ok(())
}
#[test]
fn test_region_from_host_endpoint_with_trailing_slash() -> anyhow::Result<()> {
let host_fqdn = "https://us-west-1.aws.viturhosted.net/";
let region = ZeroKmsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
assert_eq!(region.identifier(), "us-west-1.aws");
Ok(())
}
#[test]
fn test_cts_endpoint_ap_southeast_2() -> anyhow::Result<()> {
let region = Region::new("ap-southeast-2.aws")?;
let url = CtsServiceDiscovery::endpoint(region)?;
assert_eq!(
url.as_str(),
"https://ap-southeast-2.aws.cts.cipherstashmanaged.net/"
);
Ok(())
}
#[test]
fn test_cts_endpoint_us_east_1() -> anyhow::Result<()> {
let region = Region::new("us-east-1.aws")?;
let url = CtsServiceDiscovery::endpoint(region)?;
assert_eq!(
url.as_str(),
"https://us-east-1.aws.cts.cipherstashmanaged.net/"
);
Ok(())
}
#[test]
fn test_cts_region_from_host_fqdn() -> anyhow::Result<()> {
let host_fqdn = "us-east-1.aws.cts.cipherstashmanaged.net";
let region = CtsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
assert_eq!(region.identifier(), "us-east-1.aws");
Ok(())
}
#[test]
fn test_cts_region_from_host_endpoint() -> anyhow::Result<()> {
let host_fqdn = "https://ap-southeast-2.aws.cts.cipherstashmanaged.net";
let region = CtsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
assert_eq!(region.identifier(), "ap-southeast-2.aws");
Ok(())
}
#[test]
fn test_cts_region_from_host_endpoint_with_trailing_slash() -> anyhow::Result<()> {
let host_fqdn = "https://us-west-2.aws.cts.cipherstashmanaged.net/";
let region = CtsServiceDiscovery::region_from_host_fqdn(host_fqdn)?;
assert_eq!(region.identifier(), "us-west-2.aws");
Ok(())
}
#[test]
fn test_cts_fqdn_us_west_2() -> anyhow::Result<()> {
let region = Region::new("us-west-2.aws")?;
assert_eq!(
CtsServiceDiscovery::fqdn(region),
"us-west-2.aws.cts.cipherstashmanaged.net"
);
Ok(())
}
#[test]
fn test_zerokms_region_from_invalid_host() {
let result = ZeroKmsServiceDiscovery::region_from_host_fqdn("not-a-valid-host");
assert!(result.is_err());
}
#[test]
fn test_zerokms_region_from_wrong_domain() {
let result =
ZeroKmsServiceDiscovery::region_from_host_fqdn("us-east-1.aws.wrongdomain.net");
assert!(result.is_err());
}
#[test]
fn test_zerokms_region_from_missing_region_component() {
let result = ZeroKmsServiceDiscovery::region_from_host_fqdn("viturhosted.net");
assert!(result.is_err());
}
#[test]
fn test_cts_region_from_invalid_host() {
let result = CtsServiceDiscovery::region_from_host_fqdn("not-a-valid-host");
assert!(result.is_err());
}
#[test]
fn test_cts_region_from_wrong_domain() {
let result = CtsServiceDiscovery::region_from_host_fqdn("us-east-1.aws.wrongdomain.net");
assert!(result.is_err());
}
#[test]
fn test_cts_region_from_missing_region_component() {
let result = CtsServiceDiscovery::region_from_host_fqdn("cts.cipherstashmanaged.net");
assert!(result.is_err());
}
#[test]
fn test_secrets_endpoint_returns_global_url() -> anyhow::Result<()> {
let region = Region::new("ap-southeast-2.aws")?;
let url = SecretsServiceDiscovery::endpoint(region)?;
assert_eq!(
url.as_str(),
"https://dashboard.cipherstash.com/",
"secrets endpoint should be the global dashboard URL regardless of region"
);
Ok(())
}
#[test]
fn test_secrets_endpoint_ignores_region() -> anyhow::Result<()> {
let url_apse2 = SecretsServiceDiscovery::endpoint(Region::new("ap-southeast-2.aws")?)?;
let url_use1 = SecretsServiceDiscovery::endpoint(Region::new("us-east-1.aws")?)?;
assert_eq!(
url_apse2, url_use1,
"secrets endpoint should be the same for all regions"
);
Ok(())
}
#[test]
fn test_secrets_fqdn() -> anyhow::Result<()> {
let region = Region::new("us-east-1.aws")?;
assert_eq!(
SecretsServiceDiscovery::fqdn(region),
"dashboard.cipherstash.com"
);
Ok(())
}
}