go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/apps/cnquery/cmd/status.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package cmd
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/rs/zerolog/log"
    14  	"github.com/spf13/cobra"
    15  	"github.com/spf13/viper"
    16  	"go.mondoo.com/cnquery"
    17  	"go.mondoo.com/cnquery/cli/config"
    18  	"go.mondoo.com/cnquery/cli/sysinfo"
    19  	"go.mondoo.com/cnquery/cli/theme"
    20  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    21  	"go.mondoo.com/cnquery/providers-sdk/v1/upstream"
    22  	"go.mondoo.com/cnquery/providers-sdk/v1/upstream/health"
    23  	"go.mondoo.com/ranger-rpc"
    24  	"sigs.k8s.io/yaml"
    25  )
    26  
    27  func init() {
    28  	StatusCmd.Flags().StringP("output", "o", "", "Set output format. Accepts json or yaml.")
    29  	rootCmd.AddCommand(StatusCmd)
    30  }
    31  
    32  // StatusCmd represents the version command
    33  var StatusCmd = &cobra.Command{
    34  	Use:   "status",
    35  	Short: "Verify access to Mondoo Platform.",
    36  	Long: `
    37  Status sends a ping to Mondoo Platform to verify the credentials.
    38  	`,
    39  	PreRun: func(cmd *cobra.Command, args []string) {
    40  		viper.BindPFlag("output", cmd.Flags().Lookup("output"))
    41  	},
    42  	Run: func(cmd *cobra.Command, args []string) {
    43  		opts, optsErr := config.Read()
    44  		if optsErr != nil {
    45  			log.Fatal().Err(optsErr).Msg("could not load configuration")
    46  		}
    47  
    48  		config.DisplayUsedConfig()
    49  
    50  		s := Status{
    51  			Client: ClientStatus{
    52  				Timestamp: time.Now().Format(time.RFC3339),
    53  				Version:   cnquery.GetVersion(),
    54  				Build:     cnquery.GetBuild(),
    55  			},
    56  		}
    57  
    58  		httpClient, err := opts.GetHttpClient()
    59  		if err != nil {
    60  			log.Fatal().Err(err).Msg("failed to set up Mondoo API client")
    61  		}
    62  
    63  		sysInfo, err := sysinfo.GatherSystemInfo()
    64  		if err == nil {
    65  			s.Client.Platform = sysInfo.Platform
    66  			s.Client.Hostname = sysInfo.Hostname
    67  			s.Client.IP = sysInfo.IP
    68  		}
    69  
    70  		// check server health and clock skew
    71  		upstreamStatus, err := health.CheckApiHealth(httpClient, opts.UpstreamApiEndpoint())
    72  		if err != nil {
    73  			log.Error().Err(err).Msg("could not check upstream health")
    74  		}
    75  		s.Upstream = upstreamStatus
    76  
    77  		// check valid agent authentication
    78  		plugins := []ranger.ClientPlugin{}
    79  
    80  		// try to load config into credentials struct
    81  		credentials := opts.GetServiceCredential()
    82  		if credentials != nil && len(credentials.Mrn) > 0 {
    83  			s.Client.ParentMrn = credentials.ParentMrn
    84  			s.Client.Registered = true
    85  			s.Client.ServiceAccount = credentials.Mrn
    86  			s.Client.Mrn = opts.AgentMrn
    87  			if s.Client.Mrn == "" {
    88  				s.Client.Mrn = "no managed client"
    89  			}
    90  
    91  			certAuth, err := upstream.NewServiceAccountRangerPlugin(credentials)
    92  			if err != nil {
    93  				log.Fatal().Err(err).Msg("invalid credentials")
    94  			}
    95  			plugins = append(plugins, certAuth)
    96  
    97  			// try to ping the server
    98  			client, err := upstream.NewAgentManagerClient(s.Upstream.API.Endpoint, httpClient, plugins...)
    99  			if err == nil {
   100  				_, err = client.PingPong(context.Background(), &upstream.Ping{})
   101  				if err != nil {
   102  					s.Client.PingPongError = err
   103  				}
   104  			} else {
   105  				s.Client.PingPongError = err
   106  			}
   107  		}
   108  
   109  		switch strings.ToLower(viper.GetString("output")) {
   110  		case "yaml":
   111  			s.RenderYaml()
   112  		case "json":
   113  			s.RenderJson()
   114  		default:
   115  			s.RenderCliStatus()
   116  		}
   117  
   118  		if !s.Client.Registered || s.Client.PingPongError != nil {
   119  			os.Exit(1)
   120  		}
   121  	},
   122  }
   123  
   124  type Status struct {
   125  	Client   ClientStatus  `json:"client"`
   126  	Upstream health.Status `json:"upstream"`
   127  }
   128  
   129  type ClientStatus struct {
   130  	Timestamp      string              `json:"timestamp,omitempty"`
   131  	Mrn            string              `json:"mrn,omitempty"`
   132  	ServiceAccount string              `json:"service_account,omitempty"`
   133  	ParentMrn      string              `json:"parentMrn,omitempty"`
   134  	Version        string              `json:"version,omitempty"`
   135  	Build          string              `json:"build,omitempty"`
   136  	Labels         map[string]string   `json:"labels,omitempty"`
   137  	Platform       *inventory.Platform `json:"platform,omitempty"`
   138  	IP             string              `json:"ip,omitempty"`
   139  	Hostname       string              `json:"hostname,omitempty"`
   140  	Registered     bool                `json:"registered,omitempty"`
   141  	PingPongError  error               `json:"pingPongError,omitempty"`
   142  }
   143  
   144  func (s Status) RenderCliStatus() {
   145  	if s.Client.Platform != nil {
   146  		agent := s.Client
   147  		log.Info().Msg("Platform:\t" + agent.Platform.Name)
   148  		log.Info().Msg("Version:\t" + agent.Platform.Version)
   149  		log.Info().Msg("Hostname:\t" + agent.Hostname)
   150  		log.Info().Msg("IP:\t\t" + agent.IP)
   151  	} else {
   152  		log.Warn().Msg("could not determine client platform information")
   153  	}
   154  
   155  	log.Info().Msg("Time:\t\t" + s.Client.Timestamp)
   156  	log.Info().Msg("Version:\t" + cnquery.GetVersion() + " (API Version: " + cnquery.APIVersion() + ")")
   157  
   158  	log.Info().Msg("API ConnectionConfig:\t" + s.Upstream.API.Endpoint)
   159  	log.Info().Msg("API Status:\t" + s.Upstream.API.Status)
   160  	log.Info().Msg("API Time:\t" + s.Upstream.API.Timestamp)
   161  	log.Info().Msg("API Version:\t" + s.Upstream.API.Version)
   162  
   163  	if s.Upstream.API.Version != cnquery.APIVersion() {
   164  		log.Warn().Msg("API versions do not match, please update the client")
   165  	}
   166  
   167  	if len(s.Upstream.Features) > 0 {
   168  		log.Info().Msg("Features:\t" + strings.Join(s.Upstream.Features, ","))
   169  	}
   170  
   171  	if s.Client.ParentMrn == "" {
   172  		log.Info().Msg("Owner:\t" + s.Client.ParentMrn)
   173  	}
   174  
   175  	if s.Client.Registered {
   176  		log.Info().Msg("Client:\t" + s.Client.Mrn)
   177  		log.Info().Msg("Service Account:\t" + s.Client.ServiceAccount)
   178  		log.Info().Msg(theme.DefaultTheme.Success("client is registered"))
   179  	} else {
   180  		log.Error().Msg("client is not registered")
   181  	}
   182  
   183  	if s.Client.Registered && s.Client.PingPongError == nil {
   184  		log.Info().Msg(theme.DefaultTheme.Success("client authenticated successfully"))
   185  	} else if s.Client.PingPongError == nil {
   186  		log.Error().Err(s.Client.PingPongError).
   187  			Msgf("The Mondoo Platform credentials provided at %s didn't successfully authenticate with Mondoo Platform. Please re-authenticate with Mondoo Platform. To learn how, read https://mondoo.com/docs/cnspec/cnspec-adv-install/registration.",
   188  				viper.ConfigFileUsed())
   189  	}
   190  
   191  	for i := range s.Upstream.Warnings {
   192  		log.Warn().Msg(s.Upstream.Warnings[i])
   193  	}
   194  }
   195  
   196  func (s Status) RenderJson() {
   197  	output, err := json.Marshal(s)
   198  	if err != nil {
   199  		log.Error().Err(err).Msg("could not generate json")
   200  	}
   201  	os.Stdout.Write(output)
   202  }
   203  
   204  func (s Status) RenderYaml() {
   205  	output, err := yaml.Marshal(s)
   206  	if err != nil {
   207  		log.Error().Err(err).Msg("could not generate yaml")
   208  	}
   209  	os.Stdout.Write(output)
   210  }