-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This change depends on mondoohq/cnquery#786 and mondoohq/cnquery#787 **Problem** As a user, I want to run cnspec continuously and report the results into Mondoo Platform. **Solution** With this new change, `cnspec` has a new `serve` subcommand that runs scans in the background. The package also comes with Linux service configuration for SysV and Systemd. ```bash cnspec serve → loaded configuration from /Users/chris/.config/mondoo/mondoo.yml using source default → start cnspec client=//agents.api.mondoo.app/spaces/saha-saha-123/agents/1zDY7auR20SgrFfiGUT5qZWx6mE service_account=//agents.api.mondoo.app/spaces/saha-saha-123/serviceaccounts/1zDY7cJ7bA84JxxNBWDxBdui2xE space=//captain.api.mondoo.app/spaces/saha-saha-123 version=v7.12.1 → using service account credentials → scan local operating system → start cnspec background service → scan interval is 60 minute(s) → discover related assets for 1 asset(s) → resolved assets resolved-assets=1 → connecting to asset spacerocket.fritz.box (baremetal) → scan for asset spacerocket.fritz.box (baremetal) completed ^C→ stop service gracefully → stop worker → bye bye space cowboy ``` If you want to enable `cnspec` as a service on Linux run the following commands: ``` systemctl enable cnspec.service systemctl start cnspec.service systemctl daemon-reload ```
- Loading branch information
1 parent
e110510
commit 6ea2f69
Showing
20 changed files
with
673 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package backgroundjob | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/spf13/viper" | ||
) | ||
|
||
const ( | ||
// Service Name | ||
SvcName = "cnspec" // NOTE: this name needs to align with the service name in packages | ||
) | ||
|
||
type JobRunner func() error | ||
|
||
func New() (*BackgroundScanner, error) { | ||
return &BackgroundScanner{}, nil | ||
} | ||
|
||
type BackgroundScanner struct{} | ||
|
||
func (bs *BackgroundScanner) Run(runScanFn JobRunner) error { | ||
Serve(time.Duration(viper.GetInt64("timer"))*time.Minute, runScanFn) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package backgroundjob | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"time" | ||
|
||
"github.com/rs/zerolog/log" | ||
"go.mondoo.com/cnquery/upstream/health" | ||
) | ||
|
||
type healthPinger struct { | ||
ctx context.Context | ||
interval time.Duration | ||
quit chan struct{} | ||
wg sync.WaitGroup | ||
endpoint string | ||
} | ||
|
||
func NewHealthPinger(ctx context.Context, endpoint string, interval time.Duration) *healthPinger { | ||
return &healthPinger{ | ||
ctx: ctx, | ||
interval: interval, | ||
quit: make(chan struct{}), | ||
endpoint: endpoint, | ||
} | ||
} | ||
|
||
func (h *healthPinger) Start() { | ||
h.wg.Add(1) | ||
runHealthCheck := func() { | ||
_, err := health.CheckApiHealth(h.endpoint) | ||
if err != nil { | ||
log.Info().Err(err).Msg("could not perform health check") | ||
} | ||
} | ||
|
||
// run health check once on startup | ||
runHealthCheck() | ||
|
||
// TODO we may want to add jitter and backoff | ||
healthTicker := time.NewTicker(h.interval) | ||
go func() { | ||
defer h.wg.Done() | ||
for { | ||
select { | ||
case <-healthTicker.C: | ||
runHealthCheck() | ||
case <-h.quit: | ||
healthTicker.Stop() | ||
return | ||
} | ||
} | ||
}() | ||
} | ||
|
||
func (h *healthPinger) Stop() { | ||
close(h.quit) | ||
h.wg.Wait() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
//go:build linux || darwin || netbsd || openbsd || freebsd | ||
// +build linux darwin netbsd openbsd freebsd | ||
|
||
package backgroundjob | ||
|
||
import ( | ||
"os" | ||
"os/signal" | ||
"sync" | ||
"syscall" | ||
"time" | ||
|
||
"github.com/rs/zerolog/log" | ||
) | ||
|
||
func Serve(timer time.Duration, handler JobRunner) { | ||
log.Info().Msg("start cnspec background service") | ||
log.Info().Msgf("scan interval is %d minute(s)", int(timer.Minutes())) | ||
|
||
quitChannel := make(chan os.Signal) | ||
signal.Notify(quitChannel, syscall.SIGINT, syscall.SIGTERM) | ||
|
||
shutdownChannel := make(chan struct{}) | ||
waitGroup := &sync.WaitGroup{} | ||
|
||
initTick := time.Tick(1 * time.Second) | ||
defaultTick := time.Tick(timer) | ||
tick := initTick | ||
waitGroup.Add(1) | ||
|
||
go func(shutdownChannel chan struct{}, wg *sync.WaitGroup) { | ||
defer wg.Done() | ||
for { | ||
// Give shutdown priority | ||
select { | ||
case <-shutdownChannel: | ||
log.Info().Msg("stop worker") | ||
return | ||
default: | ||
} | ||
|
||
select { | ||
case <-tick: | ||
if tick == initTick { | ||
tick = defaultTick | ||
} | ||
err := handler() | ||
if err != nil { | ||
log.Error().Err(err).Send() | ||
} | ||
case <-shutdownChannel: | ||
log.Info().Msg("stop worker") | ||
return | ||
} | ||
} | ||
}(shutdownChannel, waitGroup) | ||
|
||
<-quitChannel // received SIGINT or SIGTERM | ||
close(shutdownChannel) | ||
|
||
log.Info().Msg("stop service gracefully") | ||
|
||
waitGroup.Wait() // wait for all goroutines | ||
log.Info().Msg("bye bye space cowboy") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
//go:build windows | ||
// +build windows | ||
|
||
package backgroundjob | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/rs/zerolog/log" | ||
"go.mondoo.com/cnquery/logger/eventlog" | ||
"golang.org/x/sys/windows/svc" | ||
"golang.org/x/sys/windows/svc/debug" | ||
) | ||
|
||
func Serve(timer time.Duration, handler JobRunner) { | ||
isIntSess, err := svc.IsAnInteractiveSession() | ||
if err != nil { | ||
log.Fatal().Err(err).Msg("failed to determine if we are running in an interactive session") | ||
} | ||
// if it is an service ... | ||
if !isIntSess { | ||
// set windows eventlogger | ||
w, err := eventlog.NewEventlogWriter(SvcName) | ||
if err != nil { | ||
log.Fatal().Err(err).Msg("failed to connect to windows event log") | ||
} | ||
log.Logger = log.Output(w) | ||
|
||
// run service | ||
runService(false, timer, handler) | ||
return | ||
} | ||
runService(true, timer, handler) | ||
} | ||
|
||
type windowsService struct { | ||
Timer time.Duration | ||
Handler JobRunner | ||
} | ||
|
||
// NOTE: we do not support svc.AcceptPauseAndContinue yet, we may reconsider this later | ||
func (m *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { | ||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | ||
changes <- svc.Status{State: svc.StartPending} | ||
initTick := time.Tick(1 * time.Second) | ||
defaulttick := time.Tick(m.Timer) | ||
tick := initTick | ||
log.Info().Msg("schedule background scan") | ||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} | ||
|
||
runChan := make(chan struct{}) | ||
go func() { | ||
// This goroutine doesn't stop cleanly. | ||
// This isn't great, but we cannot block the service event loop. | ||
// It would be good to make sure this shuts down cleanly, but | ||
// that's not possible right now and requires wiring through | ||
// context throughout the execution. | ||
for range runChan { | ||
log.Info().Msg("starting background scan") | ||
err := m.Handler() | ||
if err != nil { | ||
log.Error().Err(err).Send() | ||
} else { | ||
log.Info().Msg("scan completed") | ||
} | ||
} | ||
}() | ||
loop: | ||
for { | ||
select { | ||
case <-tick: | ||
if tick == initTick { | ||
tick = defaulttick | ||
} | ||
select { | ||
case runChan <- struct{}{}: | ||
default: | ||
log.Error().Msg("scan not started. may be stuck") | ||
} | ||
case c := <-r: | ||
switch c.Cmd { | ||
case svc.Interrogate: | ||
changes <- c.CurrentStatus | ||
case svc.Stop, svc.Shutdown: | ||
log.Info().Msg("stopping cnspec service") | ||
break loop | ||
default: | ||
log.Error().Msgf("unexpected control request #%d", c) | ||
} | ||
} | ||
} | ||
close(runChan) | ||
changes <- svc.Status{State: svc.StopPending} | ||
return | ||
} | ||
|
||
func runService(isDebug bool, timer time.Duration, handler JobRunner) { | ||
var err error | ||
|
||
log.Info().Msgf("starting %s service", SvcName) | ||
run := svc.Run | ||
if isDebug { | ||
run = debug.Run | ||
} | ||
err = run(SvcName, &windowsService{ | ||
Handler: handler, | ||
Timer: timer, | ||
}) | ||
if err != nil { | ||
log.Info().Msgf("%s service failed: %v", SvcName, err) | ||
return | ||
} | ||
log.Info().Msgf("%s service stopped", SvcName) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.