diff --git a/command/agent/agent.go b/command/agent/agent.go index 9454dba4c0..e73af4222d 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -23,13 +23,6 @@ const ( // Path to save local agent checks checksDir = "checks" - // errSocketFileExists is the human-friendly error message displayed when - // trying to bind a socket to an existing file. - errSocketFileExists = "A file exists at the requested socket path %q. " + - "If Consul was not shut down properly, the socket file may " + - "be left behind. If the path looks correct, remove the file " + - "and try again." - // The ID of the faux health checks for maintenance mode serviceMaintCheckPrefix = "_service_maintenance" nodeMaintCheckID = "_node_maintenenace" diff --git a/command/agent/command.go b/command/agent/command.go index 3368918776..96b2a0ef9d 100644 --- a/command/agent/command.go +++ b/command/agent/command.go @@ -295,11 +295,15 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log return err } - // Error if we are trying to bind a domain socket to an existing path - if path, ok := unixSocketAddr(config.Addresses.RPC); ok { - if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { - c.Ui.Output(fmt.Sprintf(errSocketFileExists, path)) - return fmt.Errorf(errSocketFileExists, path) + // Clear the domain socket file if it exists + socketPath, isSocket := unixSocketAddr(config.Addresses.RPC) + if isSocket { + if _, err := os.Stat(socketPath); !os.IsNotExist(err) { + agent.logger.Printf("[WARN] agent: Replacing socket %q", socketPath) + } + if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { + c.Ui.Output(fmt.Sprintf("Error removing socket file: %s", err)) + return err } } @@ -310,6 +314,15 @@ func (c *Command) setupAgent(config *Config, logOutput io.Writer, logWriter *log return err } + // Set up ownership/permission bits on the socket file + if isSocket { + if err := setFilePermissions(socketPath, config.UnixSockets); err != nil { + agent.Shutdown() + c.Ui.Error(fmt.Sprintf("Error setting up socket: %s", err)) + return err + } + } + // Start the IPC layer c.Ui.Output("Starting Consul agent RPC...") c.rpcServer = NewAgentRPC(agent, rpcListener, logOutput, logWriter) diff --git a/command/agent/config.go b/command/agent/config.go index 92b9e64d37..28fb461378 100644 --- a/command/agent/config.go +++ b/command/agent/config.go @@ -343,6 +343,9 @@ type Config struct { // WatchPlans contains the compiled watches WatchPlans []*watch.WatchPlan `mapstructure:"-" json:"-"` + + // UnixSockets is a map of socket configuration data + UnixSockets map[string]string `mapstructure:"unix_sockets"` } // unixSocketAddr tests if a given address describes a domain socket, @@ -893,6 +896,15 @@ 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/http.go b/command/agent/http.go index 23e4163048..0d736734c6 100644 --- a/command/agent/http.go +++ b/command/agent/http.go @@ -96,9 +96,13 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS } // Error if we are trying to bind a domain socket to an existing path - if path, ok := unixSocketAddr(config.Addresses.HTTP); ok { - if _, err := os.Stat(path); err == nil || !os.IsNotExist(err) { - return nil, fmt.Errorf(errSocketFileExists, path) + socketPath, isSocket := unixSocketAddr(config.Addresses.HTTP) + if isSocket { + if _, err := os.Stat(socketPath); !os.IsNotExist(err) { + agent.logger.Printf("[WARN] agent: Replacing socket %q", socketPath) + } + if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("error removing socket file: %s", err) } } @@ -113,6 +117,13 @@ func NewHTTPServers(agent *Agent, config *Config, logOutput io.Writer) ([]*HTTPS list = tcpKeepAliveListener{ln.(*net.TCPListener)} } + // Set up ownership/permission bits on the socket file + if isSocket { + if err := setFilePermissions(socketPath, config.UnixSockets); err != nil { + return nil, fmt.Errorf("Failed setting up HTTP socket: %s", err) + } + } + // Create the mux mux := http.NewServeMux() diff --git a/command/agent/util.go b/command/agent/util.go index d8cb465299..a9c897d482 100644 --- a/command/agent/util.go +++ b/command/agent/util.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "runtime" + "strconv" "time" "github.com/hashicorp/go-msgpack/codec" @@ -97,3 +98,43 @@ func encodeMsgPack(msg interface{}) ([]byte, error) { func stringHash(s string) string { return fmt.Sprintf("%x", md5.Sum([]byte(s))) } + +// 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). +func setFilePermissions(path string, perms map[string]string) error { + var err error + + uid, gid := os.Getuid(), os.Getgid() + if _, ok := perms["uid"]; ok { + if uid, err = strconv.Atoi(perms["uid"]); err != nil { + return fmt.Errorf("invalid user id specified: %v", perms["uid"]) + } + } + if _, ok := perms["gid"]; ok { + if gid, err = strconv.Atoi(perms["gid"]); err != nil { + return fmt.Errorf("invalid group id specified: %v", perms["gid"]) + } + } + if err := os.Chown(path, uid, gid); err != nil { + return fmt.Errorf( + "failed setting ownership to %d:%d on %q: %s", + uid, gid, path, err) + } + + if _, ok := perms["mode"]; ok { + mode, err := strconv.ParseUint(perms["mode"], 8, 32) + if err != nil { + return fmt.Errorf("invalid mode specified for %q: %s", + path, perms["mode"]) + } + if err := os.Chmod(path, os.FileMode(mode)); err != nil { + return fmt.Errorf("failed setting permissions to %d on %q: %s", + mode, path, err) + } + } + + return nil +}