From 2c4e7f50485dbd0838623602870a99129c540329 Mon Sep 17 00:00:00 2001 From: Christian Zunker <827818+czunker@users.noreply.github.com> Date: Tue, 31 Oct 2023 12:06:29 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Regularly=20check=20in=20with=20?= =?UTF-8?q?upstream=20(#913)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🐛 Regularly check in with upstream Fixes #894 Signed-off-by: Christian Zunker --- apps/cnspec/cmd/backgroundjob/checkin.go | 148 ++++++++++++++++++++ apps/cnspec/cmd/backgroundjob/healthping.go | 66 --------- apps/cnspec/cmd/serve.go | 6 +- 3 files changed, 151 insertions(+), 69 deletions(-) create mode 100644 apps/cnspec/cmd/backgroundjob/checkin.go delete mode 100644 apps/cnspec/cmd/backgroundjob/healthping.go diff --git a/apps/cnspec/cmd/backgroundjob/checkin.go b/apps/cnspec/cmd/backgroundjob/checkin.go new file mode 100644 index 00000000..d81c14b0 --- /dev/null +++ b/apps/cnspec/cmd/backgroundjob/checkin.go @@ -0,0 +1,148 @@ +// Copyright (c) Mondoo, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package backgroundjob + +import ( + "context" + "math/rand" + "net/http" + "sync" + "time" + + "github.com/pkg/errors" + "github.com/rs/zerolog/log" + "go.mondoo.com/cnquery/v9" + "go.mondoo.com/cnquery/v9/cli/sysinfo" + "go.mondoo.com/cnquery/v9/providers-sdk/v1/upstream" + "go.mondoo.com/ranger-rpc" + "go.mondoo.com/ranger-rpc/plugins/scope" +) + +type checkinPinger struct { + ctx context.Context + interval time.Duration + quit chan struct{} + wg sync.WaitGroup + endpoint string + httpClient *http.Client + mrn string + creds *upstream.ServiceAccountCredentials +} + +func NewCheckinPinger(ctx context.Context, httpClient *http.Client, endpoint string, config *upstream.UpstreamConfig, interval time.Duration) *checkinPinger { + return &checkinPinger{ + ctx: ctx, + interval: interval, + quit: make(chan struct{}), + endpoint: endpoint, + httpClient: httpClient, + mrn: config.Creds.Mrn, + creds: config.Creds, + } +} + +func (c *checkinPinger) Start() { + // determine information about the client + sysInfo, err := sysinfo.GatherSystemInfo() + if err != nil { + log.Error().Err(err).Msg("could not gather client information") + return + } + c.wg.Add(1) + runCheckIn := func() { + err := c.checkIn(sysInfo) + if err != nil { + log.Info().Err(err).Msg("could not perform check-in") + } + } + + // run check-in once on startup + runCheckIn() + + jitter := time.Duration(rand.Int63n(int64(c.interval))) + ticker := time.NewTicker(c.interval + jitter) + go func() { + defer c.wg.Done() + for { + select { + case <-ticker.C: + runCheckIn() + case <-c.quit: + ticker.Stop() + return + } + } + }() +} + +func (c *checkinPinger) Stop() { + close(c.quit) + c.wg.Wait() +} + +func (c *checkinPinger) checkIn(sysInfo *sysinfo.SystemInfo) error { + // gather service account + plugins := []ranger.ClientPlugin{} + plugins = append(plugins, sysInfoHeader(sysInfo, cnquery.DefaultFeatures)) + + credentials := c.creds + if credentials != nil && len(credentials.Mrn) > 0 { + certAuth, err := upstream.NewServiceAccountRangerPlugin(credentials) + if err != nil { + return errors.Wrap(err, "invalid credentials") + } + plugins = append(plugins, certAuth) + } else { + return errors.New("no credentials configured") + } + + client, err := upstream.NewAgentManagerClient(c.endpoint, c.httpClient, plugins...) + if err != nil { + return errors.Wrap(err, "could not connect to mondoo platform") + } + + _, err = client.HealthCheck(context.Background(), &upstream.AgentInfo{ + Mrn: c.mrn, + Version: sysInfo.Version, + Build: sysInfo.Build, + PlatformName: sysInfo.Platform.Name, + PlatformRelease: sysInfo.Platform.Version, + PlatformArch: sysInfo.Platform.Arch, + PlatformIp: sysInfo.IP, + PlatformHostname: sysInfo.Hostname, + Labels: nil, + PlatformId: sysInfo.PlatformId, + }) + + if err != nil { + return errors.Wrap(err, "failed to check in upstream") + } + + return nil +} + +func sysInfoHeader(sysInfo *sysinfo.SystemInfo, features cnquery.Features) ranger.ClientPlugin { + const ( + HttpHeaderUserAgent = "User-Agent" + HttpHeaderClientFeatures = "Mondoo-Features" + HttpHeaderPlatformID = "Mondoo-PlatformID" + ) + + h := http.Header{} + info := map[string]string{ + "cnquery": cnquery.Version, + "build": cnquery.Build, + } + if sysInfo != nil { + info["PN"] = sysInfo.Platform.Name + info["PR"] = sysInfo.Platform.Version + info["PA"] = sysInfo.Platform.Arch + info["IP"] = sysInfo.IP + info["HN"] = sysInfo.Hostname + h.Set(HttpHeaderPlatformID, sysInfo.PlatformId) + } + h.Set(HttpHeaderUserAgent, scope.XInfoHeader(info)) + h.Set(HttpHeaderClientFeatures, features.Encode()) + return scope.NewCustomHeaderRangerPlugin(h) +} diff --git a/apps/cnspec/cmd/backgroundjob/healthping.go b/apps/cnspec/cmd/backgroundjob/healthping.go deleted file mode 100644 index f09bfc24..00000000 --- a/apps/cnspec/cmd/backgroundjob/healthping.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Mondoo, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package backgroundjob - -import ( - "context" - "net/http" - "sync" - "time" - - "github.com/rs/zerolog/log" - "go.mondoo.com/cnquery/v9/providers-sdk/v1/upstream/health" -) - -type healthPinger struct { - ctx context.Context - interval time.Duration - quit chan struct{} - wg sync.WaitGroup - endpoint string - httpClient *http.Client -} - -func NewHealthPinger(ctx context.Context, httpClient *http.Client, endpoint string, interval time.Duration) *healthPinger { - return &healthPinger{ - ctx: ctx, - interval: interval, - quit: make(chan struct{}), - endpoint: endpoint, - httpClient: httpClient, - } -} - -func (h *healthPinger) Start() { - h.wg.Add(1) - runHealthCheck := func() { - _, err := health.CheckApiHealth(h.httpClient, 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() -} diff --git a/apps/cnspec/cmd/serve.go b/apps/cnspec/cmd/serve.go index f2c048d2..4c0ac950 100644 --- a/apps/cnspec/cmd/serve.go +++ b/apps/cnspec/cmd/serve.go @@ -80,9 +80,9 @@ var serveCmd = &cobra.Command{ return cli_errors.NewCommandError(errors.Wrap(err, "could not initialize upstream client"), 1) } - hc := backgroundjob.NewHealthPinger(ctx, client.HttpClient, client.ApiEndpoint, 5*time.Minute) - hc.Start() - defer hc.Stop() + checkin := backgroundjob.NewCheckinPinger(ctx, client.HttpClient, client.ApiEndpoint, conf.runtime.UpstreamConfig, 2*time.Hour) + checkin.Start() + defer checkin.Stop() } bj, err := backgroundjob.New()