Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 68 additions & 20 deletions pkg/daemon/config_drift_monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"

ign2types "github.com/coreos/ignition/config/v2_2/types"
Expand All @@ -21,16 +22,47 @@ type configDriftErr struct {
error
}

func (c *configDriftErr) Unwrap() error {
return c.error
}

// Error type for file config drifts
type fileConfigDriftErr struct {
error
}

func (f *fileConfigDriftErr) Unwrap() error {
return f.error
}

// Error type for systemd unit config drifts
type unitConfigDriftErr struct {
error
}

func (u *unitConfigDriftErr) Unwrap() error {
return u.error
}

// Error type for SSH key config drifts
type sshConfigDriftErr struct {
error
}

func (s *sshConfigDriftErr) Unwrap() error {
return s.error
}

// Error type for unexpected SSH key config drift
type unexpectedSSHFileErr struct {
filename string
error
}

func (u *unexpectedSSHFileErr) Unwrap() error {
return u.error
}

type ConfigDriftMonitor interface {
Start(ConfigDriftMonitorOpts) error
Done() <-chan struct{}
Expand All @@ -43,9 +75,7 @@ type ConfigDriftMonitorOpts struct {
OnDrift func(error)
// The currently applied MachineConfig.
MachineConfig *mcfgv1.MachineConfig
// The Systemd dropin path location.
// Defaults to /etc/systemd/system
SystemdPath string
Paths
// Channel to report unknown errors
ErrChan chan<- error
}
Expand Down Expand Up @@ -153,10 +183,6 @@ func newConfigDriftWatcher(opts ConfigDriftMonitorOpts) (*configDriftWatcher, er
return nil, fmt.Errorf("no machine config provided")
}

if opts.SystemdPath == "" {
opts.SystemdPath = pathSystemd
}

c := &configDriftWatcher{
ConfigDriftMonitorOpts: opts,
stopCh: make(chan struct{}),
Expand Down Expand Up @@ -190,11 +216,16 @@ func (c *configDriftWatcher) initialize() error {
// This is useful when, for example, someone places a file in /etc using a
// MachineConfig (e.g., /etc/foo), but an unknown (to the MachineConfig) file
// in /etc (e.g., /etc/bar) is written to.
c.filePaths, err = getFilePathsFromMachineConfig(c.MachineConfig, c.SystemdPath)
c.filePaths, err = getFilePathsFromMachineConfig(c.MachineConfig, c.Paths)
if err != nil {
return fmt.Errorf("could not get file paths from machine config: %w", err)
}

// Add all of the SSH key path fragments to our watch list.
for _, path := range c.Paths.ExpectedSSHPathFragments() {
c.filePaths.Insert(path)
}

// fsnotify (presently) uses inotify instead of fanotify on Linux.
// See: https://github.com/fsnotify/fsnotify/issues/114
//
Expand All @@ -210,7 +241,7 @@ func (c *configDriftWatcher) initialize() error {
for _, path := range dirPaths {
glog.V(4).Infof("Watching dir: \"%s\"", path)
if err := c.watcher.Add(path); err != nil {
return fmt.Errorf("could not add fsnotify watcher to dir \"%s\": %w", path, err)
return fmt.Errorf("could not add fsnotify watcher to dir %q: %w", path, err)
}
}

Expand Down Expand Up @@ -274,39 +305,54 @@ func (c *configDriftWatcher) handleFileEvent(event fsnotify.Event) error {
return fmt.Errorf("unknown config drift error: %w", err)
}

func (c *configDriftWatcher) shouldValidateOnDiskState(event fsnotify.Event) bool {
// If the filename is not in the MachineConfig or it doesn't contain
// /home/core/.ssh, we ignore it.
//
// We do a fuzzy match of /home/core/.ssh because we want to cover any events
// in /home/core/.ssh and/or within /home/core/.ssh/authorized_keys.d. We
// can't watch for those events directly using fsnotify because:
//
// 1. fsnotify cannot recurse into a subdirectories.
// 2. fsnotify cannot alert on files that do not exist.
return c.filePaths.Has(event.Name) || strings.Contains(event.Name, c.Paths.SSHKeyRoot())
}

// Validates on disk state for potential config drift.
func (c *configDriftWatcher) checkMachineConfigForEvent(event fsnotify.Event) error {
// Ignore events for files not found in the MachineConfig.
if !c.filePaths.Has(event.Name) {
if !c.shouldValidateOnDiskState(event) {
return nil
}

if err := validateOnDiskState(c.MachineConfig, c.SystemdPath); err != nil {
if err := validateOnDiskState(c.MachineConfig, c.Paths); err != nil {
return &configDriftErr{err}
}

return nil
}

// Finds the paths for all files in a given MachineConfig.
func getFilePathsFromMachineConfig(mc *mcfgv1.MachineConfig, systemdPath string) (sets.String, error) {
func getFilePathsFromMachineConfig(mc *mcfgv1.MachineConfig, paths Paths) (sets.String, error) {
ignConfig, err := ctrlcommon.IgnParseWrapper(mc.Spec.Config.Raw)
if err != nil {
return sets.String{}, fmt.Errorf("could not get dirs from ignition config: %w", err)
}

switch typedConfig := ignConfig.(type) {
case ign3types.Config:
return getFilePathsFromIgn3Config(ignConfig.(ign3types.Config), systemdPath), nil
return getFilePathsFromIgn3Config(ignConfig.(ign3types.Config), paths), nil
case ign2types.Config:
return getFilePathsFromIgn2Config(ignConfig.(ign2types.Config), systemdPath), nil
return getFilePathsFromIgn2Config(ignConfig.(ign2types.Config), paths), nil
default:
return sets.String{}, fmt.Errorf("unexpected type for ignition config: %v", typedConfig)
}
}

// Extracts all unique directories from a given Ignition 3 config.
func getFilePathsFromIgn3Config(ignConfig ign3types.Config, systemdPath string) sets.String {
//
//nolint:dupl // This code must be duplicated because of different types.
func getFilePathsFromIgn3Config(ignConfig ign3types.Config, paths Paths) sets.String {
files := sets.NewString()

// Get all the file paths from the ignition config
Expand All @@ -318,13 +364,13 @@ func getFilePathsFromIgn3Config(ignConfig ign3types.Config, systemdPath string)

// Get all the file paths for systemd dropins from the ignition config
for _, unit := range ignConfig.Systemd.Units {
unitPath := getIgn3SystemdUnitPath(systemdPath, unit)
unitPath := paths.SystemdUnitPath(unit.Name)
if _, err := os.Stat(unitPath); err == nil && !os.IsNotExist(err) {
files.Insert(unitPath)
}

for _, dropin := range unit.Dropins {
dropinPath := getIgn3SystemdDropinPath(systemdPath, unit, dropin)
dropinPath := paths.SystemdDropinPath(unit.Name, dropin.Name)
if _, err := os.Stat(dropinPath); err == nil && !os.IsNotExist(err) {
files.Insert(dropinPath)
}
Expand All @@ -335,7 +381,9 @@ func getFilePathsFromIgn3Config(ignConfig ign3types.Config, systemdPath string)
}

// Extracts all unique directories from a given Ignition 2 config.
func getFilePathsFromIgn2Config(ignConfig ign2types.Config, systemdPath string) sets.String {
//
//nolint:dupl // This code must be duplicated because of different types.
func getFilePathsFromIgn2Config(ignConfig ign2types.Config, paths Paths) sets.String {
files := sets.NewString()

// Get all the file paths from the ignition config
Expand All @@ -347,13 +395,13 @@ func getFilePathsFromIgn2Config(ignConfig ign2types.Config, systemdPath string)

// Get all the file paths for systemd dropins from the ignition config
for _, unit := range ignConfig.Systemd.Units {
unitPath := getIgn2SystemdUnitPath(systemdPath, unit)
unitPath := paths.SystemdUnitPath(unit.Name)
if _, err := os.Stat(unitPath); err == nil && !os.IsNotExist(err) {
files.Insert(unitPath)
}

for _, dropin := range unit.Dropins {
dropinPath := getIgn2SystemdDropinPath(systemdPath, unit, dropin)
dropinPath := paths.SystemdDropinPath(unit.Name, dropin.Name)
if _, err := os.Stat(dropinPath); err == nil && !os.IsNotExist(err) {
files.Insert(dropinPath)
}
Expand Down
Loading