//! Shared authentication helpers for k8shost services. use std::sync::Arc; use anyhow::Result; use iam_client::client::IamClientConfig; use iam_client::IamClient; pub use iam_service_auth::AuthService; use iam_types::{PolicyBinding, Principal, PrincipalRef, Scope}; use tonic::metadata::MetadataValue; use tonic::{Request, Status}; pub use iam_service_auth::{ get_tenant_context, resolve_tenant_ids_from_context, resource_for_tenant, }; /// gRPC interceptor that authenticates requests and injects tenant context. pub async fn auth_interceptor( auth_service: Arc, mut req: Request<()>, ) -> Result, Status> { let tenant_context = auth_service.authenticate_request(&req).await?; req.extensions_mut().insert(tenant_context); Ok(req) } pub fn authorized_request(message: T, token: &str) -> Request { let mut req = Request::new(message); let header = format!("Bearer {}", token); let value = MetadataValue::try_from(header.as_str()).expect("valid bearer token metadata"); req.metadata_mut().insert("authorization", value); req } pub async fn issue_controller_token( iam_server_addr: &str, principal_id: &str, org_id: &str, project_id: &str, ) -> Result { let mut config = IamClientConfig::new(iam_server_addr).with_timeout(5000); if iam_server_addr.starts_with("http://") || !iam_server_addr.starts_with("https://") { config = config.without_tls(); } let client = IamClient::connect(config).await?; let principal_ref = PrincipalRef::service_account(principal_id); let principal = match client.get_principal(&principal_ref).await? { Some(existing) => existing, None => { client .create_service_account(principal_id, principal_id, org_id, project_id) .await? } }; ensure_project_admin_binding(&client, &principal, org_id, project_id).await?; let scope = Scope::project(project_id, org_id); client .issue_token( &principal, vec!["roles/ProjectAdmin".to_string()], scope, 3600, ) .await .map_err(Into::into) } async fn ensure_project_admin_binding( client: &IamClient, principal: &Principal, org_id: &str, project_id: &str, ) -> Result<()> { let scope = Scope::project(project_id, org_id); let bindings = client .list_bindings_for_principal(&principal.to_ref()) .await?; let already_bound = bindings .iter() .any(|binding| binding.role_ref == "roles/ProjectAdmin" && binding.scope == scope); if already_bound { return Ok(()); } let binding = PolicyBinding::new( format!("{}-project-admin-{}-{}", principal.id, org_id, project_id), principal.to_ref(), "roles/ProjectAdmin", scope, ) .with_created_by("k8shost-controller"); client.create_binding(&binding).await?; Ok(()) }