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使用文档 - 部署检查清单 - 运维操作手册 - 性能和安全指南 - 故障排除指南
24 KiB
24 KiB
性能优化和安全加固指南
📈 性能优化
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("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
.replace("/", "/")
}
密码安全
// 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