Files
rust-user-api/docs/performance-security-guide.md
enoch bb9d7a869d
Some checks failed
Deploy to Production / Run Tests (push) Failing after 16m35s
Deploy to Production / Security Scan (push) Has been skipped
Deploy to Production / Build Docker Image (push) Has been skipped
Deploy to Production / Deploy to Staging (push) Has been skipped
Deploy to Production / Deploy to Production (push) Has been skipped
Deploy to Production / Notify Results (push) Successful in 31s
feat: 完成Rust User API完整开发
 新功能:
- SQLite数据库集成和持久化存储
- 数据库迁移系统和版本管理
- API分页功能和高效查询
- 用户搜索和过滤机制
- 完整的RBAC角色权限系统
- 结构化日志记录和系统监控
- API限流和多层安全防护
- Docker容器化和生产部署配置

🔒 安全特性:
- JWT认证和授权
- 限流和防暴力破解
- 安全头和CORS配置
- 输入验证和XSS防护
- 审计日志和安全监控

📊 监控和运维:
- Prometheus指标收集
- 健康检查和系统监控
- 自动化备份和恢复
- 完整的运维文档和脚本
- CI/CD流水线配置

🚀 部署支持:
- 多环境Docker配置
- 生产环境部署指南
- 性能优化和安全加固
- 故障排除和应急响应
- 自动化运维脚本

📚 文档完善:
- API使用文档
- 部署检查清单
- 运维操作手册
- 性能和安全指南
- 故障排除指南
2025-08-07 16:03:32 +08:00

