agent: use interface for file permissions

This commit is contained in:
Ryan Uber 2015-01-20 18:53:18 -08:00
parent 1e0cd2e499
commit c669a17fa6
6 changed files with 79 additions and 44 deletions

View File

@ -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)

View File

@ -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...)

View File

@ -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",
},
}

View File

@ -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()

View File

@ -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",

View File

@ -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)