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
✨ 新功能: - SQLite数据库集成和持久化存储 - 数据库迁移系统和版本管理 - API分页功能和高效查询 - 用户搜索和过滤机制 - 完整的RBAC角色权限系统 - 结构化日志记录和系统监控 - API限流和多层安全防护 - Docker容器化和生产部署配置 🔒 安全特性: - JWT认证和授权 - 限流和防暴力破解 - 安全头和CORS配置 - 输入验证和XSS防护 - 审计日志和安全监控 📊 监控和运维: - Prometheus指标收集 - 健康检查和系统监控 - 自动化备份和恢复 - 完整的运维文档和脚本 - CI/CD流水线配置 🚀 部署支持: - 多环境Docker配置 - 生产环境部署指南 - 性能优化和安全加固 - 故障排除和应急响应 - 自动化运维脚本 📚 文档完善: - API使用文档 - 部署检查清单 - 运维操作手册 - 性能和安全指南 - 故障排除指南
873 lines
24 KiB
Markdown
873 lines
24 KiB
Markdown
# 性能优化和安全加固指南
|
||
|
||
## 📈 性能优化
|
||
|
||
### 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("<", "<")
|
||
.replace(">", ">")
|
||
.replace("\"", """)
|
||
.replace("'", "'")
|
||
.replace("/", "/")
|
||
}
|
||
```
|
||
|
||
#### 密码安全
|
||
```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 |