diff --git a/plugins/inputs/chrony/README.md b/plugins/inputs/chrony/README.md index c3710f7c78b9e..95cda2f72d06e 100644 --- a/plugins/inputs/chrony/README.md +++ b/plugins/inputs/chrony/README.md @@ -40,8 +40,23 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details. ## sources -- extended information about peers ## sourcestats -- statistics on peers # metrics = ["tracking"] + + ## Socket group & permissions + ## If the user requests collecting metrics via unix socket, then it is created + ## with the following group and permissions. + # socket_group = "chrony" + # socket_perms = "0660" ``` +## Local socket permissions + +To use the unix socket, telegraf must be able to talk to it. Please ensure that +the telegraf user is a member of the `chrony` group or telegraf won't be able to +use the socket! + +The unix socket is needed in order to use the `serverstats` metrics. All other +metrics can be gathered using the udp connection. + ## Metrics - chrony diff --git a/plugins/inputs/chrony/chrony.go b/plugins/inputs/chrony/chrony.go index e1e9faac37929..d7a596820a14c 100644 --- a/plugins/inputs/chrony/chrony.go +++ b/plugins/inputs/chrony/chrony.go @@ -9,11 +9,15 @@ import ( "fmt" "net" "net/url" + "os" + "os/user" + "path" "strconv" "syscall" "time" fbchrony "github.com/facebook/time/ntp/chrony" + "github.com/google/uuid" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" @@ -24,21 +28,61 @@ import ( var sampleConfig string type Chrony struct { - Server string `toml:"server"` - Timeout config.Duration `toml:"timeout"` - DNSLookup bool `toml:"dns_lookup"` - Metrics []string `toml:"metrics"` - Log telegraf.Logger `toml:"-"` + Server string `toml:"server"` + Timeout config.Duration `toml:"timeout"` + DNSLookup bool `toml:"dns_lookup"` + SocketGroup string `toml:"socket_group"` + SocketPerms string `toml:"socket_perms"` + Metrics []string `toml:"metrics"` + Log telegraf.Logger `toml:"-"` conn net.Conn client *fbchrony.Client source string + local string } func (*Chrony) SampleConfig() string { return sampleConfig } +// dialUnix opens an unixgram connection with chrony +func (c *Chrony) dialUnix(address string) (*net.UnixConn, error) { + dir := path.Dir(address) + c.local = path.Join(dir, fmt.Sprintf("chrony-telegraf-%s.sock", uuid.New().String())) + conn, err := net.DialUnix("unixgram", + &net.UnixAddr{Name: c.local, Net: "unixgram"}, + &net.UnixAddr{Name: address, Net: "unixgram"}, + ) + + if err != nil { + return nil, err + } + + filemode, err := strconv.ParseUint(c.SocketPerms, 8, 32) + if err != nil { + return nil, fmt.Errorf("parsing file mode %q failed: %w", c.SocketPerms, err) + } + + if err := os.Chmod(c.local, os.FileMode(filemode)); err != nil { + return nil, fmt.Errorf("changing file mode of %q failed: %w", c.local, err) + } + + group, err := user.LookupGroup(c.SocketGroup) + if err != nil { + return nil, fmt.Errorf("looking up group %q failed: %w", c.SocketGroup, err) + } + gid, err := strconv.Atoi(group.Gid) + if err != nil { + return nil, fmt.Errorf("parsing group ID %q failed: %w", group.Gid, err) + } + if err := os.Chown(c.local, os.Getuid(), gid); err != nil { + return nil, fmt.Errorf("changing group of %q failed: %w", c.local, err) + } + + return conn, nil +} + func (c *Chrony) Init() error { // Use the configured server, if none set, we try to guess it in Start() if c.Server != "" { @@ -48,7 +92,7 @@ func (c *Chrony) Init() error { return fmt.Errorf("parsing server address failed: %w", err) } switch u.Scheme { - case "unix": + case "unixgram": // Keep the server unmodified case "udp": // Check if we do have a port and add the default port if we don't @@ -79,6 +123,14 @@ func (c *Chrony) Init() error { } } + if c.SocketGroup == "" { + c.SocketGroup = "chrony" + } + + if c.SocketPerms == "" { + c.SocketPerms = "0660" + } + return nil } @@ -90,8 +142,8 @@ func (c *Chrony) Start(_ telegraf.Accumulator) error { return fmt.Errorf("parsing server address failed: %w", err) } switch u.Scheme { - case "unix": - conn, err := net.DialTimeout("unix", u.Path, time.Duration(c.Timeout)) + case "unixgram": + conn, err := c.dialUnix(u.Path) if err != nil { return fmt.Errorf("dialing %q failed: %w", c.Server, err) } @@ -107,7 +159,7 @@ func (c *Chrony) Start(_ telegraf.Accumulator) error { } } else { // If no server is given, reproduce chronyc's behavior - if conn, err := net.DialTimeout("unix", "/run/chrony/chronyd.sock", time.Duration(c.Timeout)); err == nil { + if conn, err := c.dialUnix("/run/chrony/chronyd.sock"); err == nil { c.Server = "unix:///run/chrony/chronyd.sock" c.conn = conn } else if conn, err := net.DialTimeout("udp", "127.0.0.1:323", time.Duration(c.Timeout)); err == nil { @@ -136,6 +188,11 @@ func (c *Chrony) Stop() { c.Log.Errorf("Closing connection to %q failed: %v", c.Server, err) } } + if c.local != "" { + if err := os.Remove(c.local); err != nil { + c.Log.Errorf("Removing temporary socket %q failed: %v", c.local, err) + } + } } func (c *Chrony) Gather(acc telegraf.Accumulator) error { diff --git a/plugins/inputs/chrony/sample.conf b/plugins/inputs/chrony/sample.conf index 11fb65fb1e8c2..d637473737b81 100644 --- a/plugins/inputs/chrony/sample.conf +++ b/plugins/inputs/chrony/sample.conf @@ -21,3 +21,9 @@ ## sources -- extended information about peers ## sourcestats -- statistics on peers # metrics = ["tracking"] + + ## Socket group & permissions + ## If the user requests collecting metrics via unix socket, then it is created + ## with the following group and permissions. + # socket_group = "chrony" + # socket_perms = "0660"