feat: Add support for Mux SSL mode and update related settings

- Introduced a new SSL mode "Mux" in the settings, allowing for HTTP to HTTPS redirection.
- Updated the `SSL` field in the `SettingUpdate` struct to include "Mux" as a valid option.
- Modified the server logic to handle Mux connections, including certificate management and HTTP redirection.
- Updated frontend components to reflect the new SSL options and improved user guidance in multiple languages.
This commit is contained in:
HynoR 2025-12-29 13:41:09 +08:00
parent 4e7b654090
commit 1112d49d2d
17 changed files with 92 additions and 26 deletions

View file

@ -64,7 +64,7 @@ type SettingUpdate struct {
type SSLUpdate struct {
SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"`
Domain string `json:"domain"`
SSL string `json:"ssl" validate:"required,oneof=Enable Disable"`
SSL string `json:"ssl" validate:"required,oneof=Enable Disable Mux"`
Cert string `json:"cert"`
Key string `json:"key"`
SSLID uint `json:"sslID"`

View file

@ -21,6 +21,7 @@ const (
StatusEnable = "Enable"
StatusDisable = "Disable"
StatusMux = "Mux"
StatusInstalling = "Installing"
StatusNormal = "Normal"

View file

@ -81,6 +81,7 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/soheilhy/cmux v0.1.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect

View file

@ -250,6 +250,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@ -342,6 +344,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -363,6 +366,7 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View file

@ -4,6 +4,11 @@ import (
"crypto/tls"
"encoding/gob"
"fmt"
"net"
"net/http"
"os"
"path"
"github.com/1Panel-dev/1Panel/core/init/auth"
"github.com/1Panel-dev/1Panel/core/init/db"
"github.com/1Panel-dev/1Panel/core/init/geo"
@ -12,10 +17,7 @@ import (
"github.com/1Panel-dev/1Panel/core/init/proxy"
"github.com/1Panel-dev/1Panel/core/init/run"
"github.com/gin-gonic/gin"
"net"
"net/http"
"os"
"path"
"github.com/soheilhy/cmux"
"github.com/1Panel-dev/1Panel/core/constant"
"github.com/1Panel-dev/1Panel/core/global"
@ -99,10 +101,64 @@ func Start() {
if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, "", ""); err != nil {
panic(err)
}
return
} else if global.CONF.Conn.SSL == constant.StatusMux {
certPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")
keyPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key")
certificate, err := os.ReadFile(certPath)
if err != nil {
panic(err)
}
key, err := os.ReadFile(keyPath)
if err != nil {
panic(err)
}
cert, err := tls.X509KeyPair(certificate, key)
if err != nil {
panic(err)
}
constant.CertStore.Store(&cert)
server.TLSConfig = &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return constant.CertStore.Load().(*tls.Certificate), nil
},
}
global.LOG.Infof("listen at mux (http/https)://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem)
m := cmux.New(ln)
httpsL := m.Match(cmux.TLS())
httpL := m.Match(cmux.Any())
go func() {
if err := server.Serve(tls.NewListener(httpsL, server.TLSConfig)); err != nil {
global.LOG.Errorf("HTTPS Serve Error: %v", err)
}
}()
go func() {
redirectServer := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
target := "https://" + r.Host + r.RequestURI
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
}),
}
if err := redirectServer.Serve(httpL); err != nil {
global.LOG.Errorf("HTTP Redirect Serve Error: %v", err)
}
}()
if err := m.Serve(); err != nil {
panic(err)
}
return
} else {
global.LOG.Infof("listen at http://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem)
if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil {
panic(err)
}
return
}
}

View file

