mirror of
https://github.com/rustfs/rustfs.git
synced 2026-01-17 01:30:33 +00:00
upgrade docker image version and fix docker comman
This commit is contained in:
@@ -9,7 +9,7 @@ Jaeger、Prometheus 等)而需要运行和维护多个代理/收集器的必
|
||||
2. 执行以下命令启动服务:
|
||||
|
||||
```bash
|
||||
docker compose up -d -f docker-compose.yml
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
### 访问监控面板
|
||||
@@ -34,7 +34,7 @@ docker compose up -d -f docker-compose.yml
|
||||
| service_name | 服务名称 | rustfs |
|
||||
| service_version | 服务版本 | 1.0.0 |
|
||||
| environment | 运行环境 | production |
|
||||
| meter_interval | 指标导出间隔 (秒) | 30 |
|
||||
| meter_interval | 指标导出间隔 (秒) | 30 |
|
||||
| sample_ratio | 采样率 | 1.0 |
|
||||
| use_stdout | 是否输出到控制台 | true/false |
|
||||
| logger_level | 日志级别 | info |
|
||||
|
||||
34
.docker/observability/config/obs-multi.toml
Normal file
34
.docker/observability/config/obs-multi.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[observability]
|
||||
endpoint = "http://otel-collector:4317" # Default is "http://localhost:4317" if not specified
|
||||
use_stdout = false # Output with stdout, true output, false no output
|
||||
sample_ratio = 2.0
|
||||
meter_interval = 30
|
||||
service_name = "rustfs"
|
||||
service_version = "0.1.0"
|
||||
environments = "production"
|
||||
logger_level = "debug"
|
||||
local_logging_enabled = true
|
||||
|
||||
[sinks]
|
||||
[sinks.kafka] # Kafka sink is disabled by default
|
||||
enabled = false
|
||||
bootstrap_servers = "localhost:9092"
|
||||
topic = "logs"
|
||||
batch_size = 100 # Default is 100 if not specified
|
||||
batch_timeout_ms = 1000 # Default is 1000ms if not specified
|
||||
|
||||
[sinks.webhook]
|
||||
enabled = false
|
||||
endpoint = "http://localhost:8080/webhook"
|
||||
auth_token = ""
|
||||
batch_size = 100 # Default is 3 if not specified
|
||||
batch_timeout_ms = 1000 # Default is 100ms if not specified
|
||||
|
||||
[sinks.file]
|
||||
enabled = true
|
||||
path = "/root/data/logs/app.log"
|
||||
batch_size = 10
|
||||
batch_timeout_ms = 1000 # Default is 8192 bytes if not specified
|
||||
|
||||
[logger]
|
||||
queue_capacity = 10
|
||||
@@ -1,5 +1,5 @@
|
||||
[observability]
|
||||
endpoint = "http://otel-collector:4317" # Default is "http://localhost:4317" if not specified
|
||||
endpoint = "http://localhost:4317" # Default is "http://localhost:4317" if not specified
|
||||
use_stdout = false # Output with stdout, true output, false no output
|
||||
sample_ratio = 2.0
|
||||
meter_interval = 30
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
otel-collector:
|
||||
image: otel/opentelemetry-collector-contrib:0.120.0
|
||||
image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.124.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
networks:
|
||||
- otel-network
|
||||
jaeger:
|
||||
image: jaegertracing/jaeger:2.4.0
|
||||
image: jaegertracing/jaeger:2.5.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
ports:
|
||||
@@ -26,7 +26,7 @@ services:
|
||||
networks:
|
||||
- otel-network
|
||||
prometheus:
|
||||
image: prom/prometheus:v3.2.1
|
||||
image: prom/prometheus:v3.3.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
networks:
|
||||
- otel-network
|
||||
loki:
|
||||
image: grafana/loki:3.4.2
|
||||
image: grafana/loki:3.5.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
networks:
|
||||
- otel-network
|
||||
grafana:
|
||||
image: grafana/grafana:11.6.0
|
||||
image: grafana/grafana:11.6.1
|
||||
ports:
|
||||
- "3000:3000" # Web UI
|
||||
environment:
|
||||
|
||||
@@ -102,7 +102,7 @@ export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
||||
|
||||
2. Start the observability stack:
|
||||
```bash
|
||||
docker compose up -d -f docker-compose.yml
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
#### Access Monitoring Dashboards
|
||||
|
||||
@@ -102,7 +102,7 @@ export RUSTFS__LOGGER__QUEUE_CAPACITY=10
|
||||
|
||||
2. 启动可观测性系统:
|
||||
```bash
|
||||
docker compose up -d -f docker-compose.yml
|
||||
docker compose -f docker-compose.yml up -d
|
||||
```
|
||||
|
||||
#### 访问监控面板
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
otel-collector:
|
||||
image: otel/opentelemetry-collector-contrib:0.120.0
|
||||
image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:0.124.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
networks:
|
||||
- rustfs-network
|
||||
jaeger:
|
||||
image: jaegertracing/jaeger:2.4.0
|
||||
image: jaegertracing/jaeger:2.5.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
ports:
|
||||
@@ -26,7 +26,7 @@ services:
|
||||
networks:
|
||||
- rustfs-network
|
||||
prometheus:
|
||||
image: prom/prometheus:v3.2.1
|
||||
image: prom/prometheus:v3.3.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
networks:
|
||||
- rustfs-network
|
||||
loki:
|
||||
image: grafana/loki:3.4.2
|
||||
image: grafana/loki:3.5.0
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
volumes:
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
networks:
|
||||
- rustfs-network
|
||||
grafana:
|
||||
image: grafana/grafana:11.6.0
|
||||
image: grafana/grafana:11.6.1
|
||||
ports:
|
||||
- "3000:3000" # Web UI
|
||||
environment:
|
||||
@@ -63,10 +63,10 @@ services:
|
||||
container_name: node1
|
||||
environment:
|
||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/root/data/target/volume/test{1...4}
|
||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||
- RUSTFS_ADDRESS=:9000
|
||||
- RUSTFS_CONSOLE_ENABLE=true
|
||||
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs.toml
|
||||
- RUSTFS_CONSOLE_ADDRESS=:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs-multi.toml
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "9001:9000" # 映射宿主机的 9001 端口到容器的 9000 端口
|
||||
@@ -84,10 +84,10 @@ services:
|
||||
container_name: node2
|
||||
environment:
|
||||
- RUSTFS_VOLUMES=/root/data/target/volume/test{1...4}
|
||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||
- RUSTFS_ADDRESS=:9000
|
||||
- RUSTFS_CONSOLE_ENABLE=true
|
||||
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs.toml
|
||||
- RUSTFS_CONSOLE_ADDRESS=:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs-multi.toml
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "9002:9000" # 映射宿主机的 9002 端口到容器的 9000 端口
|
||||
@@ -105,10 +105,10 @@ services:
|
||||
container_name: node3
|
||||
environment:
|
||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/root/data/target/volume/test{1...4}
|
||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||
- RUSTFS_ADDRESS=:9000
|
||||
- RUSTFS_CONSOLE_ENABLE=true
|
||||
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs.toml
|
||||
- RUSTFS_CONSOLE_ADDRESS=:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs-multi.toml
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "9003:9000" # 映射宿主机的 9003 端口到容器的 9000 端口
|
||||
@@ -126,10 +126,10 @@ services:
|
||||
container_name: node4
|
||||
environment:
|
||||
- RUSTFS_VOLUMES=http://node{1...4}:9000/root/data/target/volume/test{1...4}
|
||||
- RUSTFS_ADDRESS=0.0.0.0:9000
|
||||
- RUSTFS_ADDRESS=:9000
|
||||
- RUSTFS_CONSOLE_ENABLE=true
|
||||
- RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs.toml
|
||||
- RUSTFS_CONSOLE_ADDRESS=:9002
|
||||
- RUSTFS_OBS_CONFIG=/etc/observability/config/obs-multi.toml
|
||||
platform: linux/amd64
|
||||
ports:
|
||||
- "9004:9000" # 映射宿主机的 9004 端口到容器的 9000 端口
|
||||
|
||||
@@ -33,7 +33,8 @@ const RUSTFS_ADMIN_PREFIX: &str = "/rustfs/admin/v3";
|
||||
#[folder = "$CARGO_MANIFEST_DIR/static"]
|
||||
struct StaticFiles;
|
||||
|
||||
async fn static_handler(uri: axum::http::Uri) -> impl IntoResponse {
|
||||
/// Static file handler
|
||||
async fn static_handler(uri: Uri) -> impl IntoResponse {
|
||||
let mut path = uri.path().trim_start_matches('/');
|
||||
if path.is_empty() {
|
||||
path = "index.html"
|
||||
@@ -193,7 +194,7 @@ fn _is_private_ip(ip: std::net::IpAddr) -> bool {
|
||||
async fn config_handler(uri: Uri, Host(host): Host) -> impl IntoResponse {
|
||||
let scheme = uri.scheme().map(|s| s.as_str()).unwrap_or("http");
|
||||
|
||||
// 从 uri 中获取 host,如果没有则使用 Host extractor 的值
|
||||
// Get the host from the uri and use the value of the host extractor if it doesn't have one
|
||||
let host = uri.host().unwrap_or(host.as_str());
|
||||
|
||||
let host = if host.contains(':') {
|
||||
@@ -203,7 +204,7 @@ async fn config_handler(uri: Uri, Host(host): Host) -> impl IntoResponse {
|
||||
host
|
||||
};
|
||||
|
||||
// 将当前配置复制一份
|
||||
// Make a copy of the current configuration
|
||||
let mut cfg = CONSOLE_CONFIG.get().unwrap().clone();
|
||||
|
||||
let url = format!("{}://{}:{}", scheme, host, cfg.port);
|
||||
@@ -224,9 +225,9 @@ pub async fn start_static_file_server(
|
||||
secret_key: &str,
|
||||
tls_path: Option<String>,
|
||||
) {
|
||||
// 配置 CORS
|
||||
// Configure CORS
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any) // 生产环境建议指定具体域名
|
||||
.allow_origin(Any) // In the production environment, we recommend that you specify a specific domain name
|
||||
.allow_methods([http::Method::GET, http::Method::POST])
|
||||
.allow_headers([header::CONTENT_TYPE]);
|
||||
// Create a route
|
||||
@@ -298,7 +299,7 @@ async fn start_server(server_addr: SocketAddr, tls_path: Option<String>, app: Ro
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// HTTP 到 HTTPS 的 308 重定向
|
||||
/// 308 redirect for HTTP to HTTPS
|
||||
fn redirect_to_https(https_port: u16) -> Router {
|
||||
Router::new().route(
|
||||
"/*path",
|
||||
|
||||
@@ -10,6 +10,7 @@ lazy_static::lazy_static! {
|
||||
static ref LICENSE: OnceLock<Token> = OnceLock::new();
|
||||
}
|
||||
|
||||
/// Initialize the license
|
||||
pub fn init_license(license: Option<String>) {
|
||||
if license.is_none() {
|
||||
error!("License is None");
|
||||
@@ -23,10 +24,13 @@ pub fn init_license(license: Option<String>) {
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the license
|
||||
pub fn get_license() -> Option<Token> {
|
||||
LICENSE.get().cloned()
|
||||
}
|
||||
|
||||
/// Check the license
|
||||
/// This function checks if the license is valid.
|
||||
#[allow(unreachable_code)]
|
||||
pub fn license_check() -> Result<()> {
|
||||
return Ok(());
|
||||
|
||||
@@ -129,7 +129,7 @@ impl Default for ServiceStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
// Example of use
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -138,18 +138,18 @@ mod tests {
|
||||
fn test_service_state_manager() {
|
||||
let manager = ServiceStateManager::new();
|
||||
|
||||
// 初始状态应该是 Starting
|
||||
// The initial state should be Starting
|
||||
assert_eq!(manager.current_state(), ServiceState::Starting);
|
||||
|
||||
// 更新状态到 Ready
|
||||
// Update the status to Ready
|
||||
manager.update(ServiceState::Ready);
|
||||
assert_eq!(manager.current_state(), ServiceState::Ready);
|
||||
|
||||
// 更新状态到 Stopping
|
||||
// Update the status to Stopping
|
||||
manager.update(ServiceState::Stopping);
|
||||
assert_eq!(manager.current_state(), ServiceState::Stopping);
|
||||
|
||||
// 更新状态到 Stopped
|
||||
// Update the status to Stopped
|
||||
manager.update(ServiceState::Stopped);
|
||||
assert_eq!(manager.current_state(), ServiceState::Stopped);
|
||||
}
|
||||
|
||||
180
rustfs/src/utils/certs.rs
Normal file
180
rustfs/src/utils/certs.rs
Normal file
@@ -0,0 +1,180 @@
|
||||
use crate::config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
|
||||
use rustls::server::{ClientHello, ResolvesServerCert, ResolvesServerCertUsingSni};
|
||||
use rustls::sign::CertifiedKey;
|
||||
use rustls_pemfile::{certs, private_key};
|
||||
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Error;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, io};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
/// Load public certificate from file.
|
||||
/// This function loads a public certificate from the specified file.
|
||||
pub(crate) fn load_certs(filename: &str) -> io::Result<Vec<CertificateDer<'static>>> {
|
||||
// Open certificate file.
|
||||
let cert_file = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
||||
let mut reader = io::BufReader::new(cert_file);
|
||||
|
||||
// Load and return certificate.
|
||||
let certs = certs(&mut reader)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| error(format!("certificate file {} format error", filename)))?;
|
||||
if certs.is_empty() {
|
||||
return Err(error(format!("No valid certificate was found in the certificate file {}", filename)));
|
||||
}
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
/// Load private key from file.
|
||||
/// This function loads a private key from the specified file.
|
||||
pub(crate) fn load_private_key(filename: &str) -> io::Result<PrivateKeyDer<'static>> {
|
||||
// Open keyfile.
|
||||
let keyfile = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
||||
let mut reader = io::BufReader::new(keyfile);
|
||||
|
||||
// Load and return a single private key.
|
||||
private_key(&mut reader)?.ok_or_else(|| error(format!("no private key found in {}", filename)))
|
||||
}
|
||||
|
||||
/// error function
|
||||
pub(crate) fn error(err: String) -> Error {
|
||||
Error::new(io::ErrorKind::Other, err)
|
||||
}
|
||||
|
||||
/// Load all certificates and private keys in the directory
|
||||
/// This function loads all certificate and private key pairs from the specified directory.
|
||||
/// It looks for files named `rustfs_cert.pem` and `rustfs_key.pem` in each subdirectory.
|
||||
/// The root directory can also contain a default certificate/private key pair.
|
||||
pub(crate) fn load_all_certs_from_directory(
|
||||
dir_path: &str,
|
||||
) -> io::Result<HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>> {
|
||||
let mut cert_key_pairs = HashMap::new();
|
||||
let dir = Path::new(dir_path);
|
||||
|
||||
if !dir.exists() || !dir.is_dir() {
|
||||
return Err(error(format!(
|
||||
"The certificate directory does not exist or is not a directory: {}",
|
||||
dir_path
|
||||
)));
|
||||
}
|
||||
|
||||
// 1. First check whether there is a certificate/private key pair in the root directory
|
||||
let root_cert_path = dir.join(RUSTFS_TLS_CERT);
|
||||
let root_key_path = dir.join(RUSTFS_TLS_KEY);
|
||||
|
||||
if root_cert_path.exists() && root_key_path.exists() {
|
||||
debug!("find the root directory certificate: {:?}", root_cert_path);
|
||||
let root_cert_str = root_cert_path
|
||||
.to_str()
|
||||
.ok_or_else(|| error(format!("Invalid UTF-8 in root certificate path: {:?}", root_cert_path)))?;
|
||||
let root_key_str = root_key_path
|
||||
.to_str()
|
||||
.ok_or_else(|| error(format!("Invalid UTF-8 in root key path: {:?}", root_key_path)))?;
|
||||
match load_cert_key_pair(root_cert_str, root_key_str) {
|
||||
Ok((certs, key)) => {
|
||||
// The root directory certificate is used as the default certificate and is stored using special keys.
|
||||
cert_key_pairs.insert("default".to_string(), (certs, key));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("unable to load root directory certificate: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2.iterate through all folders in the directory
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
let domain_name = path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.ok_or_else(|| error(format!("invalid domain name directory:{:?}", path)))?;
|
||||
|
||||
// find certificate and private key files
|
||||
let cert_path = path.join(RUSTFS_TLS_CERT); // e.g., rustfs_cert.pem
|
||||
let key_path = path.join(RUSTFS_TLS_KEY); // e.g., rustfs_key.pem
|
||||
|
||||
if cert_path.exists() && key_path.exists() {
|
||||
debug!("find the domain name certificate: {} in {:?}", domain_name, cert_path);
|
||||
match load_cert_key_pair(cert_path.to_str().unwrap(), key_path.to_str().unwrap()) {
|
||||
Ok((certs, key)) => {
|
||||
cert_key_pairs.insert(domain_name.to_string(), (certs, key));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("unable to load the certificate for {} domain name: {}", domain_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cert_key_pairs.is_empty() {
|
||||
return Err(error(format!("No valid certificate/private key pair found in directory {}", dir_path)));
|
||||
}
|
||||
|
||||
Ok(cert_key_pairs)
|
||||
}
|
||||
|
||||
/// loading a single certificate private key pair
|
||||
/// This function loads a certificate and private key from the specified paths.
|
||||
/// It returns a tuple containing the certificate and private key.
|
||||
fn load_cert_key_pair(cert_path: &str, key_path: &str) -> io::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
||||
let certs = load_certs(cert_path)?;
|
||||
let key = load_private_key(key_path)?;
|
||||
Ok((certs, key))
|
||||
}
|
||||
|
||||
/// Create a multi-cert resolver
|
||||
/// This function loads all certificates and private keys from the specified directory.
|
||||
/// It uses the first certificate/private key pair found in the root directory as the default certificate.
|
||||
/// The rest of the certificates/private keys are used for SNI resolution.
|
||||
///
|
||||
pub fn create_multi_cert_resolver(
|
||||
cert_key_pairs: HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
|
||||
) -> io::Result<impl ResolvesServerCert> {
|
||||
#[derive(Debug)]
|
||||
struct MultiCertResolver {
|
||||
cert_resolver: ResolvesServerCertUsingSni,
|
||||
default_cert: Option<Arc<CertifiedKey>>,
|
||||
}
|
||||
impl ResolvesServerCert for MultiCertResolver {
|
||||
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
|
||||
// try matching certificates with sni
|
||||
if let Some(cert) = self.cert_resolver.resolve(client_hello) {
|
||||
return Some(cert);
|
||||
}
|
||||
|
||||
// If there is no matching SNI certificate, use the default certificate
|
||||
self.default_cert.clone()
|
||||
}
|
||||
}
|
||||
|
||||
let mut resolver = ResolvesServerCertUsingSni::new();
|
||||
let mut default_cert = None;
|
||||
|
||||
for (domain, (certs, key)) in cert_key_pairs {
|
||||
// create a signature
|
||||
let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key)
|
||||
.map_err(|_| error(format!("unsupported private key types:{}", domain)))?;
|
||||
|
||||
// create a CertifiedKey
|
||||
let certified_key = CertifiedKey::new(certs, signing_key);
|
||||
if domain == "default" {
|
||||
default_cert = Some(Arc::new(certified_key.clone()));
|
||||
} else {
|
||||
// add certificate to resolver
|
||||
resolver
|
||||
.add(&domain, certified_key)
|
||||
.map_err(|e| error(format!("failed to add a domain name certificate:{},err: {:?}", domain, e)))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MultiCertResolver {
|
||||
cert_resolver: resolver,
|
||||
default_cert,
|
||||
})
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
use crate::config::{RUSTFS_TLS_CERT, RUSTFS_TLS_KEY};
|
||||
use rustls::server::{ClientHello, ResolvesServerCert, ResolvesServerCertUsingSni};
|
||||
use rustls::sign::CertifiedKey;
|
||||
use rustls_pemfile::{certs, private_key};
|
||||
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Error;
|
||||
mod certs;
|
||||
use std::net::IpAddr;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::{fs, io};
|
||||
use tracing::{debug, warn};
|
||||
|
||||
pub(crate) use certs::create_multi_cert_resolver;
|
||||
pub(crate) use certs::error;
|
||||
pub(crate) use certs::load_all_certs_from_directory;
|
||||
pub(crate) use certs::load_certs;
|
||||
pub(crate) use certs::load_private_key;
|
||||
|
||||
/// Get the local IP address.
|
||||
/// This function retrieves the local IP address of the machine.
|
||||
@@ -21,172 +16,3 @@ pub(crate) fn get_local_ip() -> Option<std::net::Ipv4Addr> {
|
||||
Ok(IpAddr::V6(_)) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load public certificate from file.
|
||||
/// This function loads a public certificate from the specified file.
|
||||
pub(crate) fn load_certs(filename: &str) -> io::Result<Vec<CertificateDer<'static>>> {
|
||||
// Open certificate file.
|
||||
let cert_file = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
||||
let mut reader = io::BufReader::new(cert_file);
|
||||
|
||||
// Load and return certificate.
|
||||
let certs = certs(&mut reader)
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(|_| error(format!("certificate file {} format error", filename)))?;
|
||||
if certs.is_empty() {
|
||||
return Err(error(format!("No valid certificate was found in the certificate file {}", filename)));
|
||||
}
|
||||
Ok(certs)
|
||||
}
|
||||
|
||||
/// Load private key from file.
|
||||
/// This function loads a private key from the specified file.
|
||||
pub(crate) fn load_private_key(filename: &str) -> io::Result<PrivateKeyDer<'static>> {
|
||||
// Open keyfile.
|
||||
let keyfile = fs::File::open(filename).map_err(|e| error(format!("failed to open {}: {}", filename, e)))?;
|
||||
let mut reader = io::BufReader::new(keyfile);
|
||||
|
||||
// Load and return a single private key.
|
||||
private_key(&mut reader)?.ok_or_else(|| error(format!("no private key found in {}", filename)))
|
||||
}
|
||||
|
||||
/// error function
|
||||
pub(crate) fn error(err: String) -> Error {
|
||||
Error::new(io::ErrorKind::Other, err)
|
||||
}
|
||||
|
||||
/// Load all certificates and private keys in the directory
|
||||
/// This function loads all certificate and private key pairs from the specified directory.
|
||||
/// It looks for files named `rustfs_cert.pem` and `rustfs_key.pem` in each subdirectory.
|
||||
/// The root directory can also contain a default certificate/private key pair.
|
||||
pub(crate) fn load_all_certs_from_directory(
|
||||
dir_path: &str,
|
||||
) -> io::Result<HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>> {
|
||||
let mut cert_key_pairs = HashMap::new();
|
||||
let dir = Path::new(dir_path);
|
||||
|
||||
if !dir.exists() || !dir.is_dir() {
|
||||
return Err(error(format!(
|
||||
"The certificate directory does not exist or is not a directory: {}",
|
||||
dir_path
|
||||
)));
|
||||
}
|
||||
|
||||
// 1. First check whether there is a certificate/private key pair in the root directory
|
||||
let root_cert_path = dir.join(RUSTFS_TLS_CERT);
|
||||
let root_key_path = dir.join(RUSTFS_TLS_KEY);
|
||||
|
||||
if root_cert_path.exists() && root_key_path.exists() {
|
||||
debug!("find the root directory certificate: {:?}", root_cert_path);
|
||||
let root_cert_str = root_cert_path
|
||||
.to_str()
|
||||
.ok_or_else(|| error(format!("Invalid UTF-8 in root certificate path: {:?}", root_cert_path)))?;
|
||||
let root_key_str = root_key_path
|
||||
.to_str()
|
||||
.ok_or_else(|| error(format!("Invalid UTF-8 in root key path: {:?}", root_key_path)))?;
|
||||
match load_cert_key_pair(root_cert_str, root_key_str) {
|
||||
Ok((certs, key)) => {
|
||||
// The root directory certificate is used as the default certificate and is stored using special keys.
|
||||
cert_key_pairs.insert("default".to_string(), (certs, key));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("unable to load root directory certificate: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2.iterate through all folders in the directory
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
let domain_name = path
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.ok_or_else(|| error(format!("invalid domain name directory:{:?}", path)))?;
|
||||
|
||||
// find certificate and private key files
|
||||
let cert_path = path.join(RUSTFS_TLS_CERT); // e.g., rustfs_cert.pem
|
||||
let key_path = path.join(RUSTFS_TLS_KEY); // e.g., rustfs_key.pem
|
||||
|
||||
if cert_path.exists() && key_path.exists() {
|
||||
debug!("find the domain name certificate: {} in {:?}", domain_name, cert_path);
|
||||
match load_cert_key_pair(cert_path.to_str().unwrap(), key_path.to_str().unwrap()) {
|
||||
Ok((certs, key)) => {
|
||||
cert_key_pairs.insert(domain_name.to_string(), (certs, key));
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("unable to load the certificate for {} domain name: {}", domain_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cert_key_pairs.is_empty() {
|
||||
return Err(error(format!("No valid certificate/private key pair found in directory {}", dir_path)));
|
||||
}
|
||||
|
||||
Ok(cert_key_pairs)
|
||||
}
|
||||
|
||||
/// loading a single certificate private key pair
|
||||
/// This function loads a certificate and private key from the specified paths.
|
||||
/// It returns a tuple containing the certificate and private key.
|
||||
fn load_cert_key_pair(cert_path: &str, key_path: &str) -> io::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
|
||||
let certs = load_certs(cert_path)?;
|
||||
let key = load_private_key(key_path)?;
|
||||
Ok((certs, key))
|
||||
}
|
||||
|
||||
/// Create a multi-cert resolver
|
||||
/// This function loads all certificates and private keys from the specified directory.
|
||||
/// It uses the first certificate/private key pair found in the root directory as the default certificate.
|
||||
/// The rest of the certificates/private keys are used for SNI resolution.
|
||||
///
|
||||
pub fn create_multi_cert_resolver(
|
||||
cert_key_pairs: HashMap<String, (Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)>,
|
||||
) -> io::Result<impl ResolvesServerCert> {
|
||||
#[derive(Debug)]
|
||||
struct MultiCertResolver {
|
||||
cert_resolver: ResolvesServerCertUsingSni,
|
||||
default_cert: Option<Arc<CertifiedKey>>,
|
||||
}
|
||||
impl ResolvesServerCert for MultiCertResolver {
|
||||
fn resolve(&self, client_hello: ClientHello) -> Option<Arc<CertifiedKey>> {
|
||||
// try matching certificates with sni
|
||||
if let Some(cert) = self.cert_resolver.resolve(client_hello) {
|
||||
return Some(cert);
|
||||
}
|
||||
|
||||
// If there is no matching SNI certificate, use the default certificate
|
||||
self.default_cert.clone()
|
||||
}
|
||||
}
|
||||
|
||||
let mut resolver = ResolvesServerCertUsingSni::new();
|
||||
let mut default_cert = None;
|
||||
|
||||
for (domain, (certs, key)) in cert_key_pairs {
|
||||
// create a signature
|
||||
let signing_key = rustls::crypto::aws_lc_rs::sign::any_supported_type(&key)
|
||||
.map_err(|_| error(format!("unsupported private key types:{}", domain)))?;
|
||||
|
||||
// create a CertifiedKey
|
||||
let certified_key = CertifiedKey::new(certs, signing_key);
|
||||
if domain == "default" {
|
||||
default_cert = Some(Arc::new(certified_key.clone()));
|
||||
} else {
|
||||
// add certificate to resolver
|
||||
resolver
|
||||
.add(&domain, certified_key)
|
||||
.map_err(|e| error(format!("failed to add a domain name certificate:{},err: {:?}", domain, e)))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MultiCertResolver {
|
||||
cert_resolver: resolver,
|
||||
default_cert,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user