Усилить безопасность и надёжность выполнения операций
This commit is contained in:
@@ -10,7 +10,7 @@ use crank_community_auth::PasswordIdentityProvider;
|
||||
use crank_registry::{PostgresPoolConfig, PostgresRegistry};
|
||||
use crank_runtime::{
|
||||
RequestRateLimitConfig, RequestRateLimiter, RuntimeCacheConfig, RuntimeCacheStores,
|
||||
RuntimeLimits, SecretCrypto, community_default,
|
||||
RuntimeLimits, SecretCrypto,
|
||||
};
|
||||
use sqlx::postgres::PgConnectOptions;
|
||||
use tokio::net::TcpListener;
|
||||
@@ -56,7 +56,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cache_stores = RuntimeCacheStores::from_config(&cache_config).await?;
|
||||
let api_rate_limit = admin_api_rate_limit_config_from_env()?;
|
||||
let secret_crypto = SecretCrypto::new(&env::var("CRANK_MASTER_KEY")?)?;
|
||||
let runtime = community_default()
|
||||
let outbound_http_policy = crank_runtime::OutboundHttpPolicy::from_env()?;
|
||||
let runtime = crank_runtime::community_with_outbound_policy(outbound_http_policy.clone())
|
||||
.with_limits(runtime_limits)
|
||||
.with_response_cache(cache_stores.response.clone())
|
||||
.with_coordination_store(cache_stores.coordination.clone())
|
||||
@@ -70,6 +71,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
secret_crypto,
|
||||
runtime,
|
||||
)
|
||||
.with_outbound_http_policy(outbound_http_policy)
|
||||
.with_identity_provider(std::sync::Arc::new(identity_provider))
|
||||
.build();
|
||||
service.bootstrap_admin_user().await?;
|
||||
|
||||
@@ -15,7 +15,9 @@ use crank_registry::{
|
||||
AgentSummary, CreateInvocationLogRequest, OperationAgentRef, OperationSummary,
|
||||
OperationUsageSummary, PostgresRegistry, RegistryOperation, UsageBucket,
|
||||
};
|
||||
use crank_runtime::{PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, SecretCrypto};
|
||||
use crank_runtime::{
|
||||
OutboundHttpPolicy, PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, SecretCrypto,
|
||||
};
|
||||
use crank_schema::{Schema, SchemaKind};
|
||||
use serde_json::{Value, json};
|
||||
use sha2::{Digest, Sha256};
|
||||
@@ -38,8 +40,8 @@ mod workspaces;
|
||||
|
||||
use crate::{auth::AuthSettings, error::ApiError, storage::LocalArtifactStorage};
|
||||
use operation_validation::{
|
||||
validate_approval_policy, validate_idempotency_policy, validate_protocol_target,
|
||||
validate_response_cache_policy,
|
||||
validate_approval_policy, validate_execution_timeout, validate_idempotency_policy,
|
||||
validate_protocol_target, validate_response_cache_policy,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -53,6 +55,7 @@ pub struct AdminService {
|
||||
policy_engine: Arc<dyn PolicyEngine>,
|
||||
audit_sink: Arc<dyn AuditSink>,
|
||||
capability_profile: Arc<dyn CapabilityProfile>,
|
||||
outbound_http_policy: OutboundHttpPolicy,
|
||||
}
|
||||
|
||||
pub struct AdminServiceBuilder {
|
||||
@@ -65,6 +68,7 @@ pub struct AdminServiceBuilder {
|
||||
policy_engine: Option<Arc<dyn PolicyEngine>>,
|
||||
audit_sink: Option<Arc<dyn AuditSink>>,
|
||||
capability_profile: Option<Arc<dyn CapabilityProfile>>,
|
||||
outbound_http_policy: OutboundHttpPolicy,
|
||||
}
|
||||
|
||||
pub use crate::dto::*;
|
||||
@@ -139,6 +143,7 @@ impl AdminServiceBuilder {
|
||||
policy_engine: None,
|
||||
audit_sink: None,
|
||||
capability_profile: None,
|
||||
outbound_http_policy: OutboundHttpPolicy::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +152,11 @@ impl AdminServiceBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_outbound_http_policy(mut self, policy: OutboundHttpPolicy) -> Self {
|
||||
self.outbound_http_policy = policy;
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn with_policy_engine(mut self, policy_engine: Arc<dyn PolicyEngine>) -> Self {
|
||||
self.policy_engine = Some(policy_engine);
|
||||
@@ -183,6 +193,7 @@ impl AdminServiceBuilder {
|
||||
capability_profile: self
|
||||
.capability_profile
|
||||
.unwrap_or_else(|| Arc::new(CommunityCapabilityProfile)),
|
||||
outbound_http_policy: self.outbound_http_policy,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,6 +308,8 @@ impl AdminService {
|
||||
fn validate_operation_payload(&self, payload: &OperationPayload) -> Result<(), ApiError> {
|
||||
self.validate_operation_capabilities(payload.protocol, payload.security_level)?;
|
||||
validate_protocol_target(payload.protocol, &payload.target)?;
|
||||
self.validate_outbound_target(&payload.target)?;
|
||||
validate_execution_timeout(&payload.execution_config)?;
|
||||
validate_response_cache_policy(&payload.target, &payload.execution_config)?;
|
||||
validate_idempotency_policy(&payload.target, &payload.execution_config)?;
|
||||
validate_approval_policy(&payload.execution_config)?;
|
||||
@@ -308,6 +321,8 @@ impl AdminService {
|
||||
fn validate_registry_operation(&self, operation: &RegistryOperation) -> Result<(), ApiError> {
|
||||
self.validate_operation_capabilities(operation.protocol, operation.security_level)?;
|
||||
validate_protocol_target(operation.protocol, &operation.target)?;
|
||||
self.validate_outbound_target(&operation.target)?;
|
||||
validate_execution_timeout(&operation.execution_config)?;
|
||||
validate_response_cache_policy(&operation.target, &operation.execution_config)?;
|
||||
validate_idempotency_policy(&operation.target, &operation.execution_config)?;
|
||||
validate_approval_policy(&operation.execution_config)?;
|
||||
@@ -316,6 +331,15 @@ impl AdminService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_outbound_target(&self, target: &crank_core::Target) -> Result<(), ApiError> {
|
||||
match target {
|
||||
crank_core::Target::Rest(rest) => self
|
||||
.outbound_http_policy
|
||||
.validate_base_url(&rest.base_url)
|
||||
.map_err(|error| ApiError::validation(error.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_operation_capabilities(
|
||||
&self,
|
||||
protocol: Protocol,
|
||||
|
||||
@@ -3,6 +3,8 @@ use serde_json::json;
|
||||
|
||||
use crate::error::ApiError;
|
||||
|
||||
const MAX_OPERATION_TIMEOUT_MS: u64 = 300_000;
|
||||
|
||||
pub(super) fn validate_protocol_target(
|
||||
protocol: Protocol,
|
||||
target: &Target,
|
||||
@@ -16,6 +18,18 @@ pub(super) fn validate_protocol_target(
|
||||
Err(ApiError::validation("protocol and target kind must match"))
|
||||
}
|
||||
|
||||
pub(super) fn validate_execution_timeout(
|
||||
execution_config: &crank_core::ExecutionConfig,
|
||||
) -> Result<(), ApiError> {
|
||||
if !(1..=MAX_OPERATION_TIMEOUT_MS).contains(&execution_config.timeout_ms) {
|
||||
return Err(ApiError::validation_with_context(
|
||||
format!("operation timeout must be between 1 and {MAX_OPERATION_TIMEOUT_MS} ms"),
|
||||
json!({ "field": "execution_config.timeout_ms" }),
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn validate_response_cache_policy(
|
||||
target: &Target,
|
||||
execution_config: &crank_core::ExecutionConfig,
|
||||
@@ -150,7 +164,8 @@ mod tests {
|
||||
};
|
||||
|
||||
use super::{
|
||||
validate_approval_policy, validate_idempotency_policy, validate_response_cache_policy,
|
||||
validate_approval_policy, validate_execution_timeout, validate_idempotency_policy,
|
||||
validate_response_cache_policy,
|
||||
};
|
||||
|
||||
fn cacheable_execution_config() -> ExecutionConfig {
|
||||
@@ -198,6 +213,19 @@ mod tests {
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_zero_and_excessive_execution_timeouts() {
|
||||
let mut config = cacheable_execution_config();
|
||||
config.timeout_ms = 0;
|
||||
assert!(validate_execution_timeout(&config).is_err());
|
||||
|
||||
config.timeout_ms = 300_001;
|
||||
assert!(validate_execution_timeout(&config).is_err());
|
||||
|
||||
config.timeout_ms = 300_000;
|
||||
assert!(validate_execution_timeout(&config).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_response_cache_for_non_get_rest_operation() {
|
||||
let target = Target::Rest(RestTarget {
|
||||
|
||||
@@ -114,13 +114,16 @@ pub(super) fn test_service(
|
||||
auth_settings: AuthSettings,
|
||||
secret_crypto: SecretCrypto,
|
||||
) -> AdminService {
|
||||
let outbound_policy = crank_runtime::OutboundHttpPolicy::allowing_hosts(["127.0.0.1"]);
|
||||
let runtime = crank_runtime::community_with_outbound_policy(outbound_policy.clone()).build();
|
||||
AdminServiceBuilder::new(
|
||||
registry,
|
||||
storage_root,
|
||||
auth_settings,
|
||||
secret_crypto,
|
||||
crank_runtime::RuntimeExecutor::new(),
|
||||
runtime,
|
||||
)
|
||||
.with_outbound_http_policy(outbound_policy)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user