This commit is contained in:
parent
76635274fa
commit
18896997df
@ -7,6 +7,7 @@ Wants=network-online.target
|
||||
Type=simple
|
||||
User=smail
|
||||
Group=smail
|
||||
EnvironmentFile=/etc/smail/smail.env
|
||||
WorkingDirectory=/var/lib/smail
|
||||
ExecStart=/usr/local/lib/smail/smail
|
||||
Restart=on-failure
|
||||
@ -16,10 +17,12 @@ RuntimeDirectoryMode=0750
|
||||
# 目录由 smail 进程拥有,允许主服务创建受保护的本机控制套接字。
|
||||
|
||||
NoNewPrivileges=true
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
PrivateTmp=true
|
||||
ProtectHome=true
|
||||
ProtectSystem=full
|
||||
ReadWritePaths=/var/lib/smail /run/smail
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@ -72,3 +72,30 @@ func TestInstallerDisablesAdminAutostart(t *testing.T) {
|
||||
t.Fatal("installer does not clear legacy smail-admin enablement")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmailServiceUsesOnlyBindServiceCapability(t *testing.T) {
|
||||
root := filepath.Clean(filepath.Join("..", "..", ".."))
|
||||
unit, err := os.ReadFile(filepath.Join(root, "deploy", "systemd", "smail.service"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content := string(unit)
|
||||
for _, required := range []string{
|
||||
"User=smail",
|
||||
"Group=smail",
|
||||
"EnvironmentFile=/etc/smail/smail.env",
|
||||
"NoNewPrivileges=true",
|
||||
"AmbientCapabilities=CAP_NET_BIND_SERVICE",
|
||||
"CapabilityBoundingSet=CAP_NET_BIND_SERVICE",
|
||||
} {
|
||||
if !strings.Contains(content, required) {
|
||||
t.Fatalf("smail unit is missing %q", required)
|
||||
}
|
||||
}
|
||||
if strings.Contains(content, "User=root") {
|
||||
t.Fatal("smail unit must not run as root")
|
||||
}
|
||||
if strings.Count(content, "CAP_NET_BIND_SERVICE") != 2 {
|
||||
t.Fatal("smail unit grants unexpected Linux capabilities")
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
@ -55,21 +57,59 @@ func CurrentHTTPConfig() HTTPConfig {
|
||||
if manager := CurrentManager(); manager != nil {
|
||||
runtime := manager.Snapshot()
|
||||
if runtime.Generation != 0 {
|
||||
return cloneHTTPConfig(runtime.Root.HTTP)
|
||||
return ResolveHTTPPortDefaults(runtime.Root.HTTP, RuntimeSetupPort())
|
||||
}
|
||||
}
|
||||
cfg := CurrentConfig()
|
||||
if cfg == nil {
|
||||
return HTTPConfig{}
|
||||
return ResolveHTTPPortDefaults(HTTPConfig{}, RuntimeSetupPort())
|
||||
}
|
||||
mode := strings.ToLower(strings.TrimSpace(cfg.TLSMode))
|
||||
return HTTPConfig{
|
||||
return ResolveHTTPPortDefaults(HTTPConfig{
|
||||
Port: cfg.HttpPort,
|
||||
HTTPSPort: cfg.HttpsPort,
|
||||
HTTPSEnabled: cfg.HttpsEnabled != 2,
|
||||
TLSMode: mode,
|
||||
TrustedProxyCIDRs: append([]string(nil), cfg.TrustedProxyCIDRs...),
|
||||
}, cfg.GetSetupPort())
|
||||
}
|
||||
|
||||
func ResolveHTTPPortDefaults(value HTTPConfig, setupPort int) HTTPConfig {
|
||||
result := cloneHTTPConfig(value)
|
||||
result.TLSMode = strings.ToLower(strings.TrimSpace(result.TLSMode))
|
||||
if result.Port == 0 {
|
||||
result.Port = environmentPort("SMAIL_HTTP_PORT")
|
||||
if result.Port == 0 && validServicePort(setupPort) {
|
||||
result.Port = setupPort
|
||||
}
|
||||
if result.Port == 0 {
|
||||
result.Port = 80
|
||||
}
|
||||
}
|
||||
if result.TLSMode == HTTPSTLSModeProxy {
|
||||
result.HTTPSPort = 0
|
||||
result.HTTPSEnabled = false
|
||||
return result
|
||||
}
|
||||
if result.HTTPSPort == 0 {
|
||||
result.HTTPSPort = environmentPort("SMAIL_HTTPS_PORT")
|
||||
if result.HTTPSPort == 0 {
|
||||
result.HTTPSPort = 443
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func environmentPort(name string) int {
|
||||
port, err := strconv.Atoi(strings.TrimSpace(os.Getenv(name)))
|
||||
if err != nil || !validServicePort(port) {
|
||||
return 0
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
func validServicePort(port int) bool {
|
||||
return port > 0 && port <= 65535
|
||||
}
|
||||
|
||||
// CurrentWebDomain 返回当前进程实际使用的 Web 域名。
|
||||
|
||||
51
server/config/runtime_access_port_test.go
Normal file
51
server/config/runtime_access_port_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestResolveHTTPPortDefaultsPreservesConfiguredPorts(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT", "19001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "19002")
|
||||
|
||||
got := ResolveHTTPPortDefaults(HTTPConfig{
|
||||
Port: 9001,
|
||||
HTTPSPort: 9002,
|
||||
HTTPSEnabled: true,
|
||||
}, 18080)
|
||||
if got.Port != 9001 || got.HTTPSPort != 9002 {
|
||||
t.Fatalf("HTTP ports = %d/%d, want 9001/9002", got.Port, got.HTTPSPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsUsesEnvironmentPorts(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
got := ResolveHTTPPortDefaults(HTTPConfig{HTTPSEnabled: true}, 18080)
|
||||
if got.Port != 9001 || got.HTTPSPort != 9002 {
|
||||
t.Fatalf("HTTP ports = %d/%d, want 9001/9002", got.Port, got.HTTPSPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsUsesSetupAndDirectDefaults(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT", "")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "")
|
||||
|
||||
got := ResolveHTTPPortDefaults(HTTPConfig{HTTPSEnabled: true}, 9001)
|
||||
if got.Port != 9001 || got.HTTPSPort != 443 {
|
||||
t.Fatalf("HTTP ports = %d/%d, want 9001/443", got.Port, got.HTTPSPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveHTTPPortDefaultsDisablesInternalHTTPSInProxyMode(t *testing.T) {
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
got := ResolveHTTPPortDefaults(HTTPConfig{
|
||||
HTTPSPort: 9443,
|
||||
HTTPSEnabled: true,
|
||||
TLSMode: " PROXY ",
|
||||
}, 18080)
|
||||
if got.Port != 9001 || got.HTTPSPort != 0 || got.HTTPSEnabled {
|
||||
t.Fatalf("proxy HTTP config = %+v, want HTTP 9001 with internal HTTPS disabled", got)
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,13 @@ func Finish() error {
|
||||
if err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
portsChanged := applyServicePorts(cfg)
|
||||
if cfg.IsSetupReady() {
|
||||
if portsChanged {
|
||||
if err = config.WriteConfig(cfg); err != nil {
|
||||
return errors.Wrap(err)
|
||||
}
|
||||
}
|
||||
config.SetInitState(true)
|
||||
return nil
|
||||
}
|
||||
@ -37,3 +43,23 @@ func Finish() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyServicePorts(cfg *config.Config) bool {
|
||||
if cfg == nil {
|
||||
return false
|
||||
}
|
||||
resolved := config.ResolveHTTPPortDefaults(config.HTTPConfig{
|
||||
Port: cfg.HttpPort,
|
||||
HTTPSPort: cfg.HttpsPort,
|
||||
HTTPSEnabled: cfg.HttpsEnabled != 2,
|
||||
TLSMode: cfg.TLSMode,
|
||||
}, cfg.GetSetupPort())
|
||||
changed := cfg.HttpPort != resolved.Port || cfg.HttpsPort != resolved.HTTPSPort
|
||||
cfg.HttpPort = resolved.Port
|
||||
cfg.HttpsPort = resolved.HTTPSPort
|
||||
if resolved.TLSMode == config.HTTPSTLSModeProxy && cfg.HttpsEnabled != 2 {
|
||||
cfg.HttpsEnabled = 2
|
||||
changed = true
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
112
server/services/setup/finish_test.go
Normal file
112
server/services/setup/finish_test.go
Normal file
@ -0,0 +1,112 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/config"
|
||||
)
|
||||
|
||||
func TestFinishPersistsServicePorts(t *testing.T) {
|
||||
prepareFinishConfigTest(t)
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
config.SetRuntimeSetupPort(18080)
|
||||
|
||||
if err := config.WriteConfig(&config.Config{
|
||||
SetupState: config.SetupStateBootstrap,
|
||||
HttpsEnabled: 1,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Finish(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loaded, err := config.ReadConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loaded.HttpPort != 9001 || loaded.HttpsPort != 9002 {
|
||||
t.Fatalf("persisted HTTP ports = %d/%d, want 9001/9002", loaded.HttpPort, loaded.HttpsPort)
|
||||
}
|
||||
if loaded.SetupState != config.SetupStateReady || !loaded.IsInit {
|
||||
t.Fatalf("setup state = %q, isInit = %t", loaded.SetupState, loaded.IsInit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishRepairsReadyConfigWithMissingPorts(t *testing.T) {
|
||||
prepareFinishConfigTest(t)
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
if err := config.WriteConfig(&config.Config{
|
||||
SetupState: config.SetupStateReady,
|
||||
IsInit: true,
|
||||
HttpsEnabled: 1,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Finish(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loaded, err := config.ReadConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loaded.HttpPort != 9001 || loaded.HttpsPort != 9002 {
|
||||
t.Fatalf("repaired HTTP ports = %d/%d, want 9001/9002", loaded.HttpPort, loaded.HttpsPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishKeepsInternalHTTPSDisabledInProxyMode(t *testing.T) {
|
||||
prepareFinishConfigTest(t)
|
||||
t.Setenv("SMAIL_HTTP_PORT", "9001")
|
||||
t.Setenv("SMAIL_HTTPS_PORT", "9002")
|
||||
|
||||
if err := config.WriteConfig(&config.Config{
|
||||
SetupState: config.SetupStateBootstrap,
|
||||
TLSMode: config.HTTPSTLSModeProxy,
|
||||
HttpsPort: 9443,
|
||||
HttpsEnabled: 1,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := Finish(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
loaded, err := config.ReadConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if loaded.HttpPort != 9001 || loaded.HttpsPort != 0 || loaded.HttpsEnabled != 2 {
|
||||
t.Fatalf("proxy config = HTTP %d, HTTPS %d, enabled %d", loaded.HttpPort, loaded.HttpsPort, loaded.HttpsEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareFinishConfigTest(t *testing.T) {
|
||||
t.Helper()
|
||||
previousRoot := config.ROOT_PATH
|
||||
previousInstance := config.Instance
|
||||
previousManager := config.CurrentManager()
|
||||
previousSetupPort := config.RuntimeSetupPort()
|
||||
previousInitState := config.IsInit
|
||||
|
||||
config.ROOT_PATH = t.TempDir() + string(os.PathSeparator)
|
||||
config.Instance = nil
|
||||
config.PublishConfigSnapshot(nil)
|
||||
config.SetRuntimeManager(nil)
|
||||
config.SetRuntimeSetupPort(0)
|
||||
config.SetInitState(false)
|
||||
|
||||
t.Cleanup(func() {
|
||||
config.ROOT_PATH = previousRoot
|
||||
config.Instance = previousInstance
|
||||
config.PublishConfigSnapshot(previousInstance)
|
||||
config.SetRuntimeManager(previousManager)
|
||||
config.SetRuntimeSetupPort(previousSetupPort)
|
||||
config.SetInitState(previousInitState)
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user