@ -1995,7 +1995,7 @@ const message = {
error444: 'Connection Closed',
error500: 'Internal Server Error',
https: 'Setting up HTTPS protocol access for the panel can enhance the security of panel access.',
https: 'Setting up HTTPS for the panel improves security.\nStrict mode blocks non-HTTPS access.\nMux redirects HTTP to HTTPS but may reduce performance slightly.',
certType: 'Certificate type',
selfSigned: 'Self signed',
selfSignedHelper: `Browsers may not trust self-signed certificates and may display security warnings.`,

View file

@ -2009,7 +2009,7 @@ const message = {
error416: 'Rango no satisfactorio',
error444: 'Conexión cerrada',
error500: 'Error interno del servidor',
https: 'Configurar acceso al panel mediante protocolo HTTPS puede mejorar la seguridad del acceso.',
https: 'Configurar HTTPS para el panel mejora la seguridad.\nEl modo Strict bloquea el acceso sin HTTPS.\nMux redirige HTTP a HTTPS, pero puede reducir ligeramente el rendimiento.',
certType: 'Tipo de certificado',
selfSigned: 'Autofirmado',
selfSignedHelper:

View file

@ -1921,7 +1921,7 @@ const message = {
error444: '接続が閉じた',
error500: 'サーバーエラー',
https: 'パネル用のHTTPSプロトコルアクセスをセットアップするとパネルアクセスのセキュリティが強化されます',
https: 'パネルHTTPS を設定すると安全性が向上します\nStrict モードは HTTPS 以外のアクセスを遮断します\nMux HTTP HTTPS へリダイレクトしますがわずかに性能が低下する場合があります',
certType: '証明書の種類',
selfSigned: '自己署名',
selfSignedHelper: `ブラウザは、自己署名の証明書を信頼していない場合があり、セキュリティ警告を表示する場合があります。`,

View file

@ -1890,7 +1890,7 @@ const message = {
error444: '연결 닫힘',
error500: '서버 오류',
https: '패널HTTPS 프로토콜 접근 설정은 패널 접근 보안을 강화할 있습니다.',
https: '패널HTTPS를 설정하면 보안이 향상됩니다.\nStrict 모드는 HTTPS가 아닌 접근을 차단합니다.\nMux는 HTTP를 HTTPS로 리다이렉트하지만 성능이 약간 저하될 있습니다.',
certType: '인증서 유형',
selfSigned: '자가 서명',
selfSignedHelper: '자가 서명 인증서는 브라우저에서 신뢰하지 않을 있으며 보안 경고가 표시될 있습니다.',

View file

@ -1977,7 +1977,7 @@ const message = {
error444: 'Sambungan ditutup',
error500: 'Ralat Pelayan',
https: 'Menetapkan protokol akses HTTPS untuk panel boleh meningkatkan keselamatan akses panel.',
https: 'Menetapkan HTTPS untuk panel meningkatkan keselamatan.\nMod Strict menyekat akses bukan HTTPS.\nMux mengalih hala HTTP ke HTTPS tetapi mungkin mengurangkan sedikit prestasi.',
certType: 'Jenis sijil',
selfSigned: 'Diterbitkan sendiri',
selfSignedHelper:

View file

@ -1965,7 +1965,7 @@ const message = {
error444: 'Conexão fechada',
error500: 'Erro no servidor',
https: 'Configurar o acesso via protocolo HTTPS para o painel pode melhorar a segurança do acesso ao painel.',
https: 'Configurar HTTPS para o painel melhora a segurança.\nO modo Strict bloqueia acesso sem HTTPS.\nMux redireciona HTTP para HTTPS, mas pode reduzir levemente o desempenho.',
certType: 'Tipo de certificado',
selfSigned: 'Autoassinado',
selfSignedHelper:

View file

@ -1963,7 +1963,7 @@ const message = {
error444: 'Соединение закрыто',
error500: 'Ошибка сервера',
https: 'Настройка доступа по протоколу HTTPS для панели может повысить безопасность доступа к панели.',
https: 'Настройка HTTPS для панели повышает безопасность.\nРежим Strict блокирует доступ без HTTPS.\nMux перенаправляет HTTP на HTTPS, но может немного снизить производительность.',
certType: 'Тип сертификата',
selfSigned: 'Самоподписанный',
selfSignedHelper:

View file

@ -2018,7 +2018,7 @@ const message = {
error416: 'Aralık Karşılanamadı',
error444: 'Bağlantı Kapalı',
error500: 'Dahili Sunucu Hatası',
https: 'Panel için HTTPS protokolü erişimini ayarlamak, panel erişiminin güvenliğini artırabilir.',
https: 'Panel için HTTPS kurmak güvenliği artırır.\nStrict modu HTTPS olmayan erişimi engeller.\nMux HTTPyi HTTPSe yönlendirir, ancak performansı biraz düşürebilir.',
certType: 'Sertifika türü',
selfSigned: 'Kendi kendine imzalı',
selfSignedHelper:

View file

@ -1944,7 +1944,7 @@ const message = {
error444: '連線已關閉',
error500: '內部伺服器錯誤',
https: '為面板設定 https 協議訪問提升面板訪問安全性',
https: '為面板設定 HTTPS 可提升安全性\nStrict 模式會阻擋非 HTTPS 連線\nMux 會將 HTTP 重新導向到 HTTPS但可能稍微降低效能',
certType: '證書類型',
selfSigned: '自簽名',
selfSignedHelper: '自簽證書不被瀏覽器信任顯示不安全是正常現象',

View file

@ -1943,7 +1943,7 @@ const message = {
error444: '连接被关闭',
error500: '内部错误',
https: '为面板设置 https 协议访问提升面板访问安全性',
https: '为面板设置https协议访问可提升面板访问安全性 \n Strict模式下非HTTPS流量无法连接面板\nMux模式会将 HTTP重定向到 HTTPS, 但可能会降低少许性能',
certType: '证书类型',
selfSigned: '自签名',
selfSignedHelper: '自签证书不被浏览器信任显示不安全是正常现象',

View file

@ -104,14 +104,13 @@
</el-form-item>
<el-form-item :label="$t('setting.panelSSL')" prop="ssl">
<el-switch
@change="handleSSL"
v-model="form.ssl"
active-value="Enable"
inactive-value="Disable"
/>
<el-radio-group v-model="form.ssl" @change="handleSSL">
<el-radio value="Disable">{{ $t('setting.sslDisable') }}</el-radio>
<el-radio value="Enable">{{ $t('commons.button.enable') }} (Strict)</el-radio>
<el-radio value="Mux">{{ $t('commons.button.enable') }} (Mux)</el-radio>
</el-radio-group>
<span class="input-help">{{ $t('setting.https') }}</span>
<div v-if="form.ssl === 'Enable' && sslInfo">
<div v-if="form.ssl !== 'Disable' && sslInfo">
<el-tag>{{ $t('setting.domainOrIP') }} {{ sslInfo.domain }}</el-tag>
<el-tag style="margin-left: 5px">
{{ $t('setting.timeOut') }} {{ sslInfo.timeout }}
@ -210,6 +209,7 @@ const mfaRef = ref();
const responseRef = ref();
const sslRef = ref();
const lastSSL = ref('Disable');
const sslInfo = ref<Setting.SSLInfo>();
const domainRef = ref();
const allowIPsRef = ref();
@ -243,8 +243,9 @@ const search = async () => {
form.ipv6 = res.data.ipv6;
form.bindAddress = res.data.bindAddress;
form.ssl = res.data.ssl;
lastSSL.value = form.ssl;
form.sslType = res.data.sslType;
if (form.ssl === 'Enable') {
if (form.ssl !== 'Disable') {
loadInfo();
}
form.securityEntrance = res.data.securityEntrance;
@ -326,7 +327,7 @@ const onChangeAllowIPs = () => {
allowIPsRef.value.acceptParams({ allowIPs: form.allowIPs });
};
const handleSSL = async () => {
if (form.ssl === 'Enable') {
if (form.ssl !== 'Disable') {
let params = {
ssl: form.ssl,
sslType: form.sslType,
@ -343,6 +344,7 @@ const handleSSL = async () => {
.then(async () => {
await updateSSL({ ssl: 'Disable', domain: '', sslType: form.sslType, key: '', cert: '', sslID: 0 });
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
lastSSL.value = 'Disable';
let href = window.location.href;
globalStore.isLogin = false;
let address = href.split('://')[1];
@ -356,7 +358,7 @@ const handleSSL = async () => {
}, 1000);
})
.catch(() => {
form.ssl = 'Enable';
form.ssl = lastSSL.value;
});
};

View file

@ -158,10 +158,12 @@ const sslList = ref();
const itemSSL = ref();
interface DialogProps {
ssl: string;
sslType: string;
sslInfo?: Setting.SSLInfo;
}
const acceptParams = async (params: DialogProps): Promise<void> => {
form.ssl = params.ssl;
if (params.sslType.indexOf('-') !== -1) {
form.sslType = 'import';
form.itemSSLType = params.sslType.split('-')[1];
@ -232,7 +234,7 @@ const onSaveSSL = async (formEl: FormInstance | undefined) => {
itemType = form.itemSSLType === 'paste' ? 'import-paste' : 'import-local';
}
let param = {
ssl: 'Enable',
ssl: form.ssl,
sslType: itemType,
domain: '',
sslID: form.sslID,