agent: use interface for file permissions

This commit is contained in:
Ryan Uber 2015-01-20 18:53:18 -08:00
parent 782b0ddd88
commit b1dae530d4
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 conf.Addresses.RPC = "unix://" + socketPath
// Custom mode for socket file // Custom mode for socket file
conf.UnixSockets = map[string]string{"mode": "0777"} conf.UnixSockets.Perms = "0777"
shutdownCh := make(chan struct{}) shutdownCh := make(chan struct{})
defer close(shutdownCh) defer close(shutdownCh)

View File

@ -345,7 +345,27 @@ type Config struct {
WatchPlans []*watch.WatchPlan `mapstructure:"-" json:"-"` WatchPlans []*watch.WatchPlan `mapstructure:"-" json:"-"`
// UnixSockets is a map of socket configuration data // 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, // unixSocketAddr tests if a given address describes a domain socket,
@ -886,6 +906,15 @@ func MergeConfig(a, b *Config) *Config {
if b.DisableAnonymousSignature { if b.DisableAnonymousSignature {
result.DisableAnonymousSignature = true 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 len(b.HTTPAPIResponseHeaders) != 0 {
if result.HTTPAPIResponseHeaders == nil { 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 // Copy the start join addresses
result.StartJoin = make([]string, 0, len(a.StartJoin)+len(b.StartJoin)) result.StartJoin = make([]string, 0, len(a.StartJoin)+len(b.StartJoin))
result.StartJoin = append(result.StartJoin, a.StartJoin...) result.StartJoin = append(result.StartJoin, a.StartJoin...)

View File

@ -595,12 +595,14 @@ func TestDecodeConfig(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
if !reflect.DeepEqual(config.UnixSockets, map[string]string{ if config.UnixSockets.Usr != "500" {
"user": "500", t.Fatalf("bad: %#v", config)
"group": "500", }
"mode": "0700", if config.UnixSockets.Grp != "500" {
}) { t.Fatalf("bad: %#v", config)
t.Fatalf("bad: %v", config.UnixSockets) }
if config.UnixSockets.Perms != "0700" {
t.Fatalf("bad: %#v", config)
} }
// Disable updates // Disable updates
@ -1013,10 +1015,10 @@ func TestMergeConfig(t *testing.T) {
HTTPAPIResponseHeaders: map[string]string{ HTTPAPIResponseHeaders: map[string]string{
"Access-Control-Allow-Origin": "*", "Access-Control-Allow-Origin": "*",
}, },
UnixSockets: map[string]string{ UnixSockets: UnixSocketConfig{
"user": "500", Usr: "500",
"group": "500", Grp: "500",
"mode": "0700", Perms: "0700",
}, },
} }

View File

@ -70,7 +70,7 @@ func TestHTTPServer_UnixSocket(t *testing.T) {
// Only testing mode, since uid/gid might not be settable // Only testing mode, since uid/gid might not be settable
// from test environment. // from test environment.
c.UnixSockets = map[string]string{"mode": "0777"} c.UnixSockets = UnixSocketConfig{Perms: "0777"}
}) })
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
defer srv.Shutdown() defer srv.Shutdown()

View File

@ -100,34 +100,47 @@ func stringHash(s string) string {
return fmt.Sprintf("%x", md5.Sum([]byte(s))) 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 // setFilePermissions handles configuring ownership and permissions settings
// on a given file. It takes a map, which defines the permissions to be set. // on a given file. It takes a path and any struct implementing the
// All permission/ownership settings are optional. If no user or group is // FilePermissions interface. All permission/ownership settings are optional.
// specified, the current user/group will be used. Mode is optional, and has // If no user or group is specified, the current user/group will be used. Mode
// no default (the operation is not performed if absent). User may be // is optional, and has no default (the operation is not performed if absent).
// specified by name or ID, but group may only be specified by ID. // 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 { func setFilePermissions(path string, p FilePermissions) error {
var err error var err error
uid, gid := os.Getuid(), os.Getgid() uid, gid := os.Getuid(), os.Getgid()
if _, ok := perms["user"]; ok { if p.User() != "" {
if uid, err = strconv.Atoi(perms["user"]); err == nil { if uid, err = strconv.Atoi(p.User()); err == nil {
goto GROUP goto GROUP
} }
// Try looking up the user by name // 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) uid, _ = strconv.Atoi(u.Uid)
goto GROUP goto GROUP
} }
return fmt.Errorf("invalid user specified: %v", perms["user"]) return fmt.Errorf("invalid user specified: %v", p.User())
} }
GROUP: GROUP:
if _, ok := perms["group"]; ok { if p.Group() != "" {
if gid, err = strconv.Atoi(perms["group"]); err != nil { if gid, err = strconv.Atoi(p.Group()); err != nil {
return fmt.Errorf("invalid group specified: %v", perms["group"]) return fmt.Errorf("invalid group specified: %v", p.Group())
} }
} }
if err := os.Chown(path, uid, gid); err != nil { if err := os.Chown(path, uid, gid); err != nil {
@ -135,10 +148,10 @@ GROUP:
uid, gid, path, err) uid, gid, path, err)
} }
if _, ok := perms["mode"]; ok { if p.Mode() != "" {
mode, err := strconv.ParseUint(perms["mode"], 8, 32) mode, err := strconv.ParseUint(p.Mode(), 8, 32)
if err != nil { 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 { if err := os.Chmod(path, os.FileMode(mode)); err != nil {
return fmt.Errorf("failed setting permissions to %d on %q: %s", 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) defer os.Remove(path)
// Bad UID fails // 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") t.Fatalf("should fail")
} }
// Bad GID fails // 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") t.Fatalf("should fail")
} }
// Bad mode fails // 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") t.Fatalf("should fail")
} }
// Allows omitting user/group/mode // 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) t.Fatalf("err: %s", err)
} }
@ -74,7 +74,7 @@ func TestSetFilePermissions(t *testing.T) {
if err := os.Chmod(path, 0700); err != nil { if err := os.Chmod(path, 0700); err != nil {
t.Fatalf("err: %s", err) 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) t.Fatalf("err: %s", err)
} }
fi, err := os.Stat(path) fi, err := os.Stat(path)
@ -86,7 +86,7 @@ func TestSetFilePermissions(t *testing.T) {
} }
// Changes mode if given // 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) t.Fatalf("err: %s", err)
} }
fi, err = os.Stat(path) fi, err = os.Stat(path)