mirror of
https://github.com/orbien-org/orbien.git
synced 2026-11-13 08:10:58 +00:00
feat: add HTTPS support for admin panel
This commit is contained in:
Generated
+1
@@ -4199,6 +4199,7 @@ dependencies = [
|
||||
"rustls",
|
||||
"serde",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
|
||||
@@ -40,6 +40,10 @@ port = 8020
|
||||
user = "admin"
|
||||
password = "123456"
|
||||
|
||||
# [dashboard.tls]
|
||||
# certFile = "./certs/dashboard.crt"
|
||||
# keyFile = "./certs/dashboard.key"
|
||||
|
||||
[log]
|
||||
file = "./orbien-server.log"
|
||||
level = "info"
|
||||
|
||||
@@ -10,8 +10,8 @@ pub use client::{
|
||||
pub use env::{contains_env_placeholders, expand_env_placeholders};
|
||||
pub use log::LogConfig;
|
||||
pub use server::{
|
||||
parse_host_port, DashboardConfig, QuicConfig, ServerConfig, ServerTlsConfig,
|
||||
ServerTransportConfig,
|
||||
parse_host_port, DashboardConfig, DashboardTlsConfig, QuicConfig, ServerConfig,
|
||||
ServerTlsConfig, ServerTransportConfig,
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
@@ -132,6 +132,38 @@ pub struct DashboardConfig {
|
||||
pub user: String,
|
||||
#[serde(default)]
|
||||
pub password: String,
|
||||
|
||||
#[serde(default)]
|
||||
pub tls: DashboardTlsConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||
pub struct DashboardTlsConfig {
|
||||
#[serde(default, rename = "certFile", alias = "cert_file")]
|
||||
pub cert_file: String,
|
||||
#[serde(default, rename = "keyFile", alias = "key_file")]
|
||||
pub key_file: String,
|
||||
}
|
||||
|
||||
impl DashboardTlsConfig {
|
||||
pub fn enabled(&self) -> bool {
|
||||
!self.cert_file.trim().is_empty() && !self.key_file.trim().is_empty()
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
let cert = self.cert_file.trim();
|
||||
let key = self.key_file.trim();
|
||||
match (cert.is_empty(), key.is_empty()) {
|
||||
(true, true) => Ok(()),
|
||||
(false, false) => Ok(()),
|
||||
(false, true) => anyhow::bail!(
|
||||
"dashboard.tls.keyFile is required when dashboard.tls.certFile is set"
|
||||
),
|
||||
(true, false) => anyhow::bail!(
|
||||
"dashboard.tls.certFile is required when dashboard.tls.keyFile is set"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DashboardConfig {
|
||||
@@ -156,6 +188,7 @@ impl DashboardConfig {
|
||||
"dashboard.user and dashboard.password are required when dashboard.port > 0"
|
||||
);
|
||||
}
|
||||
self.tls.validate()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -358,6 +391,10 @@ impl ServerConfig {
|
||||
tls.cert_file = super::resolve_maybe_relative(base, &tls.cert_file);
|
||||
tls.key_file = super::resolve_maybe_relative(base, &tls.key_file);
|
||||
tls.trusted_ca_file = super::resolve_maybe_relative(base, &tls.trusted_ca_file);
|
||||
|
||||
let dash_tls = &mut self.dashboard.tls;
|
||||
dash_tls.cert_file = super::resolve_maybe_relative(base, &dash_tls.cert_file);
|
||||
dash_tls.key_file = super::resolve_maybe_relative(base, &dash_tls.key_file);
|
||||
}
|
||||
|
||||
pub fn from_defaults() -> Self {
|
||||
|
||||
@@ -10,8 +10,9 @@ pub use quic::{build_client_endpoint, build_server_endpoint, quic_bi, QuicBiStre
|
||||
pub use stream::{boxed_stream, AsyncStream, DynStream};
|
||||
pub use tls::{
|
||||
check_and_enable_tls, client_crypto_from_tls_files, client_crypto_insecure, client_enable_tls,
|
||||
generate_self_signed_cert, install_ring_provider, load_pem_cert_key, new_client_tls_config,
|
||||
new_server_tls_config, server_crypto, server_crypto_from_tls_files, ALPN_ORBIEN,
|
||||
generate_self_signed_cert, install_ring_provider, load_pem_cert_key, load_server_cert_key,
|
||||
new_client_tls_config, new_server_tls_config, server_crypto, server_crypto_from_tls_files,
|
||||
ALPN_ORBIEN,
|
||||
};
|
||||
pub use websocket::{
|
||||
accept_websocket, dial_websocket, is_websocket_http_request, normalize_ws_path, WsByteStream,
|
||||
|
||||
@@ -142,6 +142,22 @@ fn load_ca_roots(ca_path: &str) -> Result<RootCertStore> {
|
||||
Ok(roots)
|
||||
}
|
||||
|
||||
pub fn load_server_cert_key(cert_file: &str, key_file: &str) -> Result<Arc<ServerConfig>> {
|
||||
install_ring_provider()?;
|
||||
let cert = cert_file.trim();
|
||||
let key = key_file.trim();
|
||||
if cert.is_empty() || key.is_empty() {
|
||||
bail!("certFile and keyFile are required");
|
||||
}
|
||||
let (certs, key) = load_pem_cert_key(cert, key)?;
|
||||
let mut cfg = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, key)
|
||||
.context("build rustls ServerConfig")?;
|
||||
cfg.alpn_protocols = vec![b"http/1.1".to_vec()];
|
||||
Ok(Arc::new(cfg))
|
||||
}
|
||||
|
||||
pub fn new_server_tls_config(
|
||||
cert_file: &str,
|
||||
key_file: &str,
|
||||
|
||||
@@ -22,9 +22,14 @@ addr = "0.0.0.0"
|
||||
port = 8020
|
||||
user = "admin"
|
||||
password = "123456"
|
||||
|
||||
# 可选
|
||||
# [dashboard.tls]
|
||||
# certFile = "./certs/dashboard.crt"
|
||||
# keyFile = "./certs/dashboard.key"
|
||||
```
|
||||
|
||||
浏览器访问 `http://SERVER_IP:8020`,在弹出框输入用户名和密码登录。
|
||||
浏览器访问 `http://SERVER_IP:8020`(启用 `[dashboard.tls]` 后为 `https://`)。
|
||||
|
||||
:::tip
|
||||
`addr` 默认为 `127.0.0.1`(仅本机),需远程访问时设为 `0.0.0.0`
|
||||
@@ -32,9 +37,16 @@ password = "123456"
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 默认值 | 说明 |
|
||||
|-----------------------|----|-------------|----------------------|
|
||||
| `dashboard.addr` | 否 | `127.0.0.1` | 监听地址;远程访问需 `0.0.0.0` |
|
||||
| `dashboard.port` | 是 | `0` | 监听端口;`0` 表示关闭 |
|
||||
| `dashboard.user` | 是 | | 登录用户名 |
|
||||
| `dashboard.password` | 是 | | 登录密码 |
|
||||
| 参数 | 必填 | 默认值 | 说明 |
|
||||
|--------------------------|------|-------------|--------------------------------|
|
||||
| `dashboard.addr` | 否 | `127.0.0.1` | 监听地址;远程访问需 `0.0.0.0` |
|
||||
| `dashboard.port` | 是 | `0` | 监听端口;`0` 表示关闭 |
|
||||
| `dashboard.user` | 是 | | 登录用户名 |
|
||||
| `dashboard.password` | 是 | | 登录密码 |
|
||||
| `dashboard.tls.certFile` | 否 | | PEM 证书路径 |
|
||||
| `dashboard.tls.keyFile` | 否 | | PEM 私钥路径 |
|
||||
|
||||
:::info[说明]
|
||||
|
||||
- keyFile 和 certFile 必须同时配置才会开启 HTTPS
|
||||
:::
|
||||
@@ -21,9 +21,14 @@ addr = "0.0.0.0"
|
||||
port = 8020
|
||||
user = "admin"
|
||||
password = "123456"
|
||||
|
||||
# Optional
|
||||
# [dashboard.tls]
|
||||
# certFile = "./certs/dashboard.crt"
|
||||
# keyFile = "./certs/dashboard.key"
|
||||
```
|
||||
|
||||
Open `http://SERVER_IP:8020` in a browser and sign in with username and password in the login dialog.
|
||||
Open `http://SERVER_IP:8020` in a browser (use `https://` when `[dashboard.tls]` is enabled).
|
||||
|
||||
:::tip
|
||||
`addr` defaults to `127.0.0.1` (localhost only). Set it to `0.0.0.0` for remote access.
|
||||
@@ -31,9 +36,16 @@ Open `http://SERVER_IP:8020` in a browser and sign in with username and password
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|-------------------------|----------|---------------|----------------------------------------------|
|
||||
| `dashboard.addr` | No | `127.0.0.1` | Listen address; use `0.0.0.0` for remote access |
|
||||
| `dashboard.port` | Yes | `0` | Listen port; `0` disables the dashboard |
|
||||
| `dashboard.user` | Yes | | Login username |
|
||||
| `dashboard.password` | Yes | | Login password |
|
||||
| Parameter | Required | Default | Description |
|
||||
|--------------------------|----------|-------------|------------------------------------------------|
|
||||
| `dashboard.addr` | No | `127.0.0.1` | Listen address; use `0.0.0.0` for remote access |
|
||||
| `dashboard.port` | Yes | `0` | Listen port; `0` disables the dashboard |
|
||||
| `dashboard.user` | Yes | | Username |
|
||||
| `dashboard.password` | Yes | | Password |
|
||||
| `dashboard.tls.certFile` | No | | Path to the PEM certificate |
|
||||
| `dashboard.tls.keyFile` | No | | Path to the PEM private key |
|
||||
|
||||
:::info[Note]
|
||||
|
||||
- Both `certFile` and `keyFile` must be set to enable HTTPS
|
||||
:::
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
"docusaurus": "docusaurus",
|
||||
"fetch-release-manifest": "node scripts/fetch-release-manifest.mjs",
|
||||
"prebuild": "node scripts/fetch-release-manifest.mjs",
|
||||
"prestart": "node scripts/fetch-release-manifest.mjs",
|
||||
"prestart:en": "node scripts/fetch-release-manifest.mjs",
|
||||
"start": "docusaurus start --no-open",
|
||||
"start:en": "docusaurus start --locale en --no-open",
|
||||
"predev": "node scripts/fetch-release-manifest.mjs",
|
||||
"build": "docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"predeploy": "node scripts/fetch-release-manifest.mjs",
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"version": "3.9.0",
|
||||
"fetchedAt": "2026-11-09T14:14:31.910Z",
|
||||
"assets": {
|
||||
"orbien-desktop_3.9.0_darwin_amd64.dmg": 14728180,
|
||||
"orbien-desktop_3.9.0_darwin_arm64.dmg": 13716115,
|
||||
"orbien-desktop_3.9.0_linux_amd64.deb": 11985208,
|
||||
"orbien-desktop_3.9.0_linux_arm64.deb": 11605068,
|
||||
"orbien-desktop_3.9.0_windows_amd64.zip": 12136382,
|
||||
"orbien-desktop_3.9.0_windows_arm64.zip": 11662935,
|
||||
"orbien-server_3.9.0_darwin_amd64.tar.gz": 2776015,
|
||||
"orbien-server_3.9.0_darwin_arm64.tar.gz": 2548570,
|
||||
"orbien-server_3.9.0_freebsd_amd64.tar.gz": 2881813,
|
||||
"orbien-server_3.9.0_linux_amd64_gnu.tar.gz": 2916874,
|
||||
"orbien-server_3.9.0_linux_amd64_musl.tar.gz": 2293429,
|
||||
"orbien-server_3.9.0_linux_arm64_gnu.tar.gz": 2753719,
|
||||
"orbien-server_3.9.0_linux_arm64_musl.tar.gz": 2013390,
|
||||
"orbien-server_3.9.0_windows_amd64.tar.gz": 2686107,
|
||||
"orbien-server_3.9.0_windows_arm64.tar.gz": 2538893,
|
||||
"orbien_3.9.0_darwin_amd64.tar.gz": 2446430,
|
||||
"orbien_3.9.0_darwin_arm64.tar.gz": 2208569,
|
||||
"orbien_3.9.0_freebsd_amd64.tar.gz": 2552659,
|
||||
"orbien_3.9.0_linux_amd64_gnu.tar.gz": 2591259,
|
||||
"orbien_3.9.0_linux_amd64_musl.tar.gz": 1967497,
|
||||
"orbien_3.9.0_linux_arm64_gnu.tar.gz": 2420371,
|
||||
"orbien_3.9.0_linux_arm64_musl.tar.gz": 1688071,
|
||||
"orbien_3.9.0_windows_amd64.tar.gz": 2358926,
|
||||
"orbien_3.9.0_windows_arm64.tar.gz": 2206299
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ quinn = { workspace = true }
|
||||
kcp-tokio = { workspace = true }
|
||||
httparse = { workspace = true }
|
||||
rustls = { workspace = true }
|
||||
tokio-rustls = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
axum = { workspace = true }
|
||||
rust-embed = { workspace = true }
|
||||
|
||||
@@ -5,24 +5,72 @@ mod snapshot;
|
||||
use crate::service::Service;
|
||||
use anyhow::Result;
|
||||
use axum::middleware;
|
||||
use axum::serve::Listener;
|
||||
use orbien_core::config::DashboardConfig;
|
||||
use orbien_core::transport::load_server_cert_key;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio_rustls::server::TlsStream;
|
||||
use tokio_rustls::TlsAcceptor;
|
||||
|
||||
pub async fn run(svc: Arc<Service>, cfg: DashboardConfig) -> Result<()> {
|
||||
let addr = format!("{}:{}", cfg.addr, cfg.port);
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
tracing::info!(%addr, user = %cfg.user, "dashboard listening");
|
||||
|
||||
let state = Arc::new(DashState { svc, cfg });
|
||||
let state = Arc::new(DashState {
|
||||
svc,
|
||||
cfg: cfg.clone(),
|
||||
});
|
||||
let app = routes::router(state.clone())
|
||||
.layer(middleware::from_fn_with_state(state, routes::basic_auth))
|
||||
.into_make_service();
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
if cfg.tls.enabled() {
|
||||
let tls_cfg = load_server_cert_key(&cfg.tls.cert_file, &cfg.tls.key_file)?;
|
||||
let acceptor = TlsAcceptor::from(tls_cfg);
|
||||
tracing::info!(%addr, user = %cfg.user, "dashboard listening (https)");
|
||||
axum::serve(TlsListener { listener, acceptor }, app).await?;
|
||||
} else {
|
||||
tracing::info!(%addr, user = %cfg.user, "dashboard listening");
|
||||
axum::serve(listener, app).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TlsListener {
|
||||
listener: TcpListener,
|
||||
acceptor: TlsAcceptor,
|
||||
}
|
||||
|
||||
impl Listener for TlsListener {
|
||||
type Io = TlsStream<TcpStream>;
|
||||
type Addr = SocketAddr;
|
||||
|
||||
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
|
||||
loop {
|
||||
let (tcp, peer) = match self.listener.accept().await {
|
||||
Ok(tup) => tup,
|
||||
Err(e) => {
|
||||
tracing::debug!(error = %e, "dashboard accept failed");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match self.acceptor.accept(tcp).await {
|
||||
Ok(tls) => return (tls, peer),
|
||||
Err(e) => {
|
||||
tracing::debug!(%peer, error = %e, "dashboard TLS handshake failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn local_addr(&self) -> io::Result<Self::Addr> {
|
||||
self.listener.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DashState {
|
||||
pub svc: Arc<Service>,
|
||||
|
||||
Reference in New Issue
Block a user