873 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 性能优化和安全加固指南
## 📈 性能优化
### 1. Rust应用优化
#### 编译优化
```toml
# Cargo.toml
[profile.release]
opt-level = 3 # 最高优化级别
lto = true # 链接时优化
codegen-units = 1 # 单个代码生成单元
panic = 'abort' # 减少二进制大小
strip = true # 移除调试符号
```
#### 内存优化
```rust
// src/config/performance.rs
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct PerformanceConfig {
pub max_connections: usize,
pub connection_timeout: u64,
pub request_timeout: u64,
pub worker_threads: usize,
pub max_blocking_threads: usize,
}
impl Default for PerformanceConfig {
fn default() -> Self {
Self {
max_connections: 1000,
connection_timeout: 30,
request_timeout: 30,
worker_threads: num_cpus::get(),
max_blocking_threads: 512,
}
}
}
// 连接池优化
pub fn optimize_database_pool() -> sqlx::SqlitePool {
sqlx::sqlite::SqlitePoolOptions::new()
.max_connections(20)
.min_connections(5)
.acquire_timeout(std::time::Duration::from_secs(30))
.idle_timeout(std::time::Duration::from_secs(600))
.max_lifetime(std::time::Duration::from_secs(1800))
.build("sqlite://production.db")
.expect("Failed to create database pool")
}
```
#### 异步优化
```rust
// 使用批量操作减少数据库调用
pub async fn batch_create_users(
pool: &SqlitePool,
users: Vec<CreateUserRequest>,
) -> Result<Vec<User>, sqlx::Error> {
let mut tx = pool.begin().await?;
let mut created_users = Vec::new();
for user in users {
let created_user = sqlx::query_as!(
User,
"INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?) RETURNING *",
user.username,
user.email,
user.password_hash
)
.fetch_one(&mut *tx)
.await?;
created_users.push(created_user);
}
tx.commit().await?;
Ok(created_users)
}
// 使用流式处理大量数据
use futures::StreamExt;
pub async fn stream_users(pool: &SqlitePool) -> impl Stream<Item = Result<User, sqlx::Error>> {
sqlx::query_as!(User, "SELECT * FROM users")
.fetch(pool)
}
```
### 2. 数据库优化
#### SQLite优化配置
```sql
-- 性能优化设置
PRAGMA journal_mode = WAL; -- 写前日志模式
PRAGMA synchronous = NORMAL; -- 平衡性能和安全
PRAGMA cache_size = 1000000; -- 1GB缓存
PRAGMA temp_store = memory; -- 临时表存储在内存
PRAGMA mmap_size = 268435456; -- 256MB内存映射
PRAGMA optimize; -- 优化查询计划
-- 索引优化
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
-- 复合索引
CREATE INDEX IF NOT EXISTS idx_users_role_created ON users(role, created_at);
CREATE INDEX IF NOT EXISTS idx_users_active_email ON users(is_active, email) WHERE is_active = 1;
```
#### 查询优化
```rust
// 使用预编译语句
pub struct OptimizedQueries {
find_user_by_email: sqlx::query::Query<'static, sqlx::Sqlite, sqlx::sqlite::SqliteArguments<'static>>,
find_users_paginated: sqlx::query::Query<'static, sqlx::Sqlite, sqlx::sqlite::SqliteArguments<'static>>,
}
impl OptimizedQueries {
pub fn new() -> Self {
Self {
find_user_by_email: sqlx::query_as!(
User,
"SELECT * FROM users WHERE email = ? LIMIT 1"
),
find_users_paginated: sqlx::query_as!(
User,
"SELECT * FROM users ORDER BY created_at DESC LIMIT ? OFFSET ?"
),
}
}
}
// 使用连接查询减少数据库往返
pub async fn get_user_with_profile(
pool: &SqlitePool,
user_id: i64,
) -> Result<UserWithProfile, sqlx::Error> {
sqlx::query_as!(
UserWithProfile,
r#"
SELECT
u.id, u.username, u.email, u.created_at,
p.bio, p.avatar_url, p.location
FROM users u
LEFT JOIN user_profiles p ON u.id = p.user_id
WHERE u.id = ?
"#,
user_id
)
.fetch_one(pool)
.await
}
```
### 3. 缓存策略
#### Redis缓存实现
```rust
// src/cache/redis.rs
use redis::{Client, Connection, RedisResult};
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub struct RedisCache {
client: Client,
}
impl RedisCache {
pub fn new(redis_url: &str) -> RedisResult<Self> {
let client = Client::open(redis_url)?;
Ok(Self { client })
}
pub async fn get<T>(&self, key: &str) -> RedisResult<Option<T>>
where
T: for<'de> Deserialize<'de>,
{
let mut conn = self.client.get_async_connection().await?;
let value: Option<String> = redis::cmd("GET").arg(key).query_async(&mut conn).await?;
match value {
Some(json) => Ok(Some(serde_json::from_str(&json).map_err(|_| {
redis::RedisError::from((redis::ErrorKind::TypeError, "JSON parse error"))
})?)),
None => Ok(None),
}
}
pub async fn set<T>(&self, key: &str, value: &T, ttl: Duration) -> RedisResult<()>
where
T: Serialize,
{
let mut conn = self.client.get_async_connection().await?;
let json = serde_json::to_string(value).map_err(|_| {
redis::RedisError::from((redis::ErrorKind::TypeError, "JSON serialize error"))
})?;
redis::cmd("SETEX")
.arg(key)
.arg(ttl.as_secs())
.arg(json)
.query_async(&mut conn)
.await
}
}
// 缓存装饰器
pub async fn cached_get_user(
cache: &RedisCache,
pool: &SqlitePool,
user_id: i64,
) -> Result<User, AppError> {
let cache_key = format!("user:{}", user_id);
// 尝试从缓存获取
if let Ok(Some(user)) = cache.get::<User>(&cache_key).await {
return Ok(user);
}
// 从数据库获取
let user = get_user_by_id(pool, user_id).await?;
// 存入缓存
let _ = cache.set(&cache_key, &user, Duration::from_secs(300)).await;
Ok(user)
}
```
#### 内存缓存
```rust
// src/cache/memory.rs
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
pub struct MemoryCache<T> {
data: Arc<RwLock<HashMap<String, CacheEntry<T>>>>,
default_ttl: Duration,
}
struct CacheEntry<T> {
value: T,
expires_at: Instant,
}
impl<T: Clone> MemoryCache<T> {
pub fn new(default_ttl: Duration) -> Self {
Self {
data: Arc::new(RwLock::new(HashMap::new())),
default_ttl,
}
}
pub async fn get(&self, key: &str) -> Option<T> {
let data = self.data.read().await;
if let Some(entry) = data.get(key) {
if entry.expires_at > Instant::now() {
return Some(entry.value.clone());
}
}
None
}
pub async fn set(&self, key: String, value: T) {
let expires_at = Instant::now() + self.default_ttl;
let entry = CacheEntry { value, expires_at };
let mut data = self.data.write().await;
data.insert(key, entry);
}
pub async fn cleanup_expired(&self) {
let mut data = self.data.write().await;
let now = Instant::now();
data.retain(|_, entry| entry.expires_at > now);
}
}
```
### 4. HTTP优化
#### 连接池和超时配置
```rust
// src/server/config.rs
use axum::http::HeaderValue;
use tower::ServiceBuilder;
use tower_http::{
compression::CompressionLayer,
cors::CorsLayer,
timeout::TimeoutLayer,
trace::TraceLayer,
};
pub fn create_optimized_app() -> Router {
Router::new()
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CompressionLayer::new())
.layer(TimeoutLayer::new(Duration::from_secs(30)))
.layer(
CorsLayer::new()
.allow_origin("https://yourdomain.com".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
.allow_headers([AUTHORIZATION, CONTENT_TYPE])
.max_age(Duration::from_secs(3600))
)
)
}
// 连接保持配置
pub fn create_server() -> Result<Server<AddrIncoming, Router>, Box<dyn std::error::Error>> {
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
Server::bind(&addr)
.http1_keepalive(true)
.http1_half_close(true)
.http2_keep_alive_interval(Some(Duration::from_secs(30)))
.http2_keep_alive_timeout(Duration::from_secs(10))
.serve(app.into_make_service())
}
```
## 🔒 安全加固
### 1. 应用层安全
#### 输入验证和清理
```rust
// src/validation/security.rs
use regex::Regex;
use once_cell::sync::Lazy;
static EMAIL_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap()
});
static USERNAME_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap()
});
pub fn validate_email(email: &str) -> Result<(), ValidationError> {
if email.len() > 254 {
return Err(ValidationError::new("email_too_long"));
}
if !EMAIL_REGEX.is_match(email) {
return Err(ValidationError::new("invalid_email_format"));
}
Ok(())
}
pub fn validate_username(username: &str) -> Result<(), ValidationError> {
if !USERNAME_REGEX.is_match(username) {
return Err(ValidationError::new("invalid_username"));
}
// 检查是否包含敏感词
let forbidden_words = ["admin", "root", "system", "api"];
if forbidden_words.iter().any(|&word| username.to_lowercase().contains(word)) {
return Err(ValidationError::new("forbidden_username"));
}
Ok(())
}
// SQL注入防护
pub fn sanitize_sql_input(input: &str) -> String {
input
.replace("'", "''")
.replace("\"", "\"\"")
.replace(";", "")
.replace("--", "")
.replace("/*", "")
.replace("*/", "")
}
// XSS防护
pub fn sanitize_html(input: &str) -> String {
input
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#x27;")
.replace("/", "&#x2F;")
}
```
#### 密码安全
```rust
// src/auth/password.rs
use argon2::{Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use argon2::password_hash::{rand_core::OsRng, SaltString};
use zxcvbn::zxcvbn;
pub struct PasswordSecurity;
impl PasswordSecurity {
pub fn hash_password(password: &str) -> Result<String, argon2::password_hash::Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2.hash_password(password.as_bytes(), &salt)?;
Ok(password_hash.to_string())
}
pub fn verify_password(password: &str, hash: &str) -> Result<bool, argon2::password_hash::Error> {
let parsed_hash = PasswordHash::new(hash)?;
let argon2 = Argon2::default();
Ok(argon2.verify_password(password.as_bytes(), &parsed_hash).is_ok())
}
pub fn check_password_strength(password: &str) -> Result<(), ValidationError> {
if password.len() < 8 {
return Err(ValidationError::new("password_too_short"));
}
if password.len() > 128 {
return Err(ValidationError::new("password_too_long"));
}
// 使用zxcvbn检查密码强度
let estimate = zxcvbn(password, &[]).unwrap();
if estimate.score() < 3 {
return Err(ValidationError::new("password_too_weak"));
}
// 检查字符类型
let has_lower = password.chars().any(|c| c.is_lowercase());
let has_upper = password.chars().any(|c| c.is_uppercase());
let has_digit = password.chars().any(|c| c.is_numeric());
let has_special = password.chars().any(|c| "!@#$%^&*()_+-=[]{}|;:,.<>?".contains(c));
let char_types = [has_lower, has_upper, has_digit, has_special]
.iter()
.filter(|&&x| x)
.count();
if char_types < 3 {
return Err(ValidationError::new("password_insufficient_complexity"));
}
Ok(())
}
}
```
#### JWT安全
```rust
// src/auth/jwt.rs
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub sub: String,
pub exp: usize,
pub iat: usize,
pub iss: String,
pub aud: String,
pub jti: String,
pub role: String,
}
pub struct JwtSecurity {
encoding_key: EncodingKey,
decoding_key: DecodingKey,
validation: Validation,
}
impl JwtSecurity {
pub fn new(secret: &str) -> Self {
let mut validation = Validation::new(Algorithm::HS256);
validation.set_issuer(&["rust-user-api"]);
validation.set_audience(&["api-users"]);
validation.leeway = 60; // 1分钟时钟偏差容忍
Self {
encoding_key: EncodingKey::from_secret(secret.as_ref()),
decoding_key: DecodingKey::from_secret(secret.as_ref()),
validation,
}
}
pub fn create_token(&self, user_id: &str, role: &str) -> Result<String, jsonwebtoken::errors::Error> {
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as usize;
let exp = now + 3600; // 1小时过期
let claims = Claims {
sub: user_id.to_string(),
exp,
iat: now,
iss: "rust-user-api".to_string(),
aud: "api-users".to_string(),
jti: uuid::Uuid::new_v4().to_string(), // JWT ID防重放
role: role.to_string(),
};
encode(&Header::default(), &claims, &self.encoding_key)
}
pub fn verify_token(&self, token: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
let token_data = decode::<Claims>(token, &self.decoding_key, &self.validation)?;
Ok(token_data.claims)
}
}
```
### 2. 网络层安全
#### TLS配置
```rust
// src/server/tls.rs
use rustls::{Certificate, PrivateKey, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::fs::File;
use std::io::BufReader;
pub fn load_tls_config(cert_path: &str, key_path: &str) -> Result<ServerConfig, Box<dyn std::error::Error>> {
let cert_file = &mut BufReader::new(File::open(cert_path)?);
let key_file = &mut BufReader::new(File::open(key_path)?);
let cert_chain = certs(cert_file)?
.into_iter()
.map(Certificate)
.collect();
let mut keys: Vec<PrivateKey> = pkcs8_private_keys(key_file)?
.into_iter()
.map(PrivateKey)
.collect();
if keys.is_empty() {
return Err("No private key found".into());
}
let config = ServerConfig::builder()
.with_safe_default_cipher_suites()
.with_safe_default_kx_groups()
.with_safe_default_protocol_versions()?
.with_no_client_auth()
.with_single_cert(cert_chain, keys.remove(0))?;
Ok(config)
}
```
#### 安全头中间件
```rust
// src/middleware/security_headers.rs
use axum::{
http::{header, HeaderMap, HeaderValue, Request, Response},
middleware::Next,
response::IntoResponse,
};
pub async fn security_headers<B>(
request: Request<B>,
next: Next<B>,
) -> impl IntoResponse {
let mut response = next.run(request).await;
let headers = response.headers_mut();
// HSTS
headers.insert(
header::STRICT_TRANSPORT_SECURITY,
HeaderValue::from_static("max-age=31536000; includeSubDomains; preload"),
);
// CSP
headers.insert(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"),
);
// X-Frame-Options
headers.insert(
header::X_FRAME_OPTIONS,
HeaderValue::from_static("DENY"),
);
// X-Content-Type-Options
headers.insert(
header::X_CONTENT_TYPE_OPTIONS,
HeaderValue::from_static("nosniff"),
);
// X-XSS-Protection
headers.insert(
HeaderValue::from_static("x-xss-protection"),
HeaderValue::from_static("1; mode=block"),
);
// Referrer Policy
headers.insert(
HeaderValue::from_static("referrer-policy"),
HeaderValue::from_static("strict-origin-when-cross-origin"),
);
// Permissions Policy
headers.insert(
HeaderValue::from_static("permissions-policy"),
HeaderValue::from_static("geolocation=(), microphone=(), camera=()"),
);
response
}
```
### 3. 数据库安全
#### 连接安全
```rust
// src/database/security.rs
use sqlx::{sqlite::SqliteConnectOptions, ConnectOptions, SqlitePool};
use std::str::FromStr;
pub async fn create_secure_pool(database_url: &str) -> Result<SqlitePool, sqlx::Error> {
let mut options = SqliteConnectOptions::from_str(database_url)?
.create_if_missing(true)
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.synchronous(sqlx::sqlite::SqliteSynchronous::Normal)
.busy_timeout(std::time::Duration::from_secs(30))
.pragma("cache_size", "1000000")
.pragma("temp_store", "memory")
.pragma("mmap_size", "268435456");
// 禁用不安全的功能
options = options
.pragma("trusted_schema", "OFF")
.pragma("defensive", "ON");
// 设置日志级别
options.log_statements(log::LevelFilter::Debug);
SqlitePool::connect_with(options).await
}
// 数据加密
pub fn encrypt_sensitive_data(data: &str, key: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
use rand::Rng;
let cipher = Aes256Gcm::new(Key::from_slice(key));
let nonce_bytes: [u8; 12] = rand::thread_rng().gen();
let nonce = Nonce::from_slice(&nonce_bytes);
let ciphertext = cipher.encrypt(nonce, data.as_bytes())?;
// 组合nonce和密文
let mut result = nonce_bytes.to_vec();
result.extend_from_slice(&ciphertext);
Ok(base64::encode(result))
}
pub fn decrypt_sensitive_data(encrypted_data: &str, key: &[u8]) -> Result<String, Box<dyn std::error::Error>> {
use aes_gcm::{Aes256Gcm, Key, Nonce};
use aes_gcm::aead::{Aead, NewAead};
let data = base64::decode(encrypted_data)?;
if data.len() < 12 {
return Err("Invalid encrypted data".into());
}
let (nonce_bytes, ciphertext) = data.split_at(12);
let cipher = Aes256Gcm::new(Key::from_slice(key));
let nonce = Nonce::from_slice(nonce_bytes);
let plaintext = cipher.decrypt(nonce, ciphertext)?;
Ok(String::from_utf8(plaintext)?)
}
```
### 4. 监控和审计
#### 安全事件监控
```rust
// src/security/monitoring.rs
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Serialize, Deserialize)]
pub struct SecurityEvent {
pub event_type: SecurityEventType,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub source_ip: String,
pub user_id: Option<String>,
pub details: HashMap<String, String>,
pub severity: SecuritySeverity,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum SecurityEventType {
LoginAttempt,
LoginFailure,
BruteForceDetected,
SuspiciousActivity,
UnauthorizedAccess,
DataBreach,
SystemIntrusion,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum SecuritySeverity {
Low,
Medium,
High,
Critical,
}
pub struct SecurityMonitor {
events: Arc<RwLock<Vec<SecurityEvent>>>,
alert_thresholds: HashMap<SecurityEventType, usize>,
}
impl SecurityMonitor {
pub fn new() -> Self {
let mut thresholds = HashMap::new();
thresholds.insert(SecurityEventType::LoginFailure, 5);
thresholds.insert(SecurityEventType::BruteForceDetected, 1);
thresholds.insert(SecurityEventType::UnauthorizedAccess, 3);
Self {
events: Arc::new(RwLock::new(Vec::new())),
alert_thresholds: thresholds,
}
}
pub async fn log_event(&self, event: SecurityEvent) {
let mut events = self.events.write().await;
events.push(event.clone());
// 检查是否需要触发告警
if self.should_alert(&event).await {
self.send_alert(&event).await;
}
// 清理旧事件保留最近1000个
if events.len() > 1000 {
events.drain(0..events.len() - 1000);
}
}
async fn should_alert(&self, event: &SecurityEvent) -> bool {
if let Some(&threshold) = self.alert_thresholds.get(&event.event_type) {
let events = self.events.read().await;
let recent_events = events
.iter()
.filter(|e| {
e.event_type == event.event_type
&& e.source_ip == event.source_ip
&& e.timestamp > chrono::Utc::now() - chrono::Duration::hours(1)
})
.count();
return recent_events >= threshold;
}
matches!(event.severity, SecuritySeverity::Critical)
}
async fn send_alert(&self, event: &SecurityEvent) {
// 发送告警通知
log::error!("Security Alert: {:?}", event);
// 这里可以集成邮件、短信、Slack等通知方式
// send_email_alert(event).await;
// send_slack_alert(event).await;
}
}
```
### 5. 部署安全
#### Docker安全配置
```dockerfile
# 安全的Dockerfile
FROM rust:1.88-slim as builder
# 创建非特权用户
RUN groupadd -r rustuser && useradd -r -g rustuser rustuser
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
# 安装安全更新
RUN apt-get update && apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
ca-certificates \
sqlite3 \
libssl3 \
curl && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean
# 创建非特权用户
RUN groupadd -r apiuser && useradd -r -g apiuser apiuser
# 创建应用目录
RUN mkdir -p /app/data /app/logs && \
chown -R apiuser:apiuser /app
# 复制二进制文件
COPY --from=builder /app/target/release/rust-user-api /app/
COPY --from=builder /app/migrations /app/migrations/
# 设置权限
RUN chmod +x /app/rust-user-api && \
chown apiuser:apiuser /app/rust-user-api
# 切换到非特权用户
USER apiuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["/app/rust-user-api"]
```
#### 系统安全配置
```bash
#!/bin/bash
# security-hardening.sh
# 系统安全加固脚本
# 1. 更新系统
apt update && apt upgrade -y
# 2. 安装安全工具
apt install -y fail2ban ufw rkhunter chkrootkit
# 3. 配置防火墙
ufw default deny incoming
ufw default allow outgoing
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# 4. 配置fail2ban
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxret