use anyhow::Result; use iam_client::client::IamClientConfig; use iam_client::IamClient; use iam_types::{PolicyBinding, Principal, PrincipalRef, Scope}; use tonic::metadata::MetadataValue; use tonic::Request; 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, 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("fleet-scheduler"); client.create_binding(&binding).await?; Ok(()) }