Compare commits

...
2 Commits
Author SHA1 Message Date
networkException 79bc17afab feat: add support for systemd listener activation sockets
To support starting a network service on demand and to
support a "least privilege-approach" with regards to the
permission a network service process needs to have, systemd
supports opening a network socket on behalf of the service
and passing it as an open file descriptor.

The service gets notified about open file descriptors for
this purpose as well as metadata such as named listeners
via environment variables.

This patch adds support for prefixing the listen address
passed with --address with "sd-listen-fd:" to access these
file descriptors, taking either a listener name passed using
the `LISTEN_FDNAMES` environment variable or `LISTEN_FD_$n`
for unnamed file descriptiors where `n` is the id of the
descriptor starting at 3 (LISTEN_FD_3).

See sd_listen_fds(3)
2024-12-11 17:29:03 +01:00
Henrique Dias 51b101d3d8 feat: rules behavior 2024-12-07 12:07:12 +01:00
7 changed files with 132 additions and 6 deletions
+13 -1
View File
@@ -86,9 +86,21 @@ directory: .
# permissions. For example, to allow to read and create, set "RC". Default is "R".
permissions: R
# The default permissions rules for users. Default is none.
# The default permissions rules for users. Default is none. Rules are applied
# from last to first, that is, the first rule that matches the request, starting
# from the end, will be applied to the request.
rules: []
# The behavior of redefining the rules for users. It can be:
# - overwrite: when a user has rules defined, these will overwrite any global
# rules already defined. That is, the global rules are not applicable to the
# user.
# - append: when a user has rules defined, these will be appended to the global
# rules already defined. That is, for this user, their own specific rules will
# be checked first, and then the global rules.
# Default is 'overwrite'.
rulesBehavior: overwrite
# Logging configuration
log:
# Logging format ('console', 'json'). Default is 'console'.
+16 -1
View File
@@ -10,6 +10,7 @@ import (
"strings"
"syscall"
"github.com/coreos/go-systemd/v22/activation"
"github.com/hacdias/webdav/v5/lib"
"github.com/spf13/cobra"
"go.uber.org/zap"
@@ -115,7 +116,21 @@ func getListener(cfg *lib.Config) (net.Listener, error) {
network string
)
if strings.HasPrefix(cfg.Address, "unix:") {
if strings.HasPrefix(cfg.Address, "sd-listen-fd:") {
listeners, err := activation.ListenersWithNames()
if err != nil {
return nil, err
}
address := cfg.Address[13:]
listener, ok := listeners[address]
if !ok || len(listener) < 1 {
return nil, errors.New("unknown sd-listen-fd address '" + address + "'")
}
return listener[0], nil
} else if strings.HasPrefix(cfg.Address, "unix:") {
address = cfg.Address[5:]
network = "unix"
} else {
+1
View File
@@ -3,6 +3,7 @@ module github.com/hacdias/webdav/v5
go 1.23
require (
github.com/coreos/go-systemd/v22 v22.3.2
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/rs/cors v1.11.1
github.com/spf13/cobra v1.8.1
+3
View File
@@ -1,3 +1,5 @@
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -8,6 +10,7 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+16 -1
View File
@@ -75,6 +75,7 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) {
v.SetDefault("Prefix", DefaultPrefix)
// Other defaults
v.SetDefault("RulesBehavior", RulesOverwrite)
v.SetDefault("Directory", ".")
v.SetDefault("Permissions", "R")
v.SetDefault("Debug", false)
@@ -115,7 +116,21 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) {
cfg.Users[i].Permissions = cfg.Permissions
}
if !v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) {
if !v.IsSet(fmt.Sprintf("Users.%d.RulesBehavior", i)) {
cfg.Users[i].RulesBehavior = cfg.RulesBehavior
}
if v.IsSet(fmt.Sprintf("Users.%d.Rules", i)) {
switch cfg.Users[i].RulesBehavior {
case RulesOverwrite:
// Do nothing
case RulesAppend:
rules := append([]*Rule{}, cfg.Rules...)
rules = append(rules, cfg.Users[i].Rules...)
cfg.Users[i].Rules = rules
}
} else {
cfg.Users[i].Rules = cfg.Rules
}
}
+65
View File
@@ -232,6 +232,71 @@ rules:
require.Nil(t, cfg.Rules[1].Regex)
})
t.Run("Rules Behavior (Default: Overwrite)", func(t *testing.T) {
content := `
directory: /
rules:
- regex: '^.+\.js$'
- path: /public/access/
users:
- username: foo
password: bar
rules:
- path: /private/access/`
cfg := writeAndParseConfig(t, content, ".yaml")
require.NoError(t, cfg.Validate())
require.Len(t, cfg.Rules, 2)
require.Empty(t, cfg.Rules[0].Path)
require.NotNil(t, cfg.Rules[0].Regex)
require.True(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.js"))
require.False(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.ts"))
require.EqualValues(t, "/public/access/", cfg.Rules[1].Path)
require.Nil(t, cfg.Rules[1].Regex)
require.Len(t, cfg.Users, 1)
require.Len(t, cfg.Users[0].Rules, 1)
require.EqualValues(t, "/private/access/", cfg.Users[0].Rules[0].Path)
})
t.Run("Rules Behavior (Append)", func(t *testing.T) {
content := `
directory: /
rules:
- regex: '^.+\.js$'
- path: /public/access/
rulesBehavior: append
users:
- username: foo
password: bar
rules:
- path: /private/access/`
cfg := writeAndParseConfig(t, content, ".yaml")
require.NoError(t, cfg.Validate())
require.Len(t, cfg.Rules, 2)
require.Empty(t, cfg.Rules[0].Path)
require.NotNil(t, cfg.Rules[0].Regex)
require.True(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.js"))
require.False(t, cfg.Rules[0].Regex.MatchString("/my/path/to/file.ts"))
require.EqualValues(t, "/public/access/", cfg.Rules[1].Path)
require.Nil(t, cfg.Rules[1].Regex)
require.Len(t, cfg.Users, 1)
require.Len(t, cfg.Users[0].Rules, 3)
require.EqualValues(t, cfg.Rules[0], cfg.Users[0].Rules[0])
require.EqualValues(t, cfg.Rules[1], cfg.Users[0].Rules[1])
require.EqualValues(t, "/private/access/", cfg.Users[0].Rules[2].Path)
})
}
func TestConfigEnv(t *testing.T) {
+18 -3
View File
@@ -36,10 +36,18 @@ func (r *Rule) Matches(path string) bool {
return strings.HasPrefix(path, r.Path)
}
type RulesBehavior string
const (
RulesOverwrite RulesBehavior = "overwrite"
RulesAppend RulesBehavior = "append"
)
type UserPermissions struct {
Directory string
Permissions Permissions
Rules []*Rule
Directory string
Permissions Permissions
Rules []*Rule
RulesBehavior RulesBehavior
}
// Allowed checks if the user has permission to access a directory/file
@@ -91,6 +99,13 @@ func (p *UserPermissions) Validate() error {
}
}
switch p.RulesBehavior {
case RulesAppend, RulesOverwrite:
// Good to go
default:
return fmt.Errorf("invalid rule behavior: %s", p.RulesBehavior)
}
return nil
}