auth: cut over community machine access to agent keys

This commit is contained in:
a.tolmachev
2026-05-03 15:42:07 +00:00
parent c7b33930db
commit 6fd62df2a8
18 changed files with 768 additions and 302 deletions
+59 -26
View File
@@ -1279,7 +1279,7 @@ impl AdminService {
.collect();
let operations = self.list_operations(workspace_id).await?;
let agents = self.list_agents(workspace_id).await?;
let platform_api_keys = self.list_platform_api_keys(workspace_id).await?;
let platform_api_keys = self.registry.list_platform_api_keys(workspace_id).await?;
Ok(WorkspaceExportResponse {
workspace,
@@ -1314,27 +1314,51 @@ impl AdminService {
}
#[instrument(skip(self))]
pub async fn list_platform_api_keys(
pub async fn list_agent_platform_api_keys(
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
) -> Result<Vec<PlatformApiKeyRecord>, ApiError> {
self.ensure_workspace_exists(workspace_id).await?;
Ok(self.registry.list_platform_api_keys(workspace_id).await?)
self.registry
.get_agent_summary(workspace_id, agent_id)
.await?
.ok_or_else(|| {
ApiError::not_found_with_context(
format!("agent {} was not found", agent_id.as_str()),
json!({ "agent_id": agent_id.as_str() }),
)
})?;
Ok(self
.registry
.list_platform_api_keys_for_agent(workspace_id, agent_id)
.await?)
}
#[instrument(skip(self, payload), fields(workspace_id = %workspace_id.as_str(), key_name = %payload.name))]
pub async fn create_platform_api_key(
#[instrument(skip(self, payload), fields(workspace_id = %workspace_id.as_str(), agent_id = %agent_id.as_str(), key_name = %payload.name))]
pub async fn create_agent_platform_api_key(
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
payload: PlatformApiKeyPayload,
) -> Result<CreatedPlatformApiKeyResponse, ApiError> {
self.ensure_workspace_exists(workspace_id).await?;
self.registry
.get_agent_summary(workspace_id, agent_id)
.await?
.ok_or_else(|| {
ApiError::not_found_with_context(
format!("agent {} was not found", agent_id.as_str()),
json!({ "agent_id": agent_id.as_str() }),
)
})?;
let secret = generate_access_secret("crk");
let api_key = PlatformApiKeyRecord {
api_key: PlatformApiKey {
id: PlatformApiKeyId::new(new_prefixed_id("pk")),
workspace_id: workspace_id.clone(),
agent_id: Some(agent_id.clone()),
name: payload.name,
prefix: secret.chars().take(16).collect(),
scopes: payload.scopes,
@@ -1354,26 +1378,33 @@ impl AdminService {
Ok(CreatedPlatformApiKeyResponse { api_key, secret })
}
#[instrument(skip(self), fields(workspace_id = %workspace_id.as_str(), key_id = %key_id.as_str()))]
pub async fn revoke_platform_api_key(
#[instrument(skip(self), fields(workspace_id = %workspace_id.as_str(), agent_id = %agent_id.as_str(), key_id = %key_id.as_str()))]
pub async fn revoke_agent_platform_api_key(
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
key_id: &PlatformApiKeyId,
) -> Result<(), ApiError> {
self.registry
.revoke_platform_api_key(workspace_id, key_id, &OffsetDateTime::now_utc())
.revoke_platform_api_key_for_agent(
workspace_id,
agent_id,
key_id,
&OffsetDateTime::now_utc(),
)
.await?;
Ok(())
}
#[instrument(skip(self), fields(workspace_id = %workspace_id.as_str(), key_id = %key_id.as_str()))]
pub async fn delete_platform_api_key(
#[instrument(skip(self), fields(workspace_id = %workspace_id.as_str(), agent_id = %agent_id.as_str(), key_id = %key_id.as_str()))]
pub async fn delete_agent_platform_api_key(
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
key_id: &PlatformApiKeyId,
) -> Result<(), ApiError> {
self.registry
.delete_platform_api_key(workspace_id, key_id)
.delete_platform_api_key_for_agent(workspace_id, agent_id, key_id)
.await?;
Ok(())
}
@@ -2818,11 +2849,17 @@ impl AdminService {
.into_iter()
.map(|item| (item.agent_id.as_str().to_owned(), item.calls_total))
.collect::<BTreeMap<_, _>>();
let key_count = self
let key_counts = self
.registry
.list_platform_api_keys(workspace_id)
.await?
.len();
.into_iter()
.fold(BTreeMap::new(), |mut counts, record| {
if let Some(agent_id) = record.api_key.agent_id {
*counts.entry(agent_id.as_str().to_owned()).or_insert(0usize) += 1;
}
counts
});
let mut items = Vec::with_capacity(summaries.len());
for summary in summaries {
@@ -2837,7 +2874,7 @@ impl AdminService {
items.push(AgentSummaryView {
operation_count: operation_ids.len(),
operation_ids,
key_count,
key_count: key_counts.get(summary.id.as_str()).copied().unwrap_or(0),
calls_today: calls_today.get(summary.id.as_str()).copied().unwrap_or(0),
mcp_endpoint: agent_mcp_endpoint(
workspace.workspace.slug.as_str(),
@@ -2891,7 +2928,7 @@ impl AdminService {
.await?;
let key_count = self
.registry
.list_platform_api_keys(workspace_id)
.list_platform_api_keys_for_agent(workspace_id, agent_id)
.await?
.len();
@@ -3981,6 +4018,7 @@ impl AdminService {
self.ensure_demo_platform_api_key(
workspace_id,
&AgentId::new(revops_agent.id.clone()),
"Web Console Demo Key",
vec![PlatformApiKeyScope::Read, PlatformApiKeyScope::Write],
false,
@@ -3988,6 +4026,7 @@ impl AdminService {
.await?;
self.ensure_demo_platform_api_key(
workspace_id,
&AgentId::new(support_agent.id.clone()),
"Readonly Export Key",
vec![PlatformApiKeyScope::Read],
true,
@@ -4045,14 +4084,6 @@ impl AdminService {
},
)
.await?;
self.ensure_demo_platform_api_key(
&workspace_id,
"Growth Readonly Key",
vec![PlatformApiKeyScope::Read],
false,
)
.await?;
Ok(())
}
@@ -4109,20 +4140,22 @@ impl AdminService {
async fn ensure_demo_platform_api_key(
&self,
workspace_id: &WorkspaceId,
agent_id: &AgentId,
name: &str,
scopes: Vec<PlatformApiKeyScope>,
revoke: bool,
) -> Result<(), ApiError> {
let existing = self
.list_platform_api_keys(workspace_id)
.list_agent_platform_api_keys(workspace_id, agent_id)
.await?
.into_iter()
.find(|record| record.api_key.name == name);
let key = match existing {
Some(record) => record,
None => {
self.create_platform_api_key(
self.create_agent_platform_api_key(
workspace_id,
agent_id,
PlatformApiKeyPayload {
name: name.to_owned(),
scopes,
@@ -4134,7 +4167,7 @@ impl AdminService {
};
if revoke && key.api_key.status != PlatformApiKeyStatus::Revoked {
self.revoke_platform_api_key(workspace_id, &key.api_key.id)
self.revoke_agent_platform_api_key(workspace_id, agent_id, &key.api_key.id)
.await?;
}