use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProviderShape {
Validated { example: &'static str },
Unvalidated,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Jurisdiction {
Eu,
NonEu,
Unknown,
}
const PROVIDER_REGION_STEMS: &[(&str, &[&str], &str)] = &[
(
"aws",
&["us-", "eu-", "ap-", "sa-", "ca-", "me-", "af-", "il-", "cn-", "us-gov-"],
"us-east-1",
),
(
"gcp",
&[
"us-", "europe-", "asia-", "australia-", "southamerica-",
"northamerica-", "me-", "africa-",
],
"us-central1",
),
("azure", &[], "eastus"),
];
const EU_REGION_STEMS: &[&str] = &[
"eu-", "europe-",
"westeurope", "northeurope", "germanywestcentral", "germanynorth",
"francecentral", "francesouth", "norwayeast", "norwaywest",
"switzerlandnorth", "switzerlandwest", "swedencentral", "polandcentral",
"italynorth", "spaincentral",
];
pub fn provider_shape(provider: &str) -> ProviderShape {
let p = provider.trim().to_ascii_lowercase();
match PROVIDER_REGION_STEMS.iter().find(|(slug, _, _)| *slug == p) {
Some((_, _, example)) => ProviderShape::Validated { example },
None => ProviderShape::Unvalidated,
}
}
pub fn region_matches_provider(provider: &str, region: &str) -> Option<bool> {
let p = provider.trim().to_ascii_lowercase();
let r = region.trim().to_ascii_lowercase();
if r.is_empty() {
return None;
}
let (_, stems, _) = PROVIDER_REGION_STEMS.iter().find(|(slug, _, _)| *slug == p)?;
if p == "azure" {
return Some(!r.contains('-') && r.chars().all(|c| c.is_ascii_alphanumeric()));
}
Some(stems.iter().any(|s| r.starts_with(s)))
}
pub fn jurisdiction_of(provider: &str, region: &str) -> Jurisdiction {
let r = region.trim().to_ascii_lowercase();
if r.is_empty() || provider_shape(provider) == ProviderShape::Unvalidated {
return Jurisdiction::Unknown;
}
if EU_REGION_STEMS.iter().any(|s| r.starts_with(s)) {
Jurisdiction::Eu
} else {
Jurisdiction::NonEu
}
}
const JURISDICTION_BOUND_TAGS: &[(&str, Jurisdiction)] = &[("gdpr", Jurisdiction::Eu)];
pub fn compliance_violation(
tag: &str,
provider: &str,
region: &str,
) -> Option<ComplianceViolation> {
let t = tag.trim().to_ascii_lowercase();
let (_, required) = JURISDICTION_BOUND_TAGS
.iter()
.find(|(name, _)| *name == t)?;
let actual = jurisdiction_of(provider, region);
if actual == *required {
return None;
}
Some(ComplianceViolation {
tag: t,
required: *required,
actual,
provider: provider.to_string(),
region: region.to_string(),
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComplianceViolation {
pub tag: String,
pub required: Jurisdiction,
pub actual: Jurisdiction,
pub provider: String,
pub region: String,
}
impl fmt::Display for ComplianceViolation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.actual {
Jurisdiction::Unknown => write!(
f,
"declares compliance '{}', which binds data to the {:?} \
jurisdiction, but the substrate's jurisdiction cannot be \
determined from provider '{}' region '{}'. An obligation that \
cannot be shown to hold is not shown to hold — declare a \
region this compiler can place, or drop the tag",
self.tag, self.required, self.provider, self.region
),
_ => write!(
f,
"declares compliance '{}', which binds data to the {:?} \
jurisdiction, but provider '{}' region '{}' is {:?}. The \
deployment would move regulated data out of the jurisdiction \
the tag promises to keep it in",
self.tag, self.required, self.provider, self.region, self.actual
),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_jurisdiction_tag_is_a_real_regulatory_class() {
for (tag, _) in JURISDICTION_BOUND_TAGS {
let matches_kappa = crate::compliance::REGULATORY_CLASSES
.iter()
.any(|class| class.eq_ignore_ascii_case(tag));
assert!(
matches_kappa,
"jurisdiction tag `{tag}` is not a member of Κ ({:?}), so no program that \
compiles can carry it and this rule can never fire. Either add the class to \
`compliance::REGULATORY_CLASSES` — a product decision about what an adopter \
may assert — or delete the row.",
crate::compliance::REGULATORY_CLASSES
);
}
}
#[test]
fn the_docs_own_mismatch_example_is_caught() {
assert_eq!(region_matches_provider("aws", "eastus"), Some(false));
assert_eq!(region_matches_provider("azure", "eastus"), Some(true));
}
#[test]
fn each_validated_provider_accepts_its_own_regions() {
for (provider, region) in [
("aws", "us-east-1"),
("aws", "eu-west-2"),
("aws", "sa-east-1"),
("gcp", "us-central1"),
("gcp", "europe-west1"),
("gcp", "southamerica-east1"),
("azure", "eastus"),
("azure", "westeurope"),
("azure", "brazilsouth"),
] {
assert_eq!(
region_matches_provider(provider, region),
Some(true),
"{provider} should accept {region}"
);
}
}
#[test]
fn cross_provider_regions_are_rejected_in_both_directions() {
assert_eq!(region_matches_provider("azure", "us-east-1"), Some(false));
assert_eq!(region_matches_provider("aws", "westeurope"), Some(false));
assert_eq!(region_matches_provider("gcp", "eastus"), Some(false));
}
#[test]
fn an_unvalidated_provider_makes_no_region_judgment_and_says_so() {
assert_eq!(provider_shape("onprem"), ProviderShape::Unvalidated);
assert_eq!(provider_shape("kubernetes"), ProviderShape::Unvalidated);
assert_eq!(region_matches_provider("onprem", "rack-7"), None);
}
#[test]
fn an_absent_region_makes_no_judgment_because_region_is_optional() {
assert_eq!(region_matches_provider("aws", ""), None);
assert_eq!(region_matches_provider("aws", " "), None);
}
#[test]
fn jurisdiction_places_eu_regions_across_all_three_naming_styles() {
assert_eq!(jurisdiction_of("aws", "eu-west-1"), Jurisdiction::Eu);
assert_eq!(jurisdiction_of("gcp", "europe-west4"), Jurisdiction::Eu);
assert_eq!(jurisdiction_of("azure", "westeurope"), Jurisdiction::Eu);
assert_eq!(jurisdiction_of("azure", "germanywestcentral"), Jurisdiction::Eu);
}
#[test]
fn jurisdiction_places_non_eu_regions_and_admits_when_it_cannot() {
assert_eq!(jurisdiction_of("aws", "us-east-1"), Jurisdiction::NonEu);
assert_eq!(jurisdiction_of("gcp", "asia-northeast1"), Jurisdiction::NonEu);
assert_eq!(jurisdiction_of("azure", "eastus"), Jurisdiction::NonEu);
assert_eq!(jurisdiction_of("onprem", "rack-7"), Jurisdiction::Unknown);
assert_eq!(jurisdiction_of("aws", ""), Jurisdiction::Unknown);
}
#[test]
fn the_docs_own_gdpr_example_is_caught() {
let v = compliance_violation("gdpr", "aws", "us-east-1").expect("violation");
assert_eq!(v.actual, Jurisdiction::NonEu);
assert_eq!(v.required, Jurisdiction::Eu);
assert!(v.to_string().contains("out of the jurisdiction"), "{v}");
}
#[test]
fn gdpr_in_an_eu_region_is_clean() {
assert_eq!(compliance_violation("gdpr", "aws", "eu-west-1"), None);
assert_eq!(compliance_violation("GDPR", "azure", "westeurope"), None);
}
#[test]
fn an_undeterminable_jurisdiction_is_a_violation_never_a_pass() {
let v = compliance_violation("gdpr", "onprem", "rack-7").expect("violation");
assert_eq!(v.actual, Jurisdiction::Unknown);
assert!(v.to_string().contains("cannot be determined"), "{v}");
}
#[test]
fn tags_with_no_geographic_obligation_are_not_invented_into_one() {
assert_eq!(compliance_violation("soc2", "aws", "us-east-1"), None);
assert_eq!(compliance_violation("hipaa", "gcp", "asia-east1"), None);
}
}