Skip to content

HTTPS и TLS в Axum

Защита соединений с помощью HTTPS (HTTP Secure) и TLS (Transport Layer Security) является необходимым аспектом для современных веб-приложений. В этом разделе рассмотрим, как настроить и оптимизировать защищенные соединения в приложениях на Axum.

Содержание

Основы HTTPS и TLS

HTTPS - это протокол HTTP поверх TLS/SSL, который обеспечивает:

  • Шифрование данных между клиентом и сервером
  • Аутентификацию сервера для клиента
  • Защиту от атак "человек посередине"
  • Проверку целостности передаваемых данных

Настройка HTTPS сервера

Для настройки HTTPS в Axum необходимо использовать библиотеку rustls или native-tls.

Установка зависимостей

toml
[dependencies]
axum = "0.7.2"
tokio = { version = "1.32.0", features = ["full"] }
hyper = { version = "1.0.1", features = ["full"] }
hyper-util = { version = "0.1.1", features = ["tokio"] }
rustls = "0.21.7"
rustls-pemfile = "1.0.3"
tokio-rustls = "0.24.1"

Базовая настройка HTTPS

rust
use axum::{routing::get, Router};
use hyper::server::conn::http1;
use hyper_util::rt::TokioIo;
use rustls::{Certificate, PrivateKey, ServerConfig};
use std::{
    fs::File,
    io::BufReader,
    net::SocketAddr,
    path::{Path, PathBuf},
    sync::Arc,
};
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;

// Обработчик маршрута
async fn hello() -> &'static str {
    "Привет, HTTPS!"
}

// Загрузка сертификата и ключа
fn load_rustls_config() -> Result<ServerConfig, Box<dyn std::error::Error>> {
    // Загрузка сертификата
    let cert_file = File::open("path/to/cert.pem")?;
    let mut cert_reader = BufReader::new(cert_file);
    let certs = rustls_pemfile::certs(&mut cert_reader)?
        .into_iter()
        .map(Certificate)
        .collect();

    // Загрузка приватного ключа
    let key_file = File::open("path/to/key.pem")?;
    let mut key_reader = BufReader::new(key_file);
    // Приоритет на PKCS8 ключи
    let mut keys = rustls_pemfile::pkcs8_private_keys(&mut key_reader)?;
    
    if keys.is_empty() {
        // Попробовать RSA ключи, если PKCS8 не найдены
        key_reader = BufReader::new(File::open("path/to/key.pem")?);
        keys = rustls_pemfile::rsa_private_keys(&mut key_reader)?;
    }

    let Some(key) = keys.into_iter().next() else {
        return Err("Приватный ключ не найден".into());
    };
    
    // Создание конфигурации TLS
    let config = ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_single_cert(certs, PrivateKey(key))?;
    
    Ok(config)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Создание маршрутов
    let app = Router::new().route("/", get(hello));

    // Загрузка TLS конфигурации
    let tls_config = load_rustls_config()?;
    let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));

    // Привязка сервера к порту
    let addr = SocketAddr::from(([127, 0, 0, 1], 443));
    let listener = TcpListener::bind(addr).await?;
    println!("HTTPS сервер запущен на {}", addr);

    // Обработка входящих соединений
    loop {
        let (stream, addr) = listener.accept().await?;
        let acceptor = tls_acceptor.clone();
        let app = app.clone();

        tokio::spawn(async move {
            let tls_stream = match acceptor.accept(stream).await {
                Ok(tls_stream) => tls_stream,
                Err(err) => {
                    eprintln!("Ошибка установки TLS: {:?}", err);
                    return;
                }
            };
            
            let service = app.into_make_service_with_connect_info::<SocketAddr>();
            let connection = http1::Builder::new()
                .serve_connection(TokioIo::new(tls_stream), service)
                .await;
                
            if let Err(err) = connection {
                eprintln!("Ошибка соединения: {}", err);
            }
        });
    }
}

Автоматическое перенаправление с HTTP на HTTPS

Для автоматического перенаправления запросов с HTTP на HTTPS:

rust
use axum::{
    http::{StatusCode, Uri},
    response::{IntoResponse, Redirect},
    routing::get,
    Router,
};
use std::{net::SocketAddr, time::Duration};
use tokio::net::TcpListener;

// HTTP обработчик - перенаправляет на HTTPS
async fn redirect_http_to_https(uri: Uri) -> impl IntoResponse {
    let https_uri = format!("https://example.com{}", uri.path());
    Redirect::permanent(&https_uri)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Маршруты для HTTPS сервера
    let https_app = Router::new().route("/", get(hello));
    
    // Маршруты для HTTP сервера - все перенаправляют на HTTPS
    let http_app = Router::new().fallback(redirect_http_to_https);
    
    // Запуск HTTP сервера для перенаправления
    let http_addr = SocketAddr::from(([127, 0, 0, 1], 80));
    tokio::spawn(async move {
        let listener = TcpListener::bind(http_addr).await.unwrap();
        println!("HTTP сервер запущен на {}", http_addr);
        axum::serve(listener, http_app).await.unwrap();
    });
    
    // Запуск HTTPS сервера (как в предыдущем примере)
    // ...
    
    Ok(())
}

Let's Encrypt и автоматические сертификаты

Для автоматического получения и обновления сертификатов с помощью Let's Encrypt можно использовать библиотеку rustls-acme:

toml
[dependencies]
# ... предыдущие зависимости
rustls-acme = "0.7.6"
rust
use axum::{routing::get, Router};
use hyper::server::conn::http1;
use hyper_util::rt::TokioIo;
use rustls_acme::{
    AcmeConfig, ChallengeType, CertResolver, 
    ResolvesServerCertArc, rustls::ServerConfig
};
use std::{
    net::SocketAddr,
    sync::Arc,
    time::Duration,
};
use tokio::net::TcpListener;
use tokio_rustls::TlsAcceptor;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Создание маршрутов
    let app = Router::new().route("/", get(hello));

    // Настройка Let's Encrypt
    let domains = vec!["example.com".to_string(), "www.example.com".to_string()];
    let mut state = AcmeConfig::new(domains)
        .contact(vec!["mailto:admin@example.com".to_string()])
        .directory_lets_encrypt_production() // или .directory_lets_encrypt_staging() для тестирования
        .challenge_type(ChallengeType::Http01) // Тип проверки владения доменом
        .state();
        
    // Кэширование сертификатов
    state.persist_to_directory("./acme-cache");
    
    // Создание TLS акцептора с ACME
    let cert_resolver = CertResolver::new(state);
    let config = ServerConfig::builder()
        .with_safe_defaults()
        .with_no_client_auth()
        .with_cert_resolver(Arc::new(cert_resolver));
    
    let tls_acceptor = TlsAcceptor::from(Arc::new(config));

    // Привязка к порту 443
    let addr = SocketAddr::from(([0, 0, 0, 0], 443));
    let listener = TcpListener::bind(addr).await?;
    println!("HTTPS сервер запущен на {}", addr);

    // Обработка входящих соединений
    // ... (как в предыдущем примере)
}

HTTP/2 поддержка

Axum поддерживает HTTP/2 при использовании TLS. Для активации:

rust
use hyper::server::conn::http2;

// В основном цикле обработки соединений:
let service = app.into_make_service_with_connect_info::<SocketAddr>();
let connection = http2::Builder::new(
    hyper_util::rt::TokioExecutor::new(),
)
    .serve_connection(TokioIo::new(tls_stream), service)
    .await;

Для поддержки как HTTP/1.1, так и HTTP/2:

rust
use hyper::server::conn::http1;
use hyper::server::conn::http2;
use hyper_util::rt::TokioExecutor;

// Определение протокола
let io = TokioIo::new(tls_stream);
let protocol = match io.peek(5).await {
    Ok(buf) if buf.len() >= 5 => {
        if &buf[..2] == b"\x16\x03" {
            // TLS handshake, возможно HTTP/2
            "https"
        } else if &buf[..5] == b"PRI *" {
            // HTTP/2 preface
            "h2"
        } else {
            // По умолчанию HTTP/1.1
            "http/1.1"
        }
    },
    _ => "http/1.1",
};

let service = app.into_make_service_with_connect_info::<SocketAddr>();

match protocol {
    "h2" => {
        http2::Builder::new(TokioExecutor::new())
            .serve_connection(io, service)
            .await
    },
    _ => {
        http1::Builder::new()
            .serve_connection(io, service)
            .await
    }
}

Оптимизация TLS

Для улучшения производительности TLS:

rust
use rustls::{ServerConfig, ServerSessionMemoryCache};

// В функции создания конфигурации TLS
let mut config = ServerConfig::builder()
    .with_safe_defaults()
    .with_no_client_auth()
    .with_single_cert(certs, PrivateKey(key))?;

// Настройка повторного использования сессий
config.session_storage = ServerSessionMemoryCache::new(1024);

// Настройка ALPN для поддержки HTTP/2
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];

// Включение 0-RTT (может иметь последствия для безопасности)
// config.enable_early_data = true;

Ok(config)

Лучшие практики безопасности

  1. Регулярное обновление сертификатов

    • Автоматизируйте процесс обновления с помощью Let's Encrypt
    • Настройте мониторинг срока действия сертификатов
  2. Настройка HSTS (HTTP Strict Transport Security)

    rust
    use tower_http::set_header::SetResponseHeaderLayer;
    use http::header::{HeaderName, HeaderValue};
    
    let app = Router::new()
        .route("/", get(hello))
        .layer(
            SetResponseHeaderLayer::if_not_present(
                HeaderName::from_static("strict-transport-security"),
                HeaderValue::from_static("max-age=31536000; includeSubDomains; preload"),
            )
        );
  3. Современные протоколы шифрования

    • Используйте TLS 1.3, отключите поддержку устаревших протоколов
    rust
    // При настройке rustls
    ServerConfig::builder()
        .with_safe_defaults() // Включает TLS 1.3 и отключает устаревшие шифры
  4. Правильная настройка сертификатов

    • Используйте полную цепочку сертификатов
    • Добавьте все доменные имена, включая субдомены
    • Применяйте OCSP Stapling для проверки отзыва сертификатов
  5. Мониторинг и тестирование

    • Регулярно проверяйте настройки TLS с помощью инструментов вроде SSL Labs
    • Настройте мониторинг истечения срока действия сертификатов

Правильная настройка HTTPS и TLS является фундаментальной для безопасности веб-приложений на Axum. Использование современных библиотек и следование лучшим практикам позволит обеспечить надежную защиту передаваемых данных.