Merge branch 'main' into v2
* main: feat(config): add option for audit file (fixes #9481) (#10066) chore(api): log X-Forwarded-For (#10035) chore(gui): update dependency copyrights, add script for periodic maintenance (#10067) chore(gui, man, authors): update docs, translations, and contributors chore(syncthing): remove support for TLS 1.2 sync connections (#10064) fix(osutil): give threads same I/O priority on Linux (#10063) chore(stun): switch lookup warning to debug level chore(gui, man, authors): update docs, translations, and contributors
This commit is contained in:
+48
-8
@@ -18,6 +18,7 @@ import (
|
||||
ldap "github.com/go-ldap/ldap/v3"
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
)
|
||||
|
||||
@@ -27,15 +28,54 @@ const (
|
||||
randomTokenLength = 64
|
||||
)
|
||||
|
||||
func emitLoginAttempt(success bool, username, address string, evLogger events.Logger) {
|
||||
evLogger.Log(events.LoginAttempt, map[string]interface{}{
|
||||
func emitLoginAttempt(success bool, username string, r *http.Request, evLogger events.Logger) {
|
||||
remoteAddress, proxy := remoteAddress(r)
|
||||
evData := map[string]any{
|
||||
"success": success,
|
||||
"username": username,
|
||||
"remoteAddress": address,
|
||||
})
|
||||
if !success {
|
||||
l.Infof("Wrong credentials supplied during API authorization from %s", address)
|
||||
"remoteAddress": remoteAddress,
|
||||
}
|
||||
if proxy != "" {
|
||||
evData["proxy"] = proxy
|
||||
}
|
||||
evLogger.Log(events.LoginAttempt, evData)
|
||||
|
||||
if success {
|
||||
return
|
||||
}
|
||||
if proxy != "" {
|
||||
l.Infof("Wrong credentials supplied during API authorization from %s proxied by %s", remoteAddress, proxy)
|
||||
} else {
|
||||
l.Infof("Wrong credentials supplied during API authorization from %s", remoteAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func remoteAddress(r *http.Request) (remoteAddr, proxy string) {
|
||||
remoteAddr = r.RemoteAddr
|
||||
remoteIP := osutil.IPFromString(r.RemoteAddr)
|
||||
|
||||
// parse X-Forwarded-For only if the proxy connects via unix socket, localhost or a LAN IP
|
||||
var localProxy bool
|
||||
if remoteIP != nil {
|
||||
remoteAddr = remoteIP.String()
|
||||
localProxy = remoteIP.IsLoopback() || remoteIP.IsPrivate() || remoteIP.IsLinkLocalUnicast()
|
||||
} else if remoteAddr == "@" {
|
||||
localProxy = true
|
||||
}
|
||||
|
||||
if !localProxy {
|
||||
return
|
||||
}
|
||||
|
||||
forwardedAddr, _, _ := strings.Cut(r.Header.Get("X-Forwarded-For"), ",")
|
||||
forwardedAddr = strings.TrimSpace(forwardedAddr)
|
||||
forwardedIP := osutil.IPFromString(forwardedAddr)
|
||||
|
||||
if forwardedIP != nil {
|
||||
proxy = remoteAddr
|
||||
remoteAddr = forwardedIP.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func antiBruteForceSleep() {
|
||||
@@ -152,7 +192,7 @@ func (m *basicAuthAndSessionMiddleware) passwordAuthHandler(w http.ResponseWrite
|
||||
return
|
||||
}
|
||||
|
||||
emitLoginAttempt(false, req.Username, r.RemoteAddr, m.evLogger)
|
||||
emitLoginAttempt(false, req.Username, r, m.evLogger)
|
||||
antiBruteForceSleep()
|
||||
forbidden(w)
|
||||
}
|
||||
@@ -175,7 +215,7 @@ func attemptBasicAuth(r *http.Request, guiCfg config.GUIConfiguration, ldapCfg c
|
||||
return usernameFromIso, true
|
||||
}
|
||||
|
||||
emitLoginAttempt(false, username, r.RemoteAddr, evLogger)
|
||||
emitLoginAttempt(false, username, r, evLogger)
|
||||
antiBruteForceSleep()
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ func (m *tokenCookieManager) createSession(username string, persistent bool, w h
|
||||
Path: "/",
|
||||
})
|
||||
|
||||
emitLoginAttempt(true, username, r.RemoteAddr, m.evLogger)
|
||||
emitLoginAttempt(true, username, r, m.evLogger)
|
||||
}
|
||||
|
||||
func (m *tokenCookieManager) hasValidSession(r *http.Request) bool {
|
||||
|
||||
@@ -92,6 +92,8 @@ func TestDefaultValues(t *testing.T) {
|
||||
RawStunServers: []string{"default"},
|
||||
AnnounceLANAddresses: true,
|
||||
FeatureFlags: []string{},
|
||||
AuditEnabled: false,
|
||||
AuditFile: "",
|
||||
ConnectionPriorityTCPLAN: 10,
|
||||
ConnectionPriorityQUICLAN: 20,
|
||||
ConnectionPriorityTCPWAN: 30,
|
||||
@@ -295,6 +297,8 @@ func TestOverriddenValues(t *testing.T) {
|
||||
StunKeepaliveMinS: 900,
|
||||
RawStunServers: []string{"foo"},
|
||||
FeatureFlags: []string{"feature"},
|
||||
AuditEnabled: true,
|
||||
AuditFile: "nggyu",
|
||||
ConnectionPriorityTCPLAN: 40,
|
||||
ConnectionPriorityQUICLAN: 45,
|
||||
ConnectionPriorityTCPWAN: 50,
|
||||
|
||||
@@ -67,22 +67,21 @@ type OptionsConfiguration struct {
|
||||
AnnounceLANAddresses bool `json:"announceLANAddresses" xml:"announceLANAddresses" default:"true"`
|
||||
SendFullIndexOnUpgrade bool `json:"sendFullIndexOnUpgrade" xml:"sendFullIndexOnUpgrade"`
|
||||
FeatureFlags []string `json:"featureFlags" xml:"featureFlag"`
|
||||
AuditEnabled bool `json:"auditEnabled" xml:"auditEnabled" default:"false"`
|
||||
AuditFile string `json:"auditFile" xml:"auditFile"`
|
||||
// The number of connections at which we stop trying to connect to more
|
||||
// devices, zero meaning no limit. Does not affect incoming connections.
|
||||
ConnectionLimitEnough int `json:"connectionLimitEnough" xml:"connectionLimitEnough"`
|
||||
// The maximum number of connections which we will allow in total, zero
|
||||
// meaning no limit. Affects incoming connections and prevents
|
||||
// attempting outgoing connections.
|
||||
ConnectionLimitMax int `json:"connectionLimitMax" xml:"connectionLimitMax"`
|
||||
// When set, this allows TLS 1.2 on sync connections, where we otherwise
|
||||
// default to TLS 1.3+ only.
|
||||
InsecureAllowOldTLSVersions bool `json:"insecureAllowOldTLSVersions" xml:"insecureAllowOldTLSVersions"`
|
||||
ConnectionPriorityTCPLAN int `json:"connectionPriorityTcpLan" xml:"connectionPriorityTcpLan" default:"10"`
|
||||
ConnectionPriorityQUICLAN int `json:"connectionPriorityQuicLan" xml:"connectionPriorityQuicLan" default:"20"`
|
||||
ConnectionPriorityTCPWAN int `json:"connectionPriorityTcpWan" xml:"connectionPriorityTcpWan" default:"30"`
|
||||
ConnectionPriorityQUICWAN int `json:"connectionPriorityQuicWan" xml:"connectionPriorityQuicWan" default:"40"`
|
||||
ConnectionPriorityRelay int `json:"connectionPriorityRelay" xml:"connectionPriorityRelay" default:"50"`
|
||||
ConnectionPriorityUpgradeThreshold int `json:"connectionPriorityUpgradeThreshold" xml:"connectionPriorityUpgradeThreshold" default:"0"`
|
||||
ConnectionLimitMax int `json:"connectionLimitMax" xml:"connectionLimitMax"`
|
||||
ConnectionPriorityTCPLAN int `json:"connectionPriorityTcpLan" xml:"connectionPriorityTcpLan" default:"10"`
|
||||
ConnectionPriorityQUICLAN int `json:"connectionPriorityQuicLan" xml:"connectionPriorityQuicLan" default:"20"`
|
||||
ConnectionPriorityTCPWAN int `json:"connectionPriorityTcpWan" xml:"connectionPriorityTcpWan" default:"30"`
|
||||
ConnectionPriorityQUICWAN int `json:"connectionPriorityQuicWan" xml:"connectionPriorityQuicWan" default:"40"`
|
||||
ConnectionPriorityRelay int `json:"connectionPriorityRelay" xml:"connectionPriorityRelay" default:"50"`
|
||||
ConnectionPriorityUpgradeThreshold int `json:"connectionPriorityUpgradeThreshold" xml:"connectionPriorityUpgradeThreshold" default:"0"`
|
||||
// Legacy deprecated
|
||||
DeprecatedUPnPEnabled bool `json:"-" xml:"upnpEnabled,omitempty"` // Deprecated: Do not use.
|
||||
DeprecatedUPnPLeaseM int `json:"-" xml:"upnpLeaseMinutes,omitempty"` // Deprecated: Do not use.
|
||||
@@ -187,7 +186,7 @@ func (opts OptionsConfiguration) StunServers() []string {
|
||||
case "default":
|
||||
_, records, err := net.LookupSRV("stun", "udp", "syncthing.net")
|
||||
if err != nil {
|
||||
l.Warnln("Unable to resolve primary STUN servers via DNS:", err)
|
||||
l.Debugf("Unable to resolve primary STUN servers via DNS:", err)
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
|
||||
+2
@@ -45,6 +45,8 @@
|
||||
<unackedNotificationID>asdfasdf</unackedNotificationID>
|
||||
<announceLANAddresses>false</announceLANAddresses>
|
||||
<featureFlag>feature</featureFlag>
|
||||
<auditEnabled>true</auditEnabled>
|
||||
<auditFile>nggyu</auditFile>
|
||||
<connectionPriorityTcpLan>40</connectionPriorityTcpLan>
|
||||
<connectionPriorityQuicLan>45</connectionPriorityQuicLan>
|
||||
<connectionPriorityTcpWan>50</connectionPriorityTcpWan>
|
||||
|
||||
@@ -15,32 +15,6 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const ioprioClassShift = 13
|
||||
|
||||
type ioprioClass int
|
||||
|
||||
const (
|
||||
ioprioClassRT ioprioClass = iota + 1
|
||||
ioprioClassBE
|
||||
ioprioClassIdle
|
||||
)
|
||||
|
||||
const (
|
||||
ioprioWhoProcess = iota + 1
|
||||
ioprioWhoPGRP
|
||||
ioprioWhoUser
|
||||
)
|
||||
|
||||
func ioprioSet(class ioprioClass, value int) error {
|
||||
res, _, err := syscall.Syscall(syscall.SYS_IOPRIO_SET,
|
||||
uintptr(ioprioWhoProcess), 0,
|
||||
uintptr(class)<<ioprioClassShift|uintptr(value))
|
||||
if res == 0 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetLowPriority lowers the process CPU scheduling priority, and possibly
|
||||
// I/O priority depending on the platform and OS.
|
||||
func SetLowPriority() error {
|
||||
@@ -89,14 +63,13 @@ func SetLowPriority() error {
|
||||
}
|
||||
}
|
||||
|
||||
// For any new process, the default is to be assigned the IOPRIO_CLASS_BE
|
||||
// scheduling class. This class directly maps the BE prio level to the
|
||||
// niceness of a process, determined as: io_nice = (cpu_nice + 20) / 5.
|
||||
// For example, a niceness of 11 results in an I/O priority of B6.
|
||||
// https://www.kernel.org/doc/Documentation/block/ioprio.txt
|
||||
if err := syscall.Setpriority(syscall.PRIO_PGRP, pidSelf, wantNiceLevel); err != nil {
|
||||
return fmt.Errorf("set niceness: %w", err)
|
||||
}
|
||||
|
||||
// Best effort, somewhere to the end of the scale (0 through 7 being the
|
||||
// range).
|
||||
if err := ioprioSet(ioprioClassBE, 5); err != nil {
|
||||
return fmt.Errorf("set I/O priority: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package osutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetInterfaceAddrs returns the IP networks of all interfaces that are up.
|
||||
@@ -46,6 +47,17 @@ func GetInterfaceAddrs(includePtP bool) ([]*net.IPNet, error) {
|
||||
return nets, nil
|
||||
}
|
||||
|
||||
func IPFromString(addr string) net.IP {
|
||||
// strip the port
|
||||
host, _, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
host = addr
|
||||
}
|
||||
// strip IPv6 zone identifier
|
||||
host, _, _ = strings.Cut(host, "%")
|
||||
return net.ParseIP(host)
|
||||
}
|
||||
|
||||
func IPFromAddr(addr net.Addr) (net.IP, error) {
|
||||
switch a := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
|
||||
@@ -135,3 +135,35 @@ func TestRenameOrCopy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPFromString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"192.168.178.1", "192.168.178.1"},
|
||||
{"192.168.178.1:8384", "192.168.178.1"},
|
||||
{"fe80::20c:29ff:fe9a:46d2", "fe80::20c:29ff:fe9a:46d2"},
|
||||
{"[fe80::20c:29ff:fe9a:46d2]:8384", "fe80::20c:29ff:fe9a:46d2"},
|
||||
{"[fe80::20c:29ff:fe9a:46d2%eno1]:8384", "fe80::20c:29ff:fe9a:46d2"},
|
||||
{"google.com", ""},
|
||||
{"1.1.1.1.1", ""},
|
||||
{"", ""},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
ip := osutil.IPFromString(c.in)
|
||||
var address string
|
||||
if ip != nil {
|
||||
address = ip.String()
|
||||
} else {
|
||||
address = ""
|
||||
}
|
||||
|
||||
if c.out != address {
|
||||
t.Fatalf("result should be %s != %s", c.out, address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,13 +251,7 @@ func (a *App) startup() error {
|
||||
// The TLS configuration is used for both the listening socket and outgoing
|
||||
// connections.
|
||||
|
||||
var tlsCfg *tls.Config
|
||||
if a.cfg.Options().InsecureAllowOldTLSVersions {
|
||||
l.Infoln("TLS 1.2 is allowed on sync connections. This is less than optimally secure.")
|
||||
tlsCfg = tlsutil.SecureDefaultWithTLS12()
|
||||
} else {
|
||||
tlsCfg = tlsutil.SecureDefaultTLS13()
|
||||
}
|
||||
tlsCfg := tlsutil.SecureDefaultTLS13()
|
||||
tlsCfg.Certificates = []tls.Certificate{a.cert}
|
||||
tlsCfg.NextProtos = []string{bepProtocolName}
|
||||
tlsCfg.ClientAuth = tls.RequestClientCert
|
||||
|
||||
Reference in New Issue
Block a user