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

24 KiB
Raw Permalink Blame History

性能优化和安全加固指南

📈 性能优化

1. Rust应用优化

编译优化

# Cargo.toml
[profile.release]
opt-level = 3              # 最高优化级别
lto = true                 # 链接时优化
codegen-units = 1          # 单个代码生成单元
panic = 'abort'            # 减少二进制大小
strip = true               # 移除调试符号

内存优化

// 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")
}

异步优化

// 使用批量操作减少数据库调用
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优化配置

-- 性能优化设置
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;

查询优化

// 使用预编译语句
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缓存实现

// 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)
}

内存缓存

// 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优化

连接池和超时配置

// 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. 应用层安全

输入验证和清理

// 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;")
}

密码安全

// 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安全

// 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配置

// 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)
}

安全头中间件

// 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. 数据库安全

连接安全

// 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. 监控和审计

安全事件监控

// 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
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"]

系统安全配置

#!/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