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 }