diff --git a/command/agent/command_test.go b/command/agent/command_test.go index 94f7d845b5..58129bba92 100644 --- a/command/agent/command_test.go +++ b/command/agent/command_test.go @@ -188,7 +188,7 @@ func TestSetupAgent_RPCUnixSocket_FileExists(t *testing.T) { conf.Addresses.RPC = "unix://" + socketPath // Custom mode for socket file - conf.UnixSockets = map[string]string{"mode": "0777"} + conf.UnixSockets.Perms = "0777" shutdownCh := make(chan struct{}) defer close(shutdownCh) diff --git a/command/agent/config.go b/command/agent/config.go index 28fb461378..163fa18712 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -345,7 +345,27 @@ type Config struct { WatchPlans []*watch.WatchPlan `mapstructure:"-" json:"-"` // UnixSockets is a map of socket configuration data - UnixSockets map[string]string `mapstructure:"unix_sockets"` + UnixSockets UnixSocketConfig `mapstructure:"unix_sockets"` +} + +// UnixSocketConfig contains information about a unix socket, and +// implements the FilePermissions interface. +type UnixSocketConfig struct { + Usr string `mapstructure:"user"` + Grp string `mapstructure:"group"` + Perms string `mapstructure:"mode"` +} + +func (u UnixSocketConfig) User() string { + return u.Usr +} + +func (u UnixSocketConfig) Group() string { + return u.Grp +} + +func (u UnixSocketConfig) Mode() string { + return u.Perms } // unixSocketAddr tests if a given address describes a domain socket, @@ -886,6 +906,15 @@ func MergeConfig(a, b *Config) *Config { if b.DisableAnonymousSignature { result.DisableAnonymousSignature = true } + if b.UnixSockets.Usr != "" { + result.UnixSockets.Usr = b.UnixSockets.Usr + } + if b.UnixSockets.Grp != "" { + result.UnixSockets.Grp = b.UnixSockets.Grp + } + if b.UnixSockets.Perms != "" { + result.UnixSockets.Perms = b.UnixSockets.Perms + } if len(b.HTTPAPIResponseHeaders) != 0 { if result.HTTPAPIResponseHeaders == nil { @@ -896,15 +925,6 @@ func MergeConfig(a, b *Config) *Config { } } - if len(b.UnixSockets) != 0 { - if result.UnixSockets == nil { - result.UnixSockets = make(map[string]string) - } - for field, value := range b.UnixSockets { - result.UnixSockets[field] = value - } - } - // Copy the start join addresses result.StartJoin = make([]string, 0, len(a.StartJoin)+len(b.StartJoin)) result.StartJoin = append(result.StartJoin, a.StartJoin...) diff --git a/command/agent/config_test.go b/command/agent/config_test.go index bab0a645d2..afdb245233 100644 --- a/command/agent/config_test.go +++ b/command/agent/config_test.go @@ -595,12 +595,14 @@ func TestDecodeConfig(t *testing.T) { t.Fatalf("err: %s", err) } - if !reflect.DeepEqual(config.UnixSockets, map[string]string{ - "user": "500", - "group": "500", - "mode": "0700", - }) { - t.Fatalf("bad: %v", config.UnixSockets) + if config.UnixSockets.Usr != "500" { + t.Fatalf("bad: %#v", config) + } + if config.UnixSockets.Grp != "500" { + t.Fatalf("bad: %#v", config) + } + if config.UnixSockets.Perms != "0700" { + t.Fatalf("bad: %#v", config) } // Disable updates @@ -1013,10 +1015,10 @@ func TestMergeConfig(t *testing.T) { HTTPAPIResponseHeaders: map[string]string{ "Access-Control-Allow-Origin": "*", }, - UnixSockets: map[string]string{ - "user": "500", - "group": "500", - "mode": "0700", + UnixSockets: UnixSocketConfig{ + Usr: "500", + Grp: "500", + Perms: "0700", }, } diff --git a/command/agent/http_test.go b/command/agent/http_test.go index f3caeafb4b..67d597ed21 100644 --- a/command/agent/http_test.go +++ b/command/agent/http_test.go @@ -70,7 +70,7 @@ func TestHTTPServer_UnixSocket(t *testing.T) { // Only testing mode, since uid/gid might not be settable // from test environment. - c.UnixSockets = map[string]string{"mode": "0777"} + c.UnixSockets = UnixSocketConfig{Perms: "0777"} }) defer os.RemoveAll(dir) defer srv.Shutdown() diff --git a/command/agent/util.go b/command/agent/util.go index 67fd81c3e2..5f0bcff511 100644 --- a/command/agent/util.go +++ b/command/agent/util.go @@ -100,34 +100,47 @@ func stringHash(s string) string { return fmt.Sprintf("%x", md5.Sum([]byte(s))) } +// FilePermissions is an interface which allows a struct to set +// ownership and permissions easily on a file it describes. +type FilePermissions interface { + // User returns a user ID or user name + User() string + + // Group returns a group ID. Group names are not supported. + Group() string + + // Mode returns a string of file mode bits e.g. "0644" + Mode() string +} + // setFilePermissions handles configuring ownership and permissions settings -// on a given file. It takes a map, which defines the permissions to be set. -// All permission/ownership settings are optional. If no user or group is -// specified, the current user/group will be used. Mode is optional, and has -// no default (the operation is not performed if absent). User may be -// specified by name or ID, but group may only be specified by ID. -func setFilePermissions(path string, perms map[string]string) error { +// on a given file. It takes a path and any struct implementing the +// FilePermissions interface. All permission/ownership settings are optional. +// If no user or group is specified, the current user/group will be used. Mode +// is optional, and has no default (the operation is not performed if absent). +// User may be specified by name or ID, but group may only be specified by ID. +func setFilePermissions(path string, p FilePermissions) error { var err error uid, gid := os.Getuid(), os.Getgid() - if _, ok := perms["user"]; ok { - if uid, err = strconv.Atoi(perms["user"]); err == nil { + if p.User() != "" { + if uid, err = strconv.Atoi(p.User()); err == nil { goto GROUP } // Try looking up the user by name - if u, err := user.Lookup(perms["user"]); err == nil { + if u, err := user.Lookup(p.User()); err == nil { uid, _ = strconv.Atoi(u.Uid) goto GROUP } - return fmt.Errorf("invalid user specified: %v", perms["user"]) + return fmt.Errorf("invalid user specified: %v", p.User()) } GROUP: - if _, ok := perms["group"]; ok { - if gid, err = strconv.Atoi(perms["group"]); err != nil { - return fmt.Errorf("invalid group specified: %v", perms["group"]) + if p.Group() != "" { + if gid, err = strconv.Atoi(p.Group()); err != nil { + return fmt.Errorf("invalid group specified: %v", p.Group()) } } if err := os.Chown(path, uid, gid); err != nil { @@ -135,10 +148,10 @@ GROUP: uid, gid, path, err) } - if _, ok := perms["mode"]; ok { - mode, err := strconv.ParseUint(perms["mode"], 8, 32) + if p.Mode() != "" { + mode, err := strconv.ParseUint(p.Mode(), 8, 32) if err != nil { - return fmt.Errorf("invalid mode specified: %v", perms["mode"]) + return fmt.Errorf("invalid mode specified: %v", p.Mode()) } if err := os.Chmod(path, os.FileMode(mode)); err != nil { return fmt.Errorf("failed setting permissions to %d on %q: %s", diff --git a/command/agent/util_test.go b/command/agent/util_test.go index f6d1b35aaa..df5dba3fad 100644 --- a/command/agent/util_test.go +++ b/command/agent/util_test.go @@ -51,22 +51,22 @@ func TestSetFilePermissions(t *testing.T) { defer os.Remove(path) // Bad UID fails - if err := setFilePermissions(path, map[string]string{"user": "%"}); err == nil { + if err := setFilePermissions(path, UnixSocketConfig{Usr: "%"}); err == nil { t.Fatalf("should fail") } // Bad GID fails - if err := setFilePermissions(path, map[string]string{"group": "%"}); err == nil { + if err := setFilePermissions(path, UnixSocketConfig{Grp: "%"}); err == nil { t.Fatalf("should fail") } // Bad mode fails - if err := setFilePermissions(path, map[string]string{"mode": "%"}); err == nil { + if err := setFilePermissions(path, UnixSocketConfig{Perms: "%"}); err == nil { t.Fatalf("should fail") } // Allows omitting user/group/mode - if err := setFilePermissions(path, map[string]string{}); err != nil { + if err := setFilePermissions(path, UnixSocketConfig{}); err != nil { t.Fatalf("err: %s", err) } @@ -74,7 +74,7 @@ func TestSetFilePermissions(t *testing.T) { if err := os.Chmod(path, 0700); err != nil { t.Fatalf("err: %s", err) } - if err := setFilePermissions(path, map[string]string{}); err != nil { + if err := setFilePermissions(path, UnixSocketConfig{}); err != nil { t.Fatalf("err: %s", err) } fi, err := os.Stat(path) @@ -86,7 +86,7 @@ func TestSetFilePermissions(t *testing.T) { } // Changes mode if given - if err := setFilePermissions(path, map[string]string{"mode": "0777"}); err != nil { + if err := setFilePermissions(path, UnixSocketConfig{Perms: "0777"}); err != nil { t.Fatalf("err: %s", err) } fi, err = os.Stat(path)