Skip to main content

cloud_sdk/transport/endpoint/
policy.rs

1//! Provider-owned credential-destination policies.
2
3use super::EndpointIdentity;
4
5/// Maximum identities in one finite official endpoint set.
6pub const MAX_OFFICIAL_ENDPOINTS: usize = 32;
7
8/// Maximum provider region identifier length.
9pub const MAX_ENDPOINT_REGION_BYTES: usize = 63;
10
11/// Endpoint trust policy validation or matching error.
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum EndpointPolicyError {
14    /// A finite official endpoint set must not be empty.
15    EmptyOfficialSet,
16    /// A finite official endpoint set exceeds [`MAX_OFFICIAL_ENDPOINTS`].
17    TooManyOfficialEndpoints,
18    /// A region identifier is not bounded canonical lowercase ASCII.
19    InvalidRegion,
20    /// The candidate endpoint is not admitted by the policy.
21    DestinationMismatch,
22}
23
24impl_static_error!(EndpointPolicyError,
25    Self::EmptyOfficialSet => "official endpoint set is empty",
26    Self::TooManyOfficialEndpoints => "official endpoint set exceeds the length limit",
27    Self::InvalidRegion => "endpoint region identifier is invalid",
28    Self::DestinationMismatch => "endpoint destination is not admitted by policy",
29);
30
31/// Trust provenance represented by an [`EndpointPolicy`].
32#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
33pub enum EndpointPolicyKind {
34    /// One provider-owned immutable endpoint.
35    Fixed,
36    /// One endpoint from a bounded provider-owned official set.
37    OfficialSet,
38    /// One endpoint derived by a provider from a validated region.
39    RegionDerived,
40    /// One custom endpoint explicitly acknowledged by trusted configuration.
41    AcknowledgedCustom,
42}
43
44/// Explicit acknowledgement that a custom endpoint is a credential destination.
45///
46/// Construct this only at a trusted operator-configuration boundary. Tenant,
47/// request, webhook, or other attacker-controlled input must never select the
48/// endpoint. The type makes custom trust conspicuous but cannot determine
49/// whether configuration is operationally trustworthy.
50///
51/// ```compile_fail
52/// use cloud_sdk::transport::CustomEndpointAcknowledgement;
53///
54/// let acknowledgement = CustomEndpointAcknowledgement { private: () };
55/// ```
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub struct CustomEndpointAcknowledgement {
58    private: (),
59}
60
61impl CustomEndpointAcknowledgement {
62    /// Acknowledges a custom endpoint selected by trusted operator configuration.
63    #[must_use]
64    pub const fn trusted_operator_configuration() -> Self {
65        Self { private: () }
66    }
67}
68
69/// Custom endpoint paired with an explicit operator acknowledgement.
70#[derive(Clone, Copy, Debug, Eq, PartialEq)]
71pub struct AcknowledgedCustomEndpoint<'a> {
72    identity: EndpointIdentity<'a>,
73    acknowledgement: CustomEndpointAcknowledgement,
74}
75
76impl<'a> AcknowledgedCustomEndpoint<'a> {
77    /// Binds an endpoint identity to explicit operator acknowledgement.
78    #[must_use]
79    pub const fn new(
80        identity: EndpointIdentity<'a>,
81        acknowledgement: CustomEndpointAcknowledgement,
82    ) -> Self {
83        Self {
84            identity,
85            acknowledgement,
86        }
87    }
88
89    /// Returns the acknowledged endpoint identity.
90    #[must_use]
91    pub const fn identity(self) -> EndpointIdentity<'a> {
92        self.identity
93    }
94}
95
96/// Provider-derived endpoint paired with its canonical region identifier.
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
98pub struct RegionEndpoint<'a> {
99    region: &'a str,
100    identity: EndpointIdentity<'a>,
101}
102
103impl<'a> RegionEndpoint<'a> {
104    /// Validates a provider region and its already-derived endpoint identity.
105    pub fn new(
106        region: &'a str,
107        identity: EndpointIdentity<'a>,
108    ) -> Result<Self, EndpointPolicyError> {
109        validate_region(region)?;
110        Ok(Self { region, identity })
111    }
112
113    /// Returns the provider region used for derivation.
114    #[must_use]
115    pub const fn region(self) -> &'a str {
116        self.region
117    }
118
119    /// Returns the provider-derived endpoint identity.
120    #[must_use]
121    pub const fn identity(self) -> EndpointIdentity<'a> {
122        self.identity
123    }
124}
125
126/// Allocation-free provider-owned endpoint admission policy.
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub enum EndpointPolicy<'a> {
129    /// One immutable provider endpoint.
130    Fixed(EndpointIdentity<'a>),
131    /// A bounded finite set of official provider endpoints.
132    OfficialSet(&'a [EndpointIdentity<'a>]),
133    /// One endpoint derived from a provider region.
134    RegionDerived(RegionEndpoint<'a>),
135    /// One explicitly acknowledged custom credential destination.
136    AcknowledgedCustom(AcknowledgedCustomEndpoint<'a>),
137}
138
139impl<'a> EndpointPolicy<'a> {
140    /// Admits one fixed provider-owned endpoint.
141    #[must_use]
142    pub const fn fixed(identity: EndpointIdentity<'a>) -> Self {
143        Self::Fixed(identity)
144    }
145
146    /// Admits one of a bounded finite provider-owned endpoint set.
147    pub fn official_set(
148        identities: &'a [EndpointIdentity<'a>],
149    ) -> Result<Self, EndpointPolicyError> {
150        if identities.is_empty() {
151            return Err(EndpointPolicyError::EmptyOfficialSet);
152        }
153        if identities.len() > MAX_OFFICIAL_ENDPOINTS {
154            return Err(EndpointPolicyError::TooManyOfficialEndpoints);
155        }
156        Ok(Self::OfficialSet(identities))
157    }
158
159    /// Admits one provider-derived regional endpoint.
160    #[must_use]
161    pub const fn region_derived(endpoint: RegionEndpoint<'a>) -> Self {
162        Self::RegionDerived(endpoint)
163    }
164
165    /// Admits one explicitly acknowledged custom credential destination.
166    #[must_use]
167    pub const fn acknowledged_custom(endpoint: AcknowledgedCustomEndpoint<'a>) -> Self {
168        Self::AcknowledgedCustom(endpoint)
169    }
170
171    /// Returns the policy's trust provenance.
172    #[must_use]
173    pub const fn kind(self) -> EndpointPolicyKind {
174        match self {
175            Self::Fixed(_) => EndpointPolicyKind::Fixed,
176            Self::OfficialSet(_) => EndpointPolicyKind::OfficialSet,
177            Self::RegionDerived(_) => EndpointPolicyKind::RegionDerived,
178            Self::AcknowledgedCustom(_) => EndpointPolicyKind::AcknowledgedCustom,
179        }
180    }
181
182    /// Reports whether the exact destination is admitted.
183    #[must_use]
184    pub fn admits(self, candidate: EndpointIdentity<'_>) -> bool {
185        match self {
186            Self::Fixed(identity) => identity == candidate,
187            Self::OfficialSet(identities) => identities.contains(&candidate),
188            Self::RegionDerived(endpoint) => endpoint.identity() == candidate,
189            Self::AcknowledgedCustom(endpoint) => endpoint.identity() == candidate,
190        }
191    }
192
193    /// Fails closed unless the exact destination is admitted.
194    pub fn verify(self, candidate: EndpointIdentity<'_>) -> Result<(), EndpointPolicyError> {
195        if self.admits(candidate) {
196            Ok(())
197        } else {
198            Err(EndpointPolicyError::DestinationMismatch)
199        }
200    }
201}
202
203fn validate_region(region: &str) -> Result<(), EndpointPolicyError> {
204    if region.is_empty()
205        || region.len() > MAX_ENDPOINT_REGION_BYTES
206        || region.starts_with('-')
207        || region.ends_with('-')
208        || !region
209            .bytes()
210            .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-')
211    {
212        return Err(EndpointPolicyError::InvalidRegion);
213    }
214    Ok(())
215}
216
217#[cfg(test)]
218mod tests {
219    use super::{
220        AcknowledgedCustomEndpoint, CustomEndpointAcknowledgement, EndpointPolicy,
221        EndpointPolicyError, EndpointPolicyKind, RegionEndpoint,
222    };
223    use crate::transport::{EndpointIdentity, EndpointScheme};
224
225    #[test]
226    fn all_policy_classes_match_only_their_exact_destination() {
227        let a = identity("a.example");
228        let b = identity("b.example");
229        let other = identity("other.example");
230        assert!(a.is_ok() && b.is_ok() && other.is_ok());
231        let (Ok(a), Ok(b), Ok(other)) = (a, b, other) else {
232            return;
233        };
234        let official = [a, b];
235        let set = EndpointPolicy::official_set(&official);
236        let region = RegionEndpoint::new("eu-west-1", b);
237        let custom = AcknowledgedCustomEndpoint::new(
238            a,
239            CustomEndpointAcknowledgement::trusted_operator_configuration(),
240        );
241        assert!(set.is_ok() && region.is_ok());
242        if let (Ok(set), Ok(region)) = (set, region) {
243            let policies = [
244                EndpointPolicy::fixed(a),
245                set,
246                EndpointPolicy::region_derived(region),
247                EndpointPolicy::acknowledged_custom(custom),
248            ];
249            let mut policies = policies.into_iter();
250            assert!(policies.next().is_some_and(|policy| policy.kind()
251                == EndpointPolicyKind::Fixed
252                && policy.admits(a)));
253            assert!(
254                policies
255                    .next()
256                    .is_some_and(|policy| policy.admits(a) && policy.admits(b))
257            );
258            assert!(policies.next().is_some_and(|policy| policy.admits(b)));
259            assert!(policies.next().is_some_and(|policy| policy.admits(a)));
260            for policy in [
261                EndpointPolicy::fixed(a),
262                set,
263                EndpointPolicy::region_derived(region),
264                EndpointPolicy::acknowledged_custom(custom),
265            ] {
266                assert_eq!(
267                    policy.verify(other),
268                    Err(EndpointPolicyError::DestinationMismatch)
269                );
270            }
271        }
272    }
273
274    #[test]
275    fn official_sets_and_regions_are_bounded_and_canonical() {
276        assert_eq!(
277            EndpointPolicy::official_set(&[]),
278            Err(EndpointPolicyError::EmptyOfficialSet)
279        );
280        let endpoint = identity("region.example");
281        assert!(endpoint.is_ok());
282        let Ok(endpoint) = endpoint else { return };
283        for region in ["", "EU-west-1", "-eu", "eu-", "eu_west"] {
284            assert_eq!(
285                RegionEndpoint::new(region, endpoint),
286                Err(EndpointPolicyError::InvalidRegion)
287            );
288        }
289    }
290
291    #[test]
292    fn policy_accepts_non_static_provider_owned_identity() {
293        let host_bytes = *b"runtime-region.example";
294        let host = core::str::from_utf8(&host_bytes);
295        assert!(host.is_ok());
296        let Ok(host) = host else { return };
297        let endpoint = identity(host);
298        assert!(endpoint.is_ok());
299        if let Ok(endpoint) = endpoint {
300            let regional = RegionEndpoint::new("runtime-1", endpoint);
301            assert!(regional.is_ok());
302            if let Ok(regional) = regional {
303                assert!(EndpointPolicy::region_derived(regional).admits(endpoint));
304            }
305        }
306    }
307
308    fn identity(
309        host: &str,
310    ) -> Result<EndpointIdentity<'_>, crate::transport::EndpointIdentityError> {
311        EndpointIdentity::new(EndpointScheme::Https, host, 443, "/v1")
312    }
313}