Download cloud-forge/crates/cloud-iam/src/lib.rs from Snapkitty/tlm-jxcl-forge: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/Snapkitty/tlm-jxcl-forge/resolve/main/cloud-forge/crates/cloud-iam/src/lib.rs
- Command line
-
hf download hf://Snapkitty/tlm-jxcl-forge/cloud-forge/crates/cloud-iam/src/lib.rs
-
curl -L -o lib.rs https://huggingface.co/Snapkitty/tlm-jxcl-forge/resolve/main/cloud-forge/crates/cloud-iam/src/lib.rs
15.2 kB
| // Copyright (C) 2026 ERRANT DIGITAL INSTITUTE OF TECHNOLOGY (SEIT NGO) % SNAPKITTY COLLECTIVE | |
| // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial | |
| // See LICENSE-AGPL and LICENSE-COMMERCIAL in this repository for license terms. | |
| //! `IamService`: the fifth composed service in this workspace, and the | |
| //! first to compose Phase 13's IAM surface -- `cloud-credentials`, | |
| //! `cloud-session`, `cloud-policy-document` -- together with | |
| //! `cloud-policy` and `cloud-events`. No new primitive, only | |
| //! composition, exactly the shape `cloud-compute`/`cloud-storage`/ | |
| //! `cloud-database`/`cloud-messaging` (Phases 8-11) each used for their | |
| //! own primitive sets. | |
| //! | |
| //! ## The one real business rule this phase adds | |
| //! | |
| //! [`IamService::assume_role`] and [`IamService::federate`] both call | |
| //! `Policy::authorize` *before* creating a session -- the first time in | |
| //! this entire workspace that `cloud-policy` (Phase 1) governs an | |
| //! operation *within the IAM surface itself*, rather than gating | |
| //! another service's resource operation (a `launch`, a `create_volume`, | |
| //! a `create_database`). The mechanism is identical to every other | |
| //! service's own `policy.authorize(...)` call; what's new is the | |
| //! target: a principal now needs permission to assume a role or | |
| //! federate in at all, not just permission to act once it has a | |
| //! session. | |
| //! | |
| //! ## No accounts, regions, or quota -- deliberately | |
| //! | |
| //! Every prior composed service (`cloud-compute`, `cloud-storage`, | |
| //! `cloud-database`, `cloud-messaging`) validated an `AccountId` and | |
| //! `RegionId` and reserved a `cloud-quota` unit before creating | |
| //! anything, because each of those services provisions real, | |
| //! region-scoped infrastructure. `IamService` does neither: real IAM | |
| //! systems (AWS's own included) are inherently **global**, not | |
| //! region-scoped, and a credential or session isn't infrastructure | |
| //! with a finite regional capacity to exhaust -- there is nothing here | |
| //! for an `AccountId`/`RegionId` check or a quota reservation to | |
| //! usefully gate. Skipping them is a deliberate design choice | |
| //! mirroring real IAM's own shape, not an oversight. | |
| //! | |
| //! ## No `Arn`, deliberately | |
| //! | |
| //! Every prior composed service also registered a resolvable `Arn` for | |
| //! everything it created. Credentials and sessions are identity | |
| //! artifacts, not provisioned resources with a canonical name external | |
| //! callers look up -- mirroring real IAM's own inconsistent resource | |
| //! model, where a role or user has an ARN but an access key or an STS | |
| //! session token does not. `cloud-resource-registry` stays unused here. | |
| use cloud_credentials::{Credential, CredentialStore}; | |
| use cloud_errors::CloudError; | |
| use cloud_events::EventLog; | |
| use cloud_identity::Principal; | |
| use cloud_policy::Policy; | |
| use cloud_session::{Session, SessionStore}; | |
| use cloud_types::{ResourceId, Timestamp}; | |
| /// The IAM service: a policy, the credential and session stores it | |
| /// governs, and an audit trail of what happened. | |
| pub struct IamService { | |
| policy: Policy, | |
| credentials: CredentialStore, | |
| sessions: SessionStore, | |
| events: EventLog, | |
| } | |
| impl IamService { | |
| pub fn new(policy: Policy) -> Self { | |
| IamService { | |
| policy, | |
| credentials: CredentialStore::new(), | |
| sessions: SessionStore::new(), | |
| events: EventLog::new(), | |
| } | |
| } | |
| pub fn events(&self) -> &EventLog { | |
| &self.events | |
| } | |
| pub fn credential(&self, id: &ResourceId) -> Option<&Credential> { | |
| self.credentials.get(id) | |
| } | |
| pub fn session(&self, token: &ResourceId) -> Option<&Session> { | |
| self.sessions.get(token) | |
| } | |
| pub fn is_session_valid(&self, token: &ResourceId, now: Timestamp) -> bool { | |
| self.sessions.is_valid(token, now) | |
| } | |
| /// The general-purpose IAM decision: would `principal` be allowed | |
| /// to perform `action` on `resource` right now? | |
| pub fn authorize( | |
| &self, | |
| principal: &Principal, | |
| action: &str, | |
| resource: &str, | |
| ) -> Result<(), CloudError> { | |
| self.policy.authorize(principal, action, resource) | |
| } | |
| /// Replaces the active policy with one parsed from a JSON-shaped | |
| /// policy document. Every subsequent `authorize`/`assume_role`/ | |
| /// `federate` call is governed by this new policy immediately. | |
| pub fn load_policy_document(&mut self, json: &str) -> Result<(), CloudError> { | |
| self.policy = cloud_policy_document::from_json(json)?; | |
| Ok(()) | |
| } | |
| /// Serializes the active policy back to a JSON-shaped document. | |
| pub fn export_policy_document(&self) -> String { | |
| cloud_policy_document::to_json(&self.policy) | |
| } | |
| pub fn create_credential( | |
| &mut self, | |
| id: ResourceId, | |
| principal: Principal, | |
| created_at: Timestamp, | |
| ) -> Result<&Credential, CloudError> { | |
| self.credentials | |
| .create(id.clone(), principal.clone(), created_at)?; | |
| self.events.append( | |
| id.clone(), | |
| "credential.created", | |
| created_at, | |
| format!("principal={principal}"), | |
| ); | |
| Ok(self | |
| .credentials | |
| .get(&id) | |
| .expect("just created under this exact id")) | |
| } | |
| pub fn activate_credential( | |
| &mut self, | |
| id: &ResourceId, | |
| now: Timestamp, | |
| ) -> Result<(), CloudError> { | |
| self.credentials.activate(id)?; | |
| self.events | |
| .append(id.clone(), "credential.activated", now, String::new()); | |
| Ok(()) | |
| } | |
| pub fn deactivate_credential( | |
| &mut self, | |
| id: &ResourceId, | |
| now: Timestamp, | |
| ) -> Result<(), CloudError> { | |
| self.credentials.deactivate(id)?; | |
| self.events | |
| .append(id.clone(), "credential.deactivated", now, String::new()); | |
| Ok(()) | |
| } | |
| pub fn delete_credential(&mut self, id: &ResourceId, now: Timestamp) -> Result<(), CloudError> { | |
| self.credentials.delete(id)?; | |
| self.events | |
| .append(id.clone(), "credential.deleted", now, String::new()); | |
| Ok(()) | |
| } | |
| /// Assumes `role` as `principal`, producing a new session. Refused | |
| /// unless the active policy authorizes `principal` to | |
| /// `"sts:assume-role"` on `role` -- checked before any session is | |
| /// created, so a denied attempt leaves the session store untouched. | |
| pub fn assume_role( | |
| &mut self, | |
| token: ResourceId, | |
| principal: Principal, | |
| role: ResourceId, | |
| now: Timestamp, | |
| duration_millis: u64, | |
| ) -> Result<&Session, CloudError> { | |
| self.policy | |
| .authorize(&principal, "sts:assume-role", role.as_str())?; | |
| self.sessions | |
| .assume_role(token.clone(), principal.clone(), role, now, duration_millis)?; | |
| self.events.append( | |
| token.clone(), | |
| "session.assumed-role", | |
| now, | |
| format!("principal={principal}"), | |
| ); | |
| Ok(self | |
| .sessions | |
| .get(&token) | |
| .expect("just created under this exact token")) | |
| } | |
| /// Federates `principal` in from an external identity provider, | |
| /// producing a new session. Refused unless the active policy | |
| /// authorizes `principal` to `"sts:federate"` -- checked before any | |
| /// session is created. | |
| pub fn federate( | |
| &mut self, | |
| token: ResourceId, | |
| principal: Principal, | |
| external_id: impl Into<String>, | |
| now: Timestamp, | |
| duration_millis: u64, | |
| ) -> Result<&Session, CloudError> { | |
| self.policy.authorize(&principal, "sts:federate", "*")?; | |
| self.sessions.federate( | |
| token.clone(), | |
| principal.clone(), | |
| external_id, | |
| now, | |
| duration_millis, | |
| )?; | |
| self.events.append( | |
| token.clone(), | |
| "session.federated", | |
| now, | |
| format!("principal={principal}"), | |
| ); | |
| Ok(self | |
| .sessions | |
| .get(&token) | |
| .expect("just created under this exact token")) | |
| } | |
| pub fn revoke_session(&mut self, token: &ResourceId, now: Timestamp) -> Result<(), CloudError> { | |
| self.sessions.revoke(token)?; | |
| self.events | |
| .append(token.clone(), "session.revoked", now, String::new()); | |
| Ok(()) | |
| } | |
| } | |
| mod tests { | |
| use super::*; | |
| use cloud_policy::{Effect, PrincipalMatcher, Statement}; | |
| use cloud_session::SessionSource; | |
| fn allow_all_policy() -> Policy { | |
| let mut policy = Policy::new(); | |
| policy.add_statement(Statement { | |
| effect: Effect::Allow, | |
| principals: PrincipalMatcher::Any, | |
| actions: vec!["*".to_string()], | |
| resources: vec!["*".to_string()], | |
| }); | |
| policy | |
| } | |
| fn deny_all_policy() -> Policy { | |
| Policy::new() | |
| } | |
| fn principal(name: &str) -> Principal { | |
| Principal::User(ResourceId::new(name).unwrap()) | |
| } | |
| fn id(s: &str) -> ResourceId { | |
| ResourceId::new(s).unwrap() | |
| } | |
| fn create_credential_starts_it_active_and_logs_an_event() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| iam.create_credential(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| assert!(iam.credential(&id("key-1")).is_some()); | |
| assert_eq!(iam.events().len(), 1); | |
| } | |
| fn credential_lifecycle_delegates_correctly() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| iam.create_credential(id("key-1"), principal("alice"), Timestamp::from_millis(0)) | |
| .unwrap(); | |
| iam.deactivate_credential(&id("key-1"), Timestamp::from_millis(1000)) | |
| .unwrap(); | |
| iam.activate_credential(&id("key-1"), Timestamp::from_millis(2000)) | |
| .unwrap(); | |
| iam.delete_credential(&id("key-1"), Timestamp::from_millis(3000)) | |
| .unwrap(); | |
| assert!(iam.credential(&id("key-1")).is_none()); | |
| assert_eq!(iam.events().len(), 4); | |
| } | |
| fn assume_role_is_refused_without_authorization_and_creates_no_session() { | |
| let mut iam = IamService::new(deny_all_policy()); | |
| let err = iam | |
| .assume_role( | |
| id("tok-1"), | |
| principal("alice"), | |
| id("role-admin"), | |
| Timestamp::from_millis(0), | |
| 3_600_000, | |
| ) | |
| .unwrap_err(); | |
| assert!(matches!(err, CloudError::PolicyDenied { .. })); | |
| assert!(iam.session(&id("tok-1")).is_none()); | |
| } | |
| fn assume_role_succeeds_when_authorized() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| let session = iam | |
| .assume_role( | |
| id("tok-1"), | |
| principal("alice"), | |
| id("role-admin"), | |
| Timestamp::from_millis(0), | |
| 3_600_000, | |
| ) | |
| .unwrap(); | |
| assert_eq!( | |
| session.source(), | |
| &SessionSource::AssumedRole { | |
| role: id("role-admin") | |
| } | |
| ); | |
| assert!(iam.is_session_valid(&id("tok-1"), Timestamp::from_millis(0))); | |
| } | |
| fn federate_is_refused_without_authorization_and_creates_no_session() { | |
| let mut iam = IamService::new(deny_all_policy()); | |
| let err = iam | |
| .federate( | |
| id("tok-1"), | |
| principal("alice"), | |
| "external-idp-1", | |
| Timestamp::from_millis(0), | |
| 3_600_000, | |
| ) | |
| .unwrap_err(); | |
| assert!(matches!(err, CloudError::PolicyDenied { .. })); | |
| assert!(iam.session(&id("tok-1")).is_none()); | |
| } | |
| fn federate_succeeds_when_authorized() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| iam.federate( | |
| id("tok-1"), | |
| principal("alice"), | |
| "external-idp-1", | |
| Timestamp::from_millis(0), | |
| 3_600_000, | |
| ) | |
| .unwrap(); | |
| assert!(iam.session(&id("tok-1")).is_some()); | |
| } | |
| fn revoke_session_invalidates_it_immediately() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| iam.assume_role( | |
| id("tok-1"), | |
| principal("alice"), | |
| id("role-admin"), | |
| Timestamp::from_millis(0), | |
| 3_600_000, | |
| ) | |
| .unwrap(); | |
| iam.revoke_session(&id("tok-1"), Timestamp::from_millis(500)) | |
| .unwrap(); | |
| assert!(!iam.is_session_valid(&id("tok-1"), Timestamp::from_millis(500))); | |
| } | |
| fn revoke_session_of_an_unknown_token_is_not_found() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| let err = iam | |
| .revoke_session(&id("ghost"), Timestamp::from_millis(0)) | |
| .unwrap_err(); | |
| assert!(matches!(err, CloudError::NotFound { .. })); | |
| } | |
| fn authorize_delegates_directly_to_the_active_policy() { | |
| let iam = IamService::new(allow_all_policy()); | |
| assert!(iam | |
| .authorize(&principal("alice"), "compute:describe", "i-1") | |
| .is_ok()); | |
| let iam_denied = IamService::new(deny_all_policy()); | |
| assert!(iam_denied | |
| .authorize(&principal("alice"), "compute:describe", "i-1") | |
| .is_err()); | |
| } | |
| fn loading_a_policy_document_replaces_the_active_policy_immediately() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| // Freshly loaded policy denies everything (no statements). | |
| iam.load_policy_document(r#"{"Statement": []}"#).unwrap(); | |
| let err = iam | |
| .assume_role( | |
| id("tok-1"), | |
| principal("alice"), | |
| id("role-admin"), | |
| Timestamp::from_millis(0), | |
| 1000, | |
| ) | |
| .unwrap_err(); | |
| assert!(matches!(err, CloudError::PolicyDenied { .. })); | |
| } | |
| fn exported_policy_document_round_trips_through_load() { | |
| let iam = IamService::new(allow_all_policy()); | |
| let exported = iam.export_policy_document(); | |
| let mut iam2 = IamService::new(deny_all_policy()); | |
| iam2.load_policy_document(&exported).unwrap(); | |
| // The re-loaded policy is the original allow-all policy, so | |
| // assume_role now succeeds on iam2 too. | |
| iam2.assume_role( | |
| id("tok-1"), | |
| principal("alice"), | |
| id("role-admin"), | |
| Timestamp::from_millis(0), | |
| 1000, | |
| ) | |
| .unwrap(); | |
| } | |
| fn load_policy_document_rejects_malformed_json_and_keeps_the_old_policy() { | |
| let mut iam = IamService::new(allow_all_policy()); | |
| let err = iam.load_policy_document("not json").unwrap_err(); | |
| assert!(matches!(err, CloudError::InvalidFormat { .. })); | |
| // The old (allow-all) policy is still active. | |
| iam.assume_role( | |
| id("tok-1"), | |
| principal("alice"), | |
| id("role-admin"), | |
| Timestamp::from_millis(0), | |
| 1000, | |
| ) | |
| .unwrap(); | |
| } | |
| } | |