feat: resolve upstream auth at runtime

This commit is contained in:
a.tolmachev
2026-04-07 01:00:24 +03:00
parent 191e749b14
commit da94d308de
17 changed files with 702 additions and 78 deletions
+100 -5
View File
@@ -31,7 +31,9 @@ use crank_registry::{
UpdateWorkspaceRequest, UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, UsageQuery,
UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord,
};
use crank_runtime::{PreparedRequest, RuntimeError, RuntimeExecutor, RuntimeOperation};
use crank_runtime::{
PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, RuntimeOperation, SecretCrypto,
};
use crank_schema::Schema;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
@@ -46,7 +48,6 @@ use crate::{
hash_session_secret, verify_password,
},
error::ApiError,
secret_crypto::SecretCrypto,
storage::LocalArtifactStorage,
};
@@ -1918,8 +1919,18 @@ impl AdminService {
}
};
let resolved_auth = self
.resolve_operation_auth(workspace_id, &runtime.execution_config)
.await;
let started_at = std::time::Instant::now();
match self.runtime.execute(&runtime, &payload.input).await {
match match resolved_auth {
Ok(resolved_auth) => {
self.runtime
.execute_with_auth(&runtime, &payload.input, resolved_auth.as_ref())
.await
}
Err(error) => Err(error),
} {
Ok(output) => {
let duration_ms =
u64::try_from(started_at.elapsed().as_millis()).unwrap_or(u64::MAX);
@@ -1973,6 +1984,78 @@ impl AdminService {
}
}
async fn resolve_operation_auth(
&self,
workspace_id: &WorkspaceId,
execution_config: &crank_core::ExecutionConfig,
) -> Result<Option<ResolvedAuth>, RuntimeError> {
let Some(auth_profile_id) = execution_config.auth_profile_ref.as_ref() else {
return Ok(None);
};
let auth_profile = self
.registry
.get_auth_profile(workspace_id, auth_profile_id)
.await
.map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?
.ok_or_else(|| RuntimeError::MissingAuthProfile {
auth_profile_id: auth_profile_id.as_str().to_owned(),
})?;
self.resolve_auth_profile(workspace_id, &auth_profile)
.await
.map(Some)
}
async fn resolve_auth_profile(
&self,
workspace_id: &WorkspaceId,
auth_profile: &AuthProfile,
) -> Result<ResolvedAuth, RuntimeError> {
let mut secrets = BTreeMap::new();
let used_at = now_string().map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
for secret_id in auth_profile.config.secret_ids() {
let secret = self
.registry
.get_secret(workspace_id, secret_id)
.await
.map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?
.ok_or_else(|| RuntimeError::MissingSecret {
secret_id: secret_id.as_str().to_owned(),
})?;
let version = self
.registry
.get_current_secret_version(workspace_id, secret_id)
.await
.map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?
.ok_or_else(|| RuntimeError::MissingSecretVersion {
secret_id: secret_id.as_str().to_owned(),
version: secret.secret.current_version,
})?;
let plaintext = self
.secret_crypto
.decrypt(&version.secret_version.ciphertext)?;
self.registry
.touch_secret(workspace_id, secret_id, &used_at)
.await
.map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
secrets.insert(secret_id.clone(), plaintext);
}
ResolvedAuth::from_profile(auth_profile, &secrets)
}
#[instrument(skip(self))]
pub async fn list_auth_profiles(
&self,
@@ -2032,7 +2115,10 @@ impl AdminService {
updated_at: now,
last_used_at: None,
};
let ciphertext = self.secret_crypto.encrypt(&payload.value)?;
let ciphertext = self
.secret_crypto
.encrypt(&payload.value)
.map_err(|error| ApiError::internal(error.to_string()))?;
self.registry
.create_secret(CreateSecretRequest {
@@ -2061,7 +2147,10 @@ impl AdminService {
}
let now = now_string()?;
let ciphertext = self.secret_crypto.encrypt(&payload.value)?;
let ciphertext = self
.secret_crypto
.encrypt(&payload.value)
.map_err(|error| ApiError::internal(error.to_string()))?;
self.registry
.rotate_secret(RotateSecretRequest {
workspace_id,
@@ -4200,6 +4289,12 @@ fn runtime_error_code(error: &RuntimeError) -> &'static str {
RuntimeError::MissingStreamingConfig { .. } => "streaming_config_error",
RuntimeError::UnsupportedExecutionMode { .. } => "streaming_mode_error",
RuntimeError::InvalidStreamingPayload { .. } => "streaming_payload_error",
RuntimeError::MissingAuthProfile { .. } => "auth_profile_not_found",
RuntimeError::MissingSecret { .. } | RuntimeError::MissingSecretVersion { .. } => {
"secret_not_found"
}
RuntimeError::InvalidAuthSecretValue { .. } => "secret_value_error",
RuntimeError::SecretCrypto { .. } => "secret_crypto_error",
}
}