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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package cmd
     5  
     6  import (
     7  	"context"
     8  	"os"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/rs/zerolog/log"
    13  	"github.com/spf13/cobra"
    14  	"github.com/spf13/viper"
    15  	"go.mondoo.com/cnquery"
    16  	"go.mondoo.com/cnquery/cli/config"
    17  	"go.mondoo.com/cnquery/cli/sysinfo"
    18  	"go.mondoo.com/cnquery/providers-sdk/v1/upstream"
    19  	"go.mondoo.com/ranger-rpc"
    20  	"go.mondoo.com/ranger-rpc/plugins/authentication/statictoken"
    21  )
    22  
    23  func init() {
    24  	rootCmd.AddCommand(LoginCmd)
    25  	LoginCmd.Flags().StringP("token", "t", "", "Set a client registration token.")
    26  	LoginCmd.Flags().StringToString("annotation", nil, "Set the client annotations.")
    27  	LoginCmd.Flags().String("name", "", "Set asset name.")
    28  	LoginCmd.Flags().String("api-endpoint", "", "Set the Mondoo API endpoint.")
    29  }
    30  
    31  var LoginCmd = &cobra.Command{
    32  	Use:     "login",
    33  	Aliases: []string{"register"},
    34  	Short:   "Register with Mondoo Platform.",
    35  	Long: `
    36  Log in to Mondoo Platform using a registration token. To pass in the token, use 
    37  the '--token' flag.
    38  
    39  You can generate a new registration token on the Mondoo Dashboard. Go to
    40  https://console.mondoo.com -> Space -> Settings -> Registration Token. Copy the token and pass it in 
    41  using the '--token' argument.
    42  
    43  You remain logged in until you explicitly log out using the 'logout' subcommand.
    44  	`,
    45  	PreRun: func(cmd *cobra.Command, args []string) {
    46  		viper.BindPFlag("api_endpoint", cmd.Flags().Lookup("api-endpoint"))
    47  		viper.BindPFlag("name", cmd.Flags().Lookup("name"))
    48  	},
    49  	Run: func(cmd *cobra.Command, args []string) {
    50  		token, _ := cmd.Flags().GetString("token")
    51  		annotations, _ := cmd.Flags().GetStringToString("annotation")
    52  		register(token, annotations)
    53  	},
    54  }
    55  
    56  func register(token string, annotations map[string]string) {
    57  	var err error
    58  	var credential *upstream.ServiceAccountCredentials
    59  
    60  	// determine information about the client
    61  	sysInfo, err := sysinfo.GatherSystemInfo()
    62  	if err != nil {
    63  		log.Fatal().Err(err).Msg("could not gather client information")
    64  	}
    65  	defaultPlugins := defaultRangerPlugins(sysInfo, cnquery.DefaultFeatures)
    66  
    67  	apiEndpoint := viper.GetString("api_endpoint")
    68  	token = strings.TrimSpace(token)
    69  
    70  	// NOTE: login is special because we do not have a config yet
    71  	proxy, err := config.GetAPIProxy()
    72  	if err != nil {
    73  		log.Fatal().Err(err).Msg("could not parse proxy URL")
    74  	}
    75  	httpClient := ranger.NewHttpClient(ranger.WithProxy(proxy))
    76  
    77  	// we handle three cases here:
    78  	// 1. user has a token provided
    79  	// 2. user has no token provided, but has a service account file is already there
    80  	//
    81  	if token != "" {
    82  		// print token details
    83  		claims, err := upstream.ExtractTokenClaims(token)
    84  		if err != nil {
    85  			log.Warn().Err(err).Msg("could not read the token")
    86  		} else {
    87  			if len(claims.Description) > 0 {
    88  				log.Info().Msg("token description: " + claims.Description)
    89  			}
    90  			if claims.IsExpired() {
    91  				log.Warn().Msg("token is expired")
    92  			} else {
    93  				log.Info().Msg("token will expire at " + claims.Claims.Expiry.Time().Format(time.RFC1123))
    94  			}
    95  
    96  			if apiEndpoint == "" {
    97  				apiEndpoint = claims.ApiEndpoint
    98  			}
    99  		}
   100  
   101  		// gather service account
   102  		plugins := []ranger.ClientPlugin{}
   103  		plugins = append(plugins, defaultPlugins...)
   104  		plugins = append(plugins, statictoken.NewRangerPlugin(token))
   105  
   106  		client, err := upstream.NewAgentManagerClient(apiEndpoint, httpClient, plugins...)
   107  		if err != nil {
   108  			log.Fatal().Err(err).Msg("could not connect to mondoo platform")
   109  		}
   110  
   111  		name := viper.GetString("name")
   112  		if name == "" {
   113  			name = sysInfo.Hostname
   114  		}
   115  
   116  		confirmation, err := client.RegisterAgent(context.Background(), &upstream.AgentRegistrationRequest{
   117  			Token: token,
   118  			Name:  name,
   119  			AgentInfo: &upstream.AgentInfo{
   120  				Mrn:              "",
   121  				Version:          sysInfo.Version,
   122  				Build:            sysInfo.Build,
   123  				PlatformName:     sysInfo.Platform.Name,
   124  				PlatformRelease:  sysInfo.Platform.Version,
   125  				PlatformArch:     sysInfo.Platform.Arch,
   126  				PlatformIp:       sysInfo.IP,
   127  				PlatformHostname: sysInfo.Hostname,
   128  				Labels:           nil,
   129  				PlatformId:       sysInfo.PlatformId,
   130  			},
   131  		})
   132  		if err != nil {
   133  			log.Fatal().Err(err).Msg("failed to log in client")
   134  		}
   135  
   136  		log.Debug().Msg("store configuration")
   137  		// overwrite force, otherwise it will be stored
   138  		viper.Set("force", false)
   139  
   140  		// update configuration file, api-endpoint is set automatically
   141  		viper.Set("agent_mrn", confirmation.AgentMrn)
   142  		viper.Set("api_endpoint", confirmation.Credential.ApiEndpoint)
   143  		viper.Set("space_mrn", confirmation.Credential.GetParentMrn())
   144  		viper.Set("mrn", confirmation.Credential.Mrn)
   145  		viper.Set("private_key", confirmation.Credential.PrivateKey)
   146  		viper.Set("certificate", confirmation.Credential.Certificate)
   147  		viper.Set("annotations", annotations)
   148  		credential = confirmation.Credential
   149  	} else {
   150  		// try to read local options
   151  		opts, optsErr := config.Read()
   152  		if optsErr != nil {
   153  			log.Warn().Msg("could not load configuration, please use --token or --config with the appropriate values")
   154  			os.Exit(1)
   155  		}
   156  		// print the used config to the user
   157  		config.DisplayUsedConfig()
   158  
   159  		httpClient, err = opts.GetHttpClient()
   160  		if err != nil {
   161  			log.Warn().Err(err).Msg("could not create http client")
   162  			os.Exit(1)
   163  		}
   164  
   165  		if opts.AgentMrn != "" {
   166  			// already authenticated
   167  			log.Info().Msg("client is already logged in, skipping")
   168  			credential = opts.GetServiceCredential()
   169  		} else {
   170  			credential = opts.GetServiceCredential()
   171  
   172  			// run ping pong
   173  			plugins := []ranger.ClientPlugin{}
   174  			plugins = append(plugins, defaultPlugins...)
   175  			certAuth, err := upstream.NewServiceAccountRangerPlugin(credential)
   176  			if err != nil {
   177  				log.Warn().Err(err).Msg("could not initialize certificate authentication")
   178  				os.Exit(1)
   179  			}
   180  			plugins = append(plugins, certAuth)
   181  
   182  			client, err := upstream.NewAgentManagerClient(apiEndpoint, httpClient, plugins...)
   183  			if err != nil {
   184  				log.Warn().Err(err).Msg("could not connect to Mondoo Platform")
   185  				os.Exit(1)
   186  			}
   187  
   188  			name := viper.GetString("name")
   189  			if name == "" {
   190  				name = sysInfo.Hostname
   191  			}
   192  
   193  			confirmation, err := client.RegisterAgent(context.Background(), &upstream.AgentRegistrationRequest{
   194  				Name: name,
   195  				AgentInfo: &upstream.AgentInfo{
   196  					Mrn:              opts.AgentMrn,
   197  					Version:          sysInfo.Version,
   198  					Build:            sysInfo.Build,
   199  					PlatformName:     sysInfo.Platform.Name,
   200  					PlatformRelease:  sysInfo.Platform.Version,
   201  					PlatformArch:     sysInfo.Platform.Arch,
   202  					PlatformIp:       sysInfo.IP,
   203  					PlatformHostname: sysInfo.Hostname,
   204  					Labels:           opts.Labels,
   205  					PlatformId:       sysInfo.PlatformId,
   206  				},
   207  			})
   208  			if err != nil {
   209  				log.Fatal().Err(err).Msg("failed to log in client")
   210  			}
   211  
   212  			// update configuration file, api-endpoint is set automatically
   213  			// NOTE: we ignore the credentials from confirmation since the service never returns the credentials again
   214  			viper.Set("agent_mrn", confirmation.AgentMrn)
   215  		}
   216  	}
   217  
   218  	err = config.StoreConfig()
   219  	if err != nil {
   220  		log.Warn().Err(err).Msg("could not write mondoo configuration")
   221  		os.Exit(1)
   222  	}
   223  
   224  	// run ping pong to validate the service account
   225  	plugins := []ranger.ClientPlugin{}
   226  	plugins = append(plugins, defaultPlugins...)
   227  	certAuth, err := upstream.NewServiceAccountRangerPlugin(credential)
   228  	if err != nil {
   229  		log.Warn().Err(err).Msg("could not initialize certificate authentication")
   230  	}
   231  	plugins = append(plugins, certAuth)
   232  	client, err := upstream.NewAgentManagerClient(apiEndpoint, httpClient, plugins...)
   233  	if err != nil {
   234  		log.Warn().Err(err).Msg("could not connect to mondoo platform")
   235  		os.Exit(1)
   236  	}
   237  
   238  	_, err = client.PingPong(context.Background(), &upstream.Ping{})
   239  	if err != nil {
   240  		log.Warn().Msg(err.Error())
   241  		os.Exit(1)
   242  	}
   243  
   244  	log.Info().Msgf("client %s has logged in successfully", viper.Get("agent_mrn"))
   245  }