Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
601 changes: 302 additions & 299 deletions go.mod

Large diffs are not rendered by default.

1,634 changes: 797 additions & 837 deletions go.sum

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions internal/app/server/overlayfs-errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package server

const (
PathErrorOpLstat = "lstat"
PathErrorOpOpen = "open"
PathErrorOpRead = "read"
PathErrorOpReadDir = "readdir"
PathErrorOpResolve = "resolve"
PathErrorOpStat = "stat"
PathErrorOpSub = "sub"
)
40 changes: 40 additions & 0 deletions internal/app/server/overlayfs-structures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package server

import (
"iter"
"maps"
)

// Set

type Set[Node comparable] map[Node]struct{}

// Add adds the node to the set. If the node was already in the set, nothing changes.
func (s Set[Node]) Add(n ...Node) {
for _, node := range n {
s[node] = struct{}{}
}
}

// Has checks whether the node is already in the set.
func (s Set[Node]) Has(n Node) bool {
_, ok := s[n]
return ok
}

// Difference creates a new set with the difference between the set whose method is called and the
// provided set.
func (s Set[Node]) Difference(t Set[Node]) Set[Node] {
difference := make(Set[Node])
for node := range s {
if !t.Has(node) {
difference.Add(node)
}
}
return difference
}

// All returns an iterator over all elements in s.
func (s Set[Node]) All() iter.Seq[Node] {
return maps.Keys(s)
}
52 changes: 8 additions & 44 deletions internal/app/server/overlayfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package server
import (
"cmp"
"io/fs"
"iter"
"maps"
"path"
"slices"

Expand Down Expand Up @@ -34,15 +32,15 @@ func (f *OverlayFS) Open(name string) (fs.File, error) {
switch {
default:
return nil, &fs.PathError{
Op: "open",
Op: PathErrorOpOpen,
Path: name,
Err: errors.Wrapf(err, "couldn't open file %s in upper", name),
}
case errors.Is(err, fs.ErrNotExist):
file, err = f.Lower.Open(name)
if err != nil {
return nil, &fs.PathError{
Op: "open",
Op: PathErrorOpOpen,
Path: name,
Err: errors.Wrapf(err, "couldn't open file %s in lower", name),
}
Expand Down Expand Up @@ -91,14 +89,14 @@ func (f *OverlayFS) ReadDir(name string) (entries []fs.DirEntry, err error) {
if err == nil {
if !info.IsDir() {
return nil, &fs.PathError{
Op: "read",
Op: PathErrorOpRead,
Path: name,
Err: errors.Wrapf(err, "%s is a non-directory file in upper", name),
}
}
if entries, err = fs.ReadDir(f.Upper, name); err != nil {
return nil, &fs.PathError{
Op: "read",
Op: PathErrorOpRead,
Path: name,
Err: errors.Wrapf(err, "couldn't read directory %s in upper", name),
}
Expand All @@ -112,15 +110,15 @@ func (f *OverlayFS) ReadDir(name string) (entries []fs.DirEntry, err error) {
if err == nil {
if !info.IsDir() {
return nil, &fs.PathError{
Op: "read",
Op: PathErrorOpRead,
Path: name,
Err: errors.Wrapf(err, "%s is a non-directory file in lower", name),
}
}
lowerEntries, err := fs.ReadDir(f.Lower, name)
if err != nil {
return nil, &fs.PathError{
Op: "read",
Op: PathErrorOpRead,
Path: name,
Err: errors.Wrapf(err, "couldn't read directory %s in lower", name),
}
Expand Down Expand Up @@ -177,15 +175,15 @@ func (f *OverlayFS) Stat(name string) (fs.FileInfo, error) {
switch {
default:
return nil, &fs.PathError{
Op: "stat",
Op: PathErrorOpStat,
Path: name,
Err: errors.Wrapf(err, "couldn't stat file %s in upper", name),
}
case errors.Is(err, fs.ErrNotExist):
info, err := fs.Stat(f.Lower, name)
if err != nil {
return nil, &fs.PathError{
Op: "stat",
Op: PathErrorOpStat,
Path: name,
Err: errors.Wrapf(err, "couldn't stat file %s in lower", name),
}
Expand All @@ -195,37 +193,3 @@ func (f *OverlayFS) Stat(name string) (fs.FileInfo, error) {
return info, nil
}
}

// Set

type Set[Node comparable] map[Node]struct{}

// Add adds the node to the set. If the node was already in the set, nothing changes.
func (s Set[Node]) Add(n ...Node) {
for _, node := range n {
s[node] = struct{}{}
}
}

// Has checks whether the node is already in the set.
func (s Set[Node]) Has(n Node) bool {
_, ok := s[n]
return ok
}

// Difference creates a new set with the difference between the set whose method is called and the
// provided set.
func (s Set[Node]) Difference(t Set[Node]) Set[Node] {
difference := make(Set[Node])
for node := range s {
if !t.Has(node) {
difference.Add(node)
}
}
return difference
}

// All returns an iterator over all elements in s.
func (s Set[Node]) All() iter.Seq[Node] {
return maps.Keys(s)
}
26 changes: 16 additions & 10 deletions internal/app/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,39 +105,45 @@ const turboDriveStyle = `.turbo-progress-bar {
}
`

const (
cspNone = "'none'"
cspSelf = "'self'"
cspUnsafeInline = "'unsafe-inline'"
)

func (s *Server) configureHeaders(e *echo.Echo) error {
cspBuilder := cspbuilder.Builder{
Directives: map[string][]string{
cspbuilder.DefaultSrc: {"'self'"},
cspbuilder.DefaultSrc: {cspSelf},
// Note: the following is needed for the Tailscale web GUI to check device status (but the GUI
// still works without this permission, it just doesn't report device status):
cspbuilder.ConnectSrc: {"*"},
// Note: script-src "unsafe-inline" (which is ignored if we provide one or more hashes for
// CSP) is needed by the Tailscale web GUI:
cspbuilder.ScriptSrc: {"'self'", "'unsafe-inline'"},
cspbuilder.ScriptSrc: {cspSelf, cspUnsafeInline},
// cspbuilder.ScriptSrc: append(
// // Warning: script-src 'self' may not be safe to use if we're hosting user-uploaded content.
// // Then we'll need to provide hashes for scripts & styles we include by URL, and we'll need
// // to add the SRI integrity attribute to the tags including those files; however, it's
// // unclear how well-supported they are by browsers.
// []string{"'self'", "'unsafe-inline'"},
// []string{cspSelf, cspUnsafeInline},
// s.Inlines.ComputeJSHashesForCSP()...,
// ),
cspbuilder.StyleSrc: append(
[]string{
"'self'",
"'unsafe-inline'",
cspSelf,
cspUnsafeInline,
godest.ComputeCSPHash([]byte(turboDriveStyle)),
},
s.Inlines.ComputeCSSHashesForCSP()...,
),
cspbuilder.ObjectSrc: {"'none'"},
cspbuilder.ChildSrc: {"'self'"},
cspbuilder.ObjectSrc: {cspNone},
cspbuilder.ChildSrc: {cspSelf},
// Note: img-src with scheme "data:" is needed by the Tailscale web GUI:
cspbuilder.ImgSrc: {"*", "data:"},
cspbuilder.BaseURI: {"'none'"},
cspbuilder.FormAction: {"'self'"},
cspbuilder.FrameAncestors: {"'self'"},
cspbuilder.BaseURI: {cspNone},
cspbuilder.FormAction: {cspSelf},
cspbuilder.FrameAncestors: {cspSelf},
// TODO: add HTTPS-related settings for CSP, including upgrade-insecure-requests
},
}
Expand Down
11 changes: 6 additions & 5 deletions internal/clients/networkmanager/active-conns.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:goconst // many constants here are taken from the NetworkManager docs
package networkmanager

import (
Expand Down Expand Up @@ -31,27 +32,27 @@ type ActiveConnState uint32
var activeConnectionStateInfo = map[ActiveConnState]EnumInfo{
0: {
Short: "unknown",
Level: "warning",
Level: EnumInfoLevelWarning,
},
1: {
Short: "activating",
Details: "network connection is being prepared",
Level: "info",
Level: EnumInfoLevelInfo,
},
2: {
Short: "activated",
Details: "there is a connection to the network",
Level: "success",
Level: EnumInfoLevelSuccess,
},
3: {
Short: "deactivating",
Details: "network connection is being torn down and cleaned up",
Level: "info",
Level: EnumInfoLevelInfo,
},
4: {
Short: "deactivating",
Details: "network connection is disconnected and will be removed",
Level: "info",
Level: EnumInfoLevelInfo,
},
}

Expand Down
28 changes: 15 additions & 13 deletions internal/clients/networkmanager/client.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// Package networkmanager provides an interface for NetworkManager via its D-Bus API.

//nolint:goconst // many constants here are taken from the NetworkManager docs
package networkmanager

import (
Expand Down Expand Up @@ -61,42 +63,42 @@ var networkManagerState = map[NetworkManagerState]EnumInfo{
0: {
Short: "unknown",
Details: "NetworkManager daemon error makes it unable to reasonably assess state",
Level: "error",
Level: EnumInfoLevelError,
},
10: {
Short: "disabled",
Details: "NetworkManager is disabled",
Level: "warning",
Level: EnumInfoLevelWarning,
},
20: {
Short: "disconnected",
Details: "no active network connection",
Level: "warning",
Level: EnumInfoLevelWarning,
},
30: {
Short: "disconnecting",
Details: "network connections are being cleaned up",
Level: "info",
Level: EnumInfoLevelInfo,
},
40: {
Short: "connecting",
Details: "network connection is being started",
Level: "info",
Level: EnumInfoLevelInfo,
},
50: {
Short: "locally connected",
Details: "no default route to access the internet",
Level: "info",
Level: EnumInfoLevelInfo,
},
60: {
Short: "site-wide connected",
Details: "default route is available, but internet connectivity check didn't succeed",
Level: "info",
Level: EnumInfoLevelInfo,
},
70: {
Short: "globally connected",
Details: "internet connectivity check succeeded",
Level: "success",
Level: EnumInfoLevelSuccess,
},
}

Expand All @@ -118,27 +120,27 @@ var networkManagerConnectivity = map[NetworkManagerConnectivity]EnumInfo{
0: {
Short: "unknown",
Details: "connectivity checks are disabled or have not yet been run",
Level: "info",
Level: EnumInfoLevelInfo,
},
1: {
Short: "none",
Details: "not connected to any network",
Level: "warning",
Level: EnumInfoLevelWarning,
},
2: {
Short: "portal",
Details: "internet connection is hijacked by a captive portal gateway",
Level: "info",
Level: EnumInfoLevelInfo,
},
3: {
Short: "limited",
Details: "connected to a network, unable to reach full internet; no captive portal detected",
Level: "info",
Level: EnumInfoLevelInfo,
},
4: {
Short: "full",
Details: "connected to network and able to reach full internet",
Level: "success",
Level: EnumInfoLevelSuccess,
},
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:goconst // many constants here are taken from the NetworkManager docs
package networkmanager

import (
Expand Down
4 changes: 4 additions & 0 deletions internal/clients/networkmanager/conn-profiles-settings.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:goconst // many constants here are taken from the NetworkManager docs
package networkmanager

import (
Expand Down Expand Up @@ -336,6 +337,9 @@ func dumpConnProfileSettingsIPv4(
if err != nil {
return s, err
}
if rawLinkLocal > math.MaxInt32 || rawLinkLocal < math.MinInt32 {
return s, errors.Errorf("link-local %d would overflow int32", rawLinkLocal)
}
s.LinkLocal = ConnProfileSettingsIPv4LinkLocal(rawLinkLocal)
rawMethod, err := ensureVar(rawSettings, "method", "", true, "")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/clients/networkmanager/conn-profiles.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//nolint:goconst // many constants here are taken from the NetworkManager docs
package networkmanager

import (
Expand Down
7 changes: 6 additions & 1 deletion internal/clients/networkmanager/dbus-enums.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ type EnumInfo struct {
Level string
}

const EnumInfoLevelError = "error"
const (
EnumInfoLevelError = "error"
EnumInfoLevelWarning = "warning"
EnumInfoLevelInfo = "info"
EnumInfoLevelSuccess = "success"
)

// EnumSet

Expand Down
Loading
Loading