bitbucket.org/Aishee/synsec@v0.0.0-20210414005726-236fc01a153d/cmd/synsec-cli/machines.go (about)

     1  package main
     2  
     3  import (
     4  	saferand "crypto/rand"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"math/big"
     9  	"os"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/AlecAivazis/survey/v2"
    14  	"bitbucket.org/Aishee/synsec/pkg/csconfig"
    15  	"bitbucket.org/Aishee/synsec/pkg/database"
    16  	"github.com/denisbrodbeck/machineid"
    17  	"github.com/enescakir/emoji"
    18  	"github.com/go-openapi/strfmt"
    19  	"github.com/olekukonko/tablewriter"
    20  	"github.com/pkg/errors"
    21  	log "github.com/sirupsen/logrus"
    22  	"github.com/spf13/cobra"
    23  	"gopkg.in/yaml.v2"
    24  )
    25  
    26  var machineID string
    27  var machinePassword string
    28  var interactive bool
    29  var apiURL string
    30  var outputFile string
    31  var forceAdd bool
    32  var autoAdd bool
    33  
    34  var (
    35  	passwordLength = 64
    36  	upper          = "ABCDEFGHIJKLMNOPQRSTUVWXY"
    37  	lower          = "abcdefghijklmnopqrstuvwxyz"
    38  	digits         = "0123456789"
    39  )
    40  
    41  const (
    42  	uuid = "/proc/sys/kernel/random/uuid"
    43  )
    44  
    45  func generatePassword(length int) string {
    46  
    47  	charset := upper + lower + digits
    48  	charsetLength := len(charset)
    49  
    50  	buf := make([]byte, length)
    51  	for i := 0; i < length; i++ {
    52  		rInt, err := saferand.Int(saferand.Reader, big.NewInt(int64(charsetLength)))
    53  		if err != nil {
    54  			log.Fatalf("failed getting data from prng for password generation : %s", err)
    55  		}
    56  		buf[i] = charset[rInt.Int64()]
    57  	}
    58  
    59  	return string(buf)
    60  }
    61  
    62  func generateID() (string, error) {
    63  	id, err := machineid.ID()
    64  	if err != nil {
    65  		log.Debugf("failed to get machine-id with usual files : %s", err)
    66  	}
    67  	if id == "" || err != nil {
    68  		bID, err := ioutil.ReadFile(uuid)
    69  		if err != nil {
    70  			return "", errors.Wrap(err, "generating machine id")
    71  		}
    72  		id = string(bID)
    73  	}
    74  	id = strings.ReplaceAll(id, "-", "")[:32]
    75  	id = fmt.Sprintf("%s%s", id, generatePassword(16))
    76  	return id, nil
    77  }
    78  
    79  func NewMachinesCmd() *cobra.Command {
    80  	/* ---- DECISIONS COMMAND */
    81  	var cmdMachines = &cobra.Command{
    82  		Use:   "machines [action]",
    83  		Short: "Manage local API machines",
    84  		Long: `
    85  Machines Management.
    86  
    87  To list/add/delete/validate machines
    88  `,
    89  		Example: `ccscli machines [action]`,
    90  		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
    91  			if err := csConfig.LoadDBConfig(); err != nil {
    92  				log.Fatalf(err.Error())
    93  			}
    94  			return nil
    95  		},
    96  	}
    97  
    98  	var cmdMachinesList = &cobra.Command{
    99  		Use:     "list",
   100  		Short:   "List machines",
   101  		Long:    `List `,
   102  		Example: `ccscli machines list`,
   103  		Args:    cobra.MaximumNArgs(1),
   104  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   105  			var err error
   106  
   107  			dbClient, err = database.NewClient(csConfig.DbConfig)
   108  			if err != nil {
   109  				log.Fatalf("unable to create new database client: %s", err)
   110  			}
   111  		},
   112  		Run: func(cmd *cobra.Command, args []string) {
   113  			machines, err := dbClient.ListMachines()
   114  			if err != nil {
   115  				log.Errorf("unable to list blockers: %s", err)
   116  			}
   117  			if csConfig.Cscli.Output == "human" {
   118  				table := tablewriter.NewWriter(os.Stdout)
   119  				table.SetCenterSeparator("")
   120  				table.SetColumnSeparator("")
   121  
   122  				table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
   123  				table.SetAlignment(tablewriter.ALIGN_LEFT)
   124  				table.SetHeader([]string{"Name", "IP Address", "Last Update", "Status", "Version"})
   125  				for _, w := range machines {
   126  					var validated string
   127  					if w.IsValidated {
   128  						validated = fmt.Sprintf("%s", emoji.CheckMark)
   129  					} else {
   130  						validated = fmt.Sprintf("%s", emoji.Prohibited)
   131  					}
   132  					table.Append([]string{w.MachineId, w.IpAddress, w.UpdatedAt.Format(time.RFC3339), validated, w.Version})
   133  				}
   134  				table.Render()
   135  			} else if csConfig.Cscli.Output == "json" {
   136  				x, err := json.MarshalIndent(machines, "", " ")
   137  				if err != nil {
   138  					log.Fatalf("failed to unmarshal")
   139  				}
   140  				fmt.Printf("%s", string(x))
   141  			} else if csConfig.Cscli.Output == "raw" {
   142  				for _, w := range machines {
   143  					var validated string
   144  					if w.IsValidated {
   145  						validated = "true"
   146  					} else {
   147  						validated = "false"
   148  					}
   149  					fmt.Printf("%s,%s,%s,%s,%s\n", w.MachineId, w.IpAddress, w.UpdatedAt.Format(time.RFC3339), validated, w.Version)
   150  				}
   151  			} else {
   152  				log.Errorf("unknown output '%s'", csConfig.Cscli.Output)
   153  			}
   154  		},
   155  	}
   156  	cmdMachines.AddCommand(cmdMachinesList)
   157  
   158  	var cmdMachinesAdd = &cobra.Command{
   159  		Use:   "add",
   160  		Short: "add machine to the database.",
   161  		Long:  `Register a new machine in the database. ccscli should be on the same machine as LAPI.`,
   162  		Example: `
   163  ccscli machines add --auto
   164  ccscli machines add MyTestMachine --auto
   165  ccscli machines add MyTestMachine --password MyPassword
   166  `,
   167  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   168  			var err error
   169  			dbClient, err = database.NewClient(csConfig.DbConfig)
   170  			if err != nil {
   171  				log.Fatalf("unable to create new database client: %s", err)
   172  			}
   173  		},
   174  		Run: func(cmd *cobra.Command, args []string) {
   175  			var dumpFile string
   176  			var err error
   177  
   178  			// create machineID if doesn't specified by user
   179  			if len(args) == 0 {
   180  				if !autoAdd {
   181  					err = cmd.Help()
   182  					if err != nil {
   183  						log.Fatalf("unable to print help(): %s", err)
   184  					}
   185  					return
   186  				}
   187  				machineID, err = generateID()
   188  				if err != nil {
   189  					log.Fatalf("unable to generate machine id : %s", err)
   190  				}
   191  			} else {
   192  				machineID = args[0]
   193  			}
   194  
   195  			/*check if file already exists*/
   196  			if outputFile != "" {
   197  				dumpFile = outputFile
   198  			} else if csConfig.API.Client.CredentialsFilePath != "" {
   199  				dumpFile = csConfig.API.Client.CredentialsFilePath
   200  			}
   201  
   202  			// create password if doesn't specified by user
   203  			if machinePassword == "" && !interactive {
   204  				if !autoAdd {
   205  					err = cmd.Help()
   206  					if err != nil {
   207  						log.Fatalf("unable to print help(): %s", err)
   208  					}
   209  					return
   210  				}
   211  				machinePassword = generatePassword(passwordLength)
   212  			} else if machinePassword == "" && interactive {
   213  				qs := &survey.Password{
   214  					Message: "Please provide a password for the machine",
   215  				}
   216  				survey.AskOne(qs, &machinePassword)
   217  			}
   218  			password := strfmt.Password(machinePassword)
   219  			_, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd)
   220  			if err != nil {
   221  				log.Fatalf("unable to create machine: %s", err)
   222  			}
   223  			log.Infof("Machine '%s' successfully added to the local API", machineID)
   224  
   225  			if apiURL == "" {
   226  				if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" {
   227  					apiURL = csConfig.API.Client.Credentials.URL
   228  				} else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" {
   229  					apiURL = "http://" + csConfig.API.Server.ListenURI
   230  				} else {
   231  					log.Fatalf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter")
   232  				}
   233  			}
   234  			apiCfg := csconfig.ApiCredentialsCfg{
   235  				Login:    machineID,
   236  				Password: password.String(),
   237  				URL:      apiURL,
   238  			}
   239  			apiConfigDump, err := yaml.Marshal(apiCfg)
   240  			if err != nil {
   241  				log.Fatalf("unable to marshal api credentials: %s", err)
   242  			}
   243  			if dumpFile != "" {
   244  				err = ioutil.WriteFile(dumpFile, apiConfigDump, 0644)
   245  				if err != nil {
   246  					log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err)
   247  				}
   248  				log.Printf("API credentials dumped to '%s'", dumpFile)
   249  			} else {
   250  				fmt.Printf("%s\n", string(apiConfigDump))
   251  			}
   252  		},
   253  	}
   254  	cmdMachinesAdd.Flags().StringVarP(&machinePassword, "password", "p", "", "machine password to login to the API")
   255  	cmdMachinesAdd.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination (defaults to /etc/synsec/local_api_credentials.yaml)")
   256  	cmdMachinesAdd.Flags().StringVarP(&apiURL, "url", "u", "", "URL of the local API")
   257  	cmdMachinesAdd.Flags().BoolVarP(&interactive, "interactive", "i", false, "interfactive mode to enter the password")
   258  	cmdMachinesAdd.Flags().BoolVarP(&autoAdd, "auto", "a", false, "automatically generate password (and username if not provided)")
   259  	cmdMachinesAdd.Flags().BoolVar(&forceAdd, "force", false, "will force add the machine if it already exist")
   260  	cmdMachines.AddCommand(cmdMachinesAdd)
   261  
   262  	var cmdMachinesDelete = &cobra.Command{
   263  		Use:     "delete --machine MyTestMachine",
   264  		Short:   "delete machines",
   265  		Example: `ccscli machines delete <machine_name>`,
   266  		Args:    cobra.ExactArgs(1),
   267  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   268  			var err error
   269  			dbClient, err = database.NewClient(csConfig.DbConfig)
   270  			if err != nil {
   271  				log.Fatalf("unable to create new database client: %s", err)
   272  			}
   273  		},
   274  		Run: func(cmd *cobra.Command, args []string) {
   275  			machineID = args[0]
   276  			err := dbClient.DeleteWatcher(machineID)
   277  			if err != nil {
   278  				log.Errorf("unable to delete machine: %s", err)
   279  				return
   280  			}
   281  			log.Infof("machine '%s' deleted successfully", machineID)
   282  		},
   283  	}
   284  	cmdMachinesDelete.Flags().StringVarP(&machineID, "machine", "m", "", "machine to delete")
   285  	cmdMachines.AddCommand(cmdMachinesDelete)
   286  
   287  	var cmdMachinesValidate = &cobra.Command{
   288  		Use:     "validate",
   289  		Short:   "validate a machine to access the local API",
   290  		Long:    `validate a machine to access the local API.`,
   291  		Example: `ccscli machines validate <machine_name>`,
   292  		Args:    cobra.ExactArgs(1),
   293  		PersistentPreRun: func(cmd *cobra.Command, args []string) {
   294  			var err error
   295  			dbClient, err = database.NewClient(csConfig.DbConfig)
   296  			if err != nil {
   297  				log.Fatalf("unable to create new database client: %s", err)
   298  			}
   299  		},
   300  		Run: func(cmd *cobra.Command, args []string) {
   301  			machineID = args[0]
   302  			if err := dbClient.ValidateMachine(machineID); err != nil {
   303  				log.Fatalf("unable to validate machine '%s': %s", machineID, err)
   304  			}
   305  			log.Infof("machine '%s' validated successfuly", machineID)
   306  		},
   307  	}
   308  	cmdMachines.AddCommand(cmdMachinesValidate)
   309  
   310  	return cmdMachines
   311  }