360 lines · 13.5 KB · Rust
RawDownload
use std::collections::HashMap; use anyhow::{Context, Result, anyhow};use async_trait::async_trait;use cynic::{MutationBuilder, QueryBuilder};use warp_graphql::managed_secrets::{ManagedSecret, ManagedSecretType};use warp_graphql::mutations::create_managed_secret::{    CreateManagedSecret, CreateManagedSecretInput, CreateManagedSecretResult,    CreateManagedSecretVariables,};use warp_graphql::mutations::delete_managed_secret::{    DeleteManagedSecret, DeleteManagedSecretInput, DeleteManagedSecretResult,    DeleteManagedSecretVariables,};use warp_graphql::mutations::issue_task_identity_token::{    IssueTaskIdentityToken, IssueTaskIdentityTokenInput, IssueTaskIdentityTokenResult,    IssueTaskIdentityTokenVariables,};use warp_graphql::mutations::update_managed_secret::{    UpdateManagedSecret, UpdateManagedSecretInput, UpdateManagedSecretResult,    UpdateManagedSecretVariables,};use warp_graphql::object_permissions::{Owner, OwnerType};use warp_graphql::queries::list_harness_auth_secrets::{    ListHarnessAuthSecrets, ListHarnessAuthSecretsInput, ListHarnessAuthSecretsVariables,};use warp_graphql::queries::list_managed_secrets::{    ListManagedSecrets, ListManagedSecretsVariables, ManagedSecretsInput, ManagedSecretsResult,};use warp_graphql::queries::managed_secret_config::{    GetManagedSecretConfig, GetManagedSecretConfigVariables, UserResult,};use warp_graphql::queries::task_secrets::{    ManagedSecretValue, TaskSecrets, TaskSecretsInput, TaskSecretsResult, TaskSecretsVariables,};pub use warp_managed_secrets::client::{ManagedSecretConfigs, ManagedSecretsClient};use warp_managed_secrets::client::{SecretOwner, TaskIdentityToken}; use super::ServerApi;use crate::server::graphql::{get_request_context, get_user_facing_error_message};use crate::server::team_scope::RequestTeamScope; pub(crate) type AppManagedSecretManager =    warp_managed_secrets::ManagedSecretManager<RequestTeamScope>;pub(crate) type AppManagedSecretsClient = dyn ManagedSecretsClient<RequestScope = RequestTeamScope>; #[cfg_attr(not(target_family = "wasm"), async_trait)]#[cfg_attr(target_family = "wasm", async_trait(?Send))]impl ManagedSecretsClient for ServerApi {    type RequestScope = RequestTeamScope;     async fn get_managed_secret_configs(        &self,        request_scope: &Self::RequestScope,    ) -> Result<ManagedSecretConfigs> {        let variables = GetManagedSecretConfigVariables {            request_context: get_request_context(),        };        let operation = GetManagedSecretConfig::build(variables);        let response = self            .send_graphql_request_for_team(operation, *request_scope)            .await?;         match response.user {            UserResult::UserOutput(output) => {                let mut team_configs = HashMap::new();                for workspace in output.user.workspaces {                    for team in workspace.teams {                        if let Some(config) = team.managed_secrets {                            // DO NOT inline the `insert` call into the `debug_assert!` macro. It will get compiled out in release builds.                            let prior_config = team_configs.insert(team.uid.into_inner(), config);                            debug_assert!(                                prior_config.is_none(),                                "Duplicate team UID returned from server"                            );                        }                    }                }                Ok(ManagedSecretConfigs {                    user_secrets: output.user.managed_secrets,                    team_secrets: team_configs,                })            }            UserResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            UserResult::Unknown => Err(anyhow!(                "Unknown error while getting managed secret configs"            )),        }    }     async fn create_managed_secret(        &self,        request_scope: &Self::RequestScope,        owner: SecretOwner,        name: String,        secret_type: ManagedSecretType,        encrypted_value: String,        description: Option<String>,    ) -> Result<ManagedSecret> {        let graphql_owner = match owner {            SecretOwner::CurrentUser => Owner {                type_: OwnerType::User,                uid: None,            },            SecretOwner::Team { team_uid } => Owner {                type_: OwnerType::Team,                uid: Some(cynic::Id::new(team_uid)),            },        };         let variables = CreateManagedSecretVariables {            input: CreateManagedSecretInput {                description,                encrypted_value,                name,                owner: graphql_owner,                type_: secret_type,            },            request_context: get_request_context(),        };        let operation = CreateManagedSecret::build(variables);        let response = self            .send_graphql_request_for_team(operation, *request_scope)            .await?;         match response.create_managed_secret {            CreateManagedSecretResult::CreateManagedSecretOutput(output) => {                Ok(output.managed_secret)            }            CreateManagedSecretResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            CreateManagedSecretResult::Unknown => {                Err(anyhow!("Unknown error while creating managed secret"))            }        }    }     async fn delete_managed_secret(        &self,        request_scope: &Self::RequestScope,        owner: SecretOwner,        name: String,    ) -> Result<()> {        let graphql_owner = match owner {            SecretOwner::CurrentUser => Owner {                type_: OwnerType::User,                uid: None,            },            SecretOwner::Team { team_uid } => Owner {                type_: OwnerType::Team,                uid: Some(cynic::Id::new(team_uid)),            },        };         let variables = DeleteManagedSecretVariables {            input: DeleteManagedSecretInput {                name,                owner: graphql_owner,            },            request_context: get_request_context(),        };        let operation = DeleteManagedSecret::build(variables);        let response = self            .send_graphql_request_for_team(operation, *request_scope)            .await?;         match response.delete_managed_secret {            DeleteManagedSecretResult::DeleteManagedSecretOutput(_) => Ok(()),            DeleteManagedSecretResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            DeleteManagedSecretResult::Unknown => {                Err(anyhow!("Unknown error while deleting managed secret"))            }        }    }     async fn update_managed_secret(        &self,        request_scope: &Self::RequestScope,        owner: SecretOwner,        name: String,        encrypted_value: Option<String>,        description: Option<String>,    ) -> Result<ManagedSecret> {        let graphql_owner = match owner {            SecretOwner::CurrentUser => Owner {                type_: OwnerType::User,                uid: None,            },            SecretOwner::Team { team_uid } => Owner {                type_: OwnerType::Team,                uid: Some(cynic::Id::new(team_uid)),            },        };         let variables = UpdateManagedSecretVariables {            input: UpdateManagedSecretInput {                name,                owner: graphql_owner,                encrypted_value,                description,            },            request_context: get_request_context(),        };        let operation = UpdateManagedSecret::build(variables);        let response = self            .send_graphql_request_for_team(operation, *request_scope)            .await?;         match response.update_managed_secret {            UpdateManagedSecretResult::UpdateManagedSecretOutput(output) => {                Ok(output.managed_secret)            }            UpdateManagedSecretResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            UpdateManagedSecretResult::Unknown => {                Err(anyhow!("Unknown error while updating managed secret"))            }        }    }     async fn list_harness_auth_secrets(        &self,        request_scope: &Self::RequestScope,        harness: warp_graphql::ai::AgentHarness,    ) -> Result<Vec<ManagedSecret>> {        let Some(harness_input) = Option::<            warp_graphql::queries::list_harness_auth_secrets::AgentHarnessInput,        >::from(harness) else {            return Ok(vec![]);        };        let variables = ListHarnessAuthSecretsVariables {            input: ListHarnessAuthSecretsInput {                harness: harness_input,            },            request_context: get_request_context(),        };        let operation = ListHarnessAuthSecrets::build(variables);        let response = self            .send_graphql_request_for_team(operation, *request_scope)            .await?;         match response.harness_auth_secrets {            warp_graphql::queries::list_harness_auth_secrets::HarnessAuthSecretsResult::HarnessAuthSecretsOutput(output) => {                Ok(output.managed_secrets)            }            warp_graphql::queries::list_harness_auth_secrets::HarnessAuthSecretsResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            warp_graphql::queries::list_harness_auth_secrets::HarnessAuthSecretsResult::Unknown => {                Err(anyhow!("Unknown error while listing harness auth secrets"))            }        }    }     async fn list_secrets(        &self,        request_scope: Option<&Self::RequestScope>,    ) -> Result<Vec<ManagedSecret>> {        let variables = ListManagedSecretsVariables {            // Pagination over managed secrets is not yet supported.            input: ManagedSecretsInput { cursor: None },            request_context: get_request_context(),        };        let operation = ListManagedSecrets::build(variables);        let response = match request_scope {            Some(request_scope) => {                self.send_graphql_request_for_team(operation, *request_scope)                    .await?            }            None => self.send_graphql_request(operation, None).await?,        };         match response.managed_secrets {            ManagedSecretsResult::ManagedSecretsOutput(output) => Ok(output.managed_secrets),            ManagedSecretsResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            ManagedSecretsResult::Unknown => {                Err(anyhow!("Unknown error while listing managed secrets"))            }        }    }     async fn get_task_secrets(        &self,        task_id: String,        workload_token: String,    ) -> Result<HashMap<String, ManagedSecretValue>> {        let variables = TaskSecretsVariables {            input: TaskSecretsInput {                task_id: cynic::Id::new(task_id),                workload_token,            },            request_context: get_request_context(),        };        let operation = TaskSecrets::build(variables);        let response = self.send_graphql_request(operation, None).await?;         match response.task_secrets {            TaskSecretsResult::TaskSecretsOutput(output) => {                let mut secrets = HashMap::new();                for entry in output.secrets {                    secrets.insert(entry.name, entry.value);                }                Ok(secrets)            }            TaskSecretsResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            TaskSecretsResult::Unknown => Err(anyhow!("Unknown error while getting task secrets")),        }    }     async fn issue_task_identity_token(        &self,        options: warp_managed_secrets::client::IdentityTokenOptions,    ) -> Result<TaskIdentityToken> {        let requested_duration_seconds = options            .requested_duration            .as_secs()            .try_into()            .context("Requested duration out of bounds")?;        let variables = IssueTaskIdentityTokenVariables {            input: IssueTaskIdentityTokenInput {                audience: options.audience,                requested_duration_seconds,                subject_template: Some(options.subject_template.into_vec()),            },            request_context: get_request_context(),        };        let operation = IssueTaskIdentityToken::build(variables);        let response = self.send_graphql_request(operation, None).await?;         match response.issue_task_identity_token {            IssueTaskIdentityTokenResult::IssueTaskIdentityTokenOutput(output) => {                Ok(TaskIdentityToken {                    token: output.token,                    expires_at: output.expires_at.utc(),                    issuer: output.issuer,                })            }            IssueTaskIdentityTokenResult::UserFacingError(error) => {                Err(anyhow!(get_user_facing_error_message(error)))            }            IssueTaskIdentityTokenResult::Unknown => {                Err(anyhow!("Unknown error while issuing task identity token"))            }        }    }} #[cfg(test)]#[path = "managed_secrets_tests.rs"]mod tests;