139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
//go:build linux
|
|
|
|
//go:generate ../../../tools/readme_config_includer/generator
|
|
package systemd
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/coreos/go-systemd/v22/dbus"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/plugins/secretstores"
|
|
)
|
|
|
|
const systemdMinimumVersion = 250
|
|
|
|
// Required to be a variable to mock the version in tests
|
|
var getSystemdVersion = getSystemdMajorVersion
|
|
|
|
//go:embed sample.conf
|
|
var sampleConfig string
|
|
|
|
type Systemd struct {
|
|
Path string `toml:"path"`
|
|
Prefix string `toml:"prefix"`
|
|
Log telegraf.Logger `toml:"-"`
|
|
}
|
|
|
|
func (*Systemd) SampleConfig() string {
|
|
return sampleConfig
|
|
}
|
|
|
|
// Init initializes all internals of the secret-store
|
|
func (s *Systemd) Init() error {
|
|
version, err := getSystemdVersion()
|
|
if err != nil {
|
|
return fmt.Errorf("unable to detect systemd version: %w", err)
|
|
}
|
|
s.Log.Debugf("Found systemd version %d...", version)
|
|
if version < systemdMinimumVersion {
|
|
return fmt.Errorf("systemd version %d below minimum version %d", version, systemdMinimumVersion)
|
|
}
|
|
|
|
// By default the credentials directory is passed in by systemd
|
|
// via the "CREDENTIALS_DIRECTORY" environment variable.
|
|
defaultPath := os.Getenv("CREDENTIALS_DIRECTORY")
|
|
if defaultPath == "" {
|
|
s.Log.Warn("CREDENTIALS_DIRECTORY environment variable undefined. Make sure credentials are setup correctly!")
|
|
if s.Path == "" {
|
|
return errors.New("'path' required without CREDENTIALS_DIRECTORY")
|
|
}
|
|
}
|
|
|
|
// Use default path if no explicit was specified. This should be the common case.
|
|
if s.Path == "" {
|
|
s.Path = defaultPath
|
|
}
|
|
s.Path, err = filepath.Abs(s.Path)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot determine absolute path of %q: %w", s.Path, err)
|
|
}
|
|
|
|
// Check if we can access the target directory
|
|
if _, err := os.Stat(s.Path); err != nil {
|
|
return fmt.Errorf("accessing credentials directory %q failed: %w", s.Path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Systemd) Get(key string) ([]byte, error) {
|
|
secretFile, err := filepath.Abs(filepath.Join(s.Path, s.Prefix+key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if filepath.Dir(secretFile) != s.Path {
|
|
return nil, fmt.Errorf("invalid directory detected for key %q", key)
|
|
}
|
|
value, err := os.ReadFile(secretFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read the secret's value: %w", err)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func (s *Systemd) List() ([]string, error) {
|
|
secretFiles, err := os.ReadDir(s.Path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read files: %w", err)
|
|
}
|
|
secrets := make([]string, 0, len(secretFiles))
|
|
for _, entry := range secretFiles {
|
|
key := strings.TrimPrefix(entry.Name(), s.Prefix)
|
|
secrets = append(secrets, key)
|
|
}
|
|
return secrets, nil
|
|
}
|
|
|
|
func (*Systemd) Set(_, _ string) error {
|
|
return errors.New("secret-store does not support creating secrets")
|
|
}
|
|
|
|
// GetResolver returns a function to resolve the given key.
|
|
func (s *Systemd) GetResolver(key string) (telegraf.ResolveFunc, error) {
|
|
resolver := func() ([]byte, bool, error) {
|
|
s, err := s.Get(key)
|
|
return s, false, err
|
|
}
|
|
return resolver, nil
|
|
}
|
|
|
|
func getSystemdMajorVersion() (int, error) {
|
|
ctx := context.Background()
|
|
conn, err := dbus.NewWithContext(ctx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer conn.Close()
|
|
|
|
fullVersion, err := conn.GetManagerProperty("Version")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
fullVersion = strings.Trim(fullVersion, "\"")
|
|
return strconv.Atoi(strings.SplitN(fullVersion, ".", 2)[0])
|
|
}
|
|
|
|
// Register the secret-store on load.
|
|
func init() {
|
|
secretstores.Add("systemd", func(_ string) telegraf.SecretStore {
|
|
return &Systemd{Prefix: "telegraf."}
|
|
})
|
|
}
|