Pull request: all: support setgid, setuid on unix
Updates #2763. Squashed commit of the following: commit bd2077c6569b53ae341a58aa73de6063d7037e8e Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Jun 4 16:25:17 2021 +0300 all: move rlimit_nofile, imp docs commit ba95d4ab7c722bf83300d626a598aface37539ad Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Fri Jun 4 15:12:23 2021 +0300 all: support setgid, setuid on unix
This commit is contained in:
@@ -35,6 +35,19 @@ type logSettings struct {
|
||||
Verbose bool `yaml:"verbose"` // If true, verbose logging is enabled
|
||||
}
|
||||
|
||||
// osConfig contains OS-related configuration.
|
||||
type osConfig struct {
|
||||
// Group is the name of the group which AdGuard Home must switch to on
|
||||
// startup. Empty string means no switching.
|
||||
Group string `yaml:"group"`
|
||||
// User is the name of the user which AdGuard Home must switch to on
|
||||
// startup. Empty string means no switching.
|
||||
User string `yaml:"user"`
|
||||
// RlimitNoFile is the maximum number of opened fd's per process. Zero
|
||||
// means use the default value.
|
||||
RlimitNoFile uint64 `yaml:"rlimit_nofile"`
|
||||
}
|
||||
|
||||
// configuration is loaded from YAML
|
||||
// field ordering is important -- yaml fields will mirror ordering from here
|
||||
type configuration struct {
|
||||
@@ -52,10 +65,9 @@ type configuration struct {
|
||||
// AuthBlockMin is the duration, in minutes, of the block of new login
|
||||
// attempts after AuthAttempts unsuccessful login attempts.
|
||||
AuthBlockMin uint `yaml:"block_auth_min"`
|
||||
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
|
||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||
RlimitNoFile uint64 `yaml:"rlimit_nofile"` // Maximum number of opened fd's per process (0: default)
|
||||
DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060
|
||||
ProxyURL string `yaml:"http_proxy"` // Proxy address for our HTTP client
|
||||
Language string `yaml:"language"` // two-letter ISO 639-1 language code
|
||||
DebugPProf bool `yaml:"debug_pprof"` // Enable pprof HTTP server on port 6060
|
||||
|
||||
// TTL for a web session (in hours)
|
||||
// An active session is automatically refreshed once a day.
|
||||
@@ -75,6 +87,8 @@ type configuration struct {
|
||||
|
||||
logSettings `yaml:",inline"`
|
||||
|
||||
OSConfig *osConfig `yaml:"os"`
|
||||
|
||||
sync.RWMutex `yaml:"-"`
|
||||
|
||||
SchemaVersion int `yaml:"schema_version"` // keeping last so that users will be less tempted to change it -- used when upgrading between versions
|
||||
@@ -184,6 +198,7 @@ var config = configuration{
|
||||
LogMaxSize: 100,
|
||||
LogMaxAge: 3,
|
||||
},
|
||||
OSConfig: &osConfig{},
|
||||
SchemaVersion: currentSchemaVersion,
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ func Main(clientBuildFS fs.FS) {
|
||||
// support OpenBSD currently. Either patch it to do so or make
|
||||
// our own implementation of the service.System interface.
|
||||
if runtime.GOOS == "openbsd" {
|
||||
log.Fatal("service actions are not supported on openbsd")
|
||||
log.Fatal("service actions are not supported on openbsd, see issue 3226")
|
||||
}
|
||||
|
||||
handleServiceControlAction(args, clientBuildFS)
|
||||
@@ -183,6 +183,59 @@ func setupContext(args options) {
|
||||
Context.mux = http.NewServeMux()
|
||||
}
|
||||
|
||||
// logIfUnsupported logs a formatted warning if the error is one of the
|
||||
// unsupported errors and returns nil. If err is nil, logIfUnsupported returns
|
||||
// nil. Otherise, it returns err.
|
||||
func logIfUnsupported(msg string, err error) (outErr error) {
|
||||
if unsupErr := (&aghos.UnsupportedError{}); errors.As(err, &unsupErr) {
|
||||
log.Debug(msg, err)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// configureOS sets the OS-related configuration.
|
||||
func configureOS(conf *configuration) (err error) {
|
||||
osConf := conf.OSConfig
|
||||
if osConf == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if osConf.Group != "" {
|
||||
err = aghos.SetGroup(osConf.Group)
|
||||
err = logIfUnsupported("warning: setting group", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting group: %w", err)
|
||||
}
|
||||
|
||||
log.Info("group set to %s", osConf.Group)
|
||||
}
|
||||
|
||||
if osConf.User != "" {
|
||||
err = aghos.SetUser(osConf.User)
|
||||
err = logIfUnsupported("warning: setting user", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting user: %w", err)
|
||||
}
|
||||
|
||||
log.Info("user set to %s", osConf.User)
|
||||
}
|
||||
|
||||
if osConf.RlimitNoFile != 0 {
|
||||
err = aghos.SetRlimit(osConf.RlimitNoFile)
|
||||
err = logIfUnsupported("warning: setting rlimit", err)
|
||||
if err != nil {
|
||||
return fmt.Errorf("setting rlimit: %w", err)
|
||||
}
|
||||
|
||||
log.Info("rlimit_nofile set to %d", osConf.RlimitNoFile)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func setupConfig(args options) (err error) {
|
||||
config.DHCP.WorkDir = Context.workDir
|
||||
config.DHCP.HTTPRegister = httpRegister
|
||||
@@ -216,13 +269,6 @@ func setupConfig(args options) (err error) {
|
||||
Context.clients.Init(config.Clients, Context.dhcpServer, Context.etcHosts)
|
||||
config.Clients = nil
|
||||
|
||||
if config.RlimitNoFile != 0 {
|
||||
err = aghos.SetRlimit(config.RlimitNoFile)
|
||||
if err != nil && !errors.Is(err, aghos.ErrUnsupported) {
|
||||
return fmt.Errorf("setting rlimit: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// override bind host/port from the console
|
||||
if args.bindHost != nil {
|
||||
config.BindHost = args.bindHost
|
||||
@@ -309,6 +355,9 @@ func run(args options, clientBuildFS fs.FS) {
|
||||
|
||||
setupContext(args)
|
||||
|
||||
err = configureOS(&config)
|
||||
fatalOnError(err)
|
||||
|
||||
// clients package uses filtering package's static data (filtering.BlockedSvcKnown()),
|
||||
// so we have to initialize filtering's static data first,
|
||||
// but also avoid relying on automatic Go init() function
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// currentSchemaVersion is the current schema version.
|
||||
const currentSchemaVersion = 10
|
||||
const currentSchemaVersion = 11
|
||||
|
||||
// These aliases are provided for convenience.
|
||||
type (
|
||||
@@ -81,6 +81,7 @@ func upgradeConfigSchema(oldVersion int, diskConf yobj) (err error) {
|
||||
upgradeSchema7to8,
|
||||
upgradeSchema8to9,
|
||||
upgradeSchema9to10,
|
||||
upgradeSchema10to11,
|
||||
}
|
||||
|
||||
n := 0
|
||||
@@ -611,6 +612,41 @@ func upgradeSchema9to10(diskConf yobj) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// upgradeSchema10to11 performs the following changes:
|
||||
//
|
||||
// # BEFORE:
|
||||
// 'rlimit_nofile': 42
|
||||
//
|
||||
// # AFTER:
|
||||
// 'os':
|
||||
// 'group': ''
|
||||
// 'rlimit_nofile': 42
|
||||
// 'user': ''
|
||||
//
|
||||
func upgradeSchema10to11(diskConf yobj) (err error) {
|
||||
log.Printf("Upgrade yaml: 10 to 11")
|
||||
|
||||
diskConf["schema_version"] = 11
|
||||
|
||||
rlimit := 0
|
||||
rlimitVal, ok := diskConf["rlimit_nofile"]
|
||||
if ok {
|
||||
rlimit, ok = rlimitVal.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type of rlimit_nofile: %T", rlimitVal)
|
||||
}
|
||||
}
|
||||
|
||||
delete(diskConf, "rlimit_nofile")
|
||||
diskConf["os"] = yobj{
|
||||
"group": "",
|
||||
"rlimit_nofile": rlimit,
|
||||
"user": "",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Replace with log.Output when we port it to our logging
|
||||
// package.
|
||||
func funcName() string {
|
||||
|
||||
@@ -368,3 +368,50 @@ func TestUpgradeSchema9to10(t *testing.T) {
|
||||
assert.Equal(t, "unexpected type of dns: int", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpgradeSchema10to11(t *testing.T) {
|
||||
check := func(t *testing.T, conf yobj) {
|
||||
rlimit, _ := conf["rlimit_nofile"].(int)
|
||||
|
||||
err := upgradeSchema10to11(conf)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, conf["schema_version"], 11)
|
||||
|
||||
_, ok := conf["rlimit_nofile"]
|
||||
assert.False(t, ok)
|
||||
|
||||
osVal, ok := conf["os"]
|
||||
require.True(t, ok)
|
||||
|
||||
newOSConf, ok := osVal.(yobj)
|
||||
require.True(t, ok)
|
||||
|
||||
_, ok = newOSConf["group"]
|
||||
assert.True(t, ok)
|
||||
|
||||
_, ok = newOSConf["user"]
|
||||
assert.True(t, ok)
|
||||
|
||||
rlimitVal, ok := newOSConf["rlimit_nofile"].(int)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, rlimit, rlimitVal)
|
||||
}
|
||||
|
||||
const rlimit = 42
|
||||
t.Run("with_rlimit", func(t *testing.T) {
|
||||
conf := yobj{
|
||||
"rlimit_nofile": rlimit,
|
||||
"schema_version": 10,
|
||||
}
|
||||
check(t, conf)
|
||||
})
|
||||
|
||||
t.Run("without_rlimit", func(t *testing.T) {
|
||||
conf := yobj{
|
||||
"schema_version": 10,
|
||||
}
|
||||
check(t, conf)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user