feat: resolve upstream auth at runtime
This commit is contained in:
@@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user