feat(cluster): implement distributed clustering with etcd coordination

- Integrate etcd-client for distributed coordination and leader election
- Add remote client macros with proper formatting for all services
- Implement RequestMetrics for tracking RPC performance and errors
- Add rate limiting mechanism across all service endpoints
- Create ElectionRequest and ElectionResult message types for leader election
- Add role management with primary/replica switching capabilities
- Implement health checker with automatic failover detection
- Add repository count metrics for cluster monitoring
- Update Cargo.toml with etcd-client and dashmap dependencies
- Modify RepoEntry to include read_only flag for replica handling
- Implement should_accept_election logic to prevent duplicate elections
- Add RoleChangedEvent handling for cluster role updates
This commit is contained in:
zhenyi
2026-06-08 14:31:29 +08:00
parent d243dce027
commit 8f472a0443
37 changed files with 4691 additions and 83 deletions
+84
View File
@@ -0,0 +1,84 @@
//! Hook content sanitization.
//!
//! Validates custom hook scripts to prevent dangerous commands.
use crate::error::{GitError, GitResult};
/// Commands/patterns that are never allowed in custom hook scripts.
const FORBIDDEN_PATTERNS: &[&str] = &[
"rm -rf",
"rm -r /",
"chmod 777",
"chmod 666",
"mkfs",
"dd if=",
":(){ :|:& };:", // fork bomb
"> /dev/sda",
"curl -o /",
"wget -O /",
"/etc/passwd",
"/etc/shadow",
"shutdown",
"reboot",
"init 0",
"init 6",
"poweroff",
"halt",
];
/// Maximum hook script size (64KB).
const MAX_HOOK_SIZE: usize = 65536;
/// Validate a custom hook script content for safety.
pub fn validate_hook_content(content: &str) -> GitResult<()> {
if content.is_empty() {
return Err(GitError::InvalidArgument(
"hook content cannot be empty".into(),
));
}
if content.len() > MAX_HOOK_SIZE {
return Err(GitError::InvalidArgument(format!(
"hook content too large (max {} bytes): {} bytes",
MAX_HOOK_SIZE,
content.len()
)));
}
let content_lower = content.to_lowercase();
for pattern in FORBIDDEN_PATTERNS {
if content_lower.contains(pattern) {
return Err(GitError::InvalidArgument(format!(
"hook content contains forbidden pattern: '{pattern}'"
)));
}
}
if content.contains('\0') {
return Err(GitError::InvalidArgument(
"hook content cannot contain null bytes".into(),
));
}
Ok(())
}
/// Validate a hook name (must be a recognized git hook name).
pub fn validate_hook_name(name: &str) -> GitResult<()> {
const VALID_HOOK_NAMES: &[&str] = &[
"pre-receive",
"update",
"post-receive",
"pre-applypatch",
"applypatch-msg",
"post-applypatch",
"pre-commit",
"prepare-commit-msg",
"commit-msg",
"post-commit",
"pre-auto-gc",
];
if !VALID_HOOK_NAMES.contains(&name) {
return Err(GitError::InvalidArgument(format!(
"invalid hook name: '{name}'. Must be one of: {}",
VALID_HOOK_NAMES.join(", ")
)));
}
Ok(())
}