feat: 完成Rust User API完整开发
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
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使用文档 - 部署检查清单 - 运维操作手册 - 性能和安全指南 - 故障排除指南
This commit is contained in:
873
docs/performance-security-guide.md
Normal file
873
docs/performance-security-guide.md
Normal file
@@ -0,0 +1,873 @@
|
||||
# 性能优化和安全加固指南
|
||||
|
||||
## 📈 性能优化
|
||||
|
||||
### 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
|
Reference in New Issue
Block a user