diff --git a/cli/carve.go b/cli/carve.go index 4375d9c1..2cd6b023 100644 --- a/cli/carve.go +++ b/cli/carve.go @@ -94,19 +94,19 @@ func listCarves(c *cli.Context) error { "ArchivePath", } // Prepare output - if jsonFlag { + if formatFlag == jsonFormat { jsonRaw, err := json.Marshal(cs) if err != nil { return err } fmt.Println(string(jsonRaw)) - } else if csvFlag { + } else if formatFlag == csvFormat { data := carvesToData(cs, header) w := csv.NewWriter(os.Stdout) if err := w.WriteAll(data); err != nil { return err } - } else if prettyFlag { + } else if formatFlag == prettyFormat { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) if len(cs) > 0 { diff --git a/cli/main.go b/cli/main.go index ded744cc..078abd83 100644 --- a/cli/main.go +++ b/cli/main.go @@ -32,6 +32,13 @@ const ( appDescription string = appUsage + ", a fast and efficient osquery management" ) +const ( + // Values for output format + jsonFormat = "json" + csvFormat = "csv" + prettyFormat = "pretty" +) + // Global variables var ( err error @@ -49,15 +56,14 @@ var ( envs *environments.Environment db *backend.DBManager osctrlAPI *OsctrlAPI + formats map[string]bool ) // Variables for flags var ( dbFlag bool apiFlag bool - jsonFlag bool - csvFlag bool - prettyFlag bool + formatFlag string silentFlag bool insecureFlag bool dbConfigFile string @@ -177,26 +183,13 @@ func init() { Usage: "Allow insecure server connections when using SSL", Destination: &insecureFlag, }, - &cli.BoolFlag{ - Name: "json", - Aliases: []string{"j"}, - Value: false, - Usage: "Print output in JSON format", - Destination: &jsonFlag, - }, - &cli.BoolFlag{ - Name: "csv", - Aliases: []string{"c"}, - Value: false, - Usage: "Print output in CSV format", - Destination: &csvFlag, - }, - &cli.BoolFlag{ - Name: "pretty", - Aliases: []string{"p"}, - Value: true, - Usage: "Print output in pretty format (table)", - Destination: &prettyFlag, + &cli.StringFlag{ + Name: "output-format", + Aliases: []string{"o"}, + Value: prettyFormat, + Usage: "Format to be used for data output", + EnvVars: []string{"OUTPUT_FORMAT"}, + Destination: &formatFlag, }, &cli.BoolFlag{ Name: "silent", @@ -298,9 +291,9 @@ func init() { Action: cliWrapper(editUser), }, { - Name: "permissions", - Aliases: []string{"p"}, - Usage: "Permission actions for an existing user", + Name: "change-permissions", + Aliases: []string{"p", "access"}, + Usage: "Change permission in an environment for an existing user", Flags: []cli.Flag{ &cli.StringFlag{ Name: "username", @@ -336,20 +329,81 @@ func init() { Hidden: false, Usage: "Grant carve permissions", }, + }, + Action: cliWrapper(changePermissions), + }, + { + Name: "reset-permissions", + Aliases: []string{"R", "reset"}, + Usage: "Clear and reset permissions for a user in an environment", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "User to perform the action", + }, + &cli.StringFlag{ + Name: "environment", + Aliases: []string{"e"}, + Usage: "Environment for this user", + }, &cli.BoolFlag{ - Name: "reset", - Aliases: []string{"R"}, + Name: "admin", + Aliases: []string{"a"}, Hidden: false, - Usage: "Reset permissions for this user", + Usage: "Grant admin permissions", }, &cli.BoolFlag{ - Name: "show", - Aliases: []string{"s"}, + Name: "user", + Aliases: []string{"U"}, Hidden: false, - Usage: "Display all permissions for this user", + Usage: "Grant user permissions", + }, + &cli.BoolFlag{ + Name: "query", + Aliases: []string{"q"}, + Hidden: false, + Usage: "Grant query permissions", + }, + &cli.BoolFlag{ + Name: "carve", + Aliases: []string{"c"}, + Hidden: false, + Usage: "Grant carve permissions", + }, + }, + Action: cliWrapper(resetPermissions), + }, + { + Name: "show-permissions", + Aliases: []string{"S", "perms"}, + Usage: "Show permissions for a user in an environment", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "User to perform the action", + }, + &cli.StringFlag{ + Name: "environment", + Aliases: []string{"e"}, + Usage: "Environment for this user", }, }, - Action: cliWrapper(permissionsUser), + Action: cliWrapper(showPermissions), + }, + { + Name: "all-permissions", + Aliases: []string{"A", "all-perms"}, + Usage: "Show all permissions for an existing user", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "username", + Aliases: []string{"u"}, + Usage: "User to perform the action", + }, + }, + Action: cliWrapper(allPermissions), }, { Name: "delete", @@ -1389,6 +1443,11 @@ func init() { Action: checkAPI, }, } + // Initialize formats values + formats = make(map[string]bool) + formats[prettyFormat] = true + formats[jsonFormat] = true + formats[csvFormat] = true } // Action for the DB check @@ -1431,18 +1490,22 @@ func checkAPI(c *cli.Context) error { // Function to wrap actions func cliWrapper(action func(*cli.Context) error) func(*cli.Context) error { return func(c *cli.Context) error { + // Verify if format is correct + if !formats[formatFlag] { + return fmt.Errorf("❌ invalid format %s", formatFlag) + } // DB connection will be used if dbFlag { // Initialize backend if dbConfigFile != "" { db, err = backend.CreateDBManagerFile(dbConfigFile) if err != nil { - return fmt.Errorf("CreateDBManagerFile - %v", err) + return fmt.Errorf("❌ CreateDBManagerFile - %v", err) } } else { db, err = backend.CreateDBManager(dbConfig) if err != nil { - return fmt.Errorf("CreateDBManager - %v", err) + return fmt.Errorf("❌ CreateDBManager - %v", err) } } // Initialize users @@ -1466,7 +1529,7 @@ func cliWrapper(action func(*cli.Context) error) func(*cli.Context) error { if apiConfigFile != "" { apiConfig, err = loadAPIConfiguration(apiConfigFile) if err != nil { - return fmt.Errorf("loadAPIConfiguration - %v", err) + return fmt.Errorf("❌ loadAPIConfiguration - %v", err) } } // Initialize API @@ -1483,7 +1546,7 @@ func cliWrapper(action func(*cli.Context) error) func(*cli.Context) error { func cliAction(c *cli.Context) error { if c.NumFlags() == 0 { if err := cli.ShowAppHelp(c); err != nil { - log.Fatalf("Error with CLI help - %s", err) + log.Fatalf("❌ Error with CLI help - %s", err) } return cli.Exit("\nNo flags provided", 2) } @@ -1502,6 +1565,6 @@ func main() { app.Commands = commands app.Action = cliAction if err := app.Run(os.Args); err != nil { - log.Fatalf("Failed to execute %v", err) + log.Fatalf("❌ Failed to execute %v", err) } } diff --git a/cli/node.go b/cli/node.go index 0f31489b..3f12008d 100644 --- a/cli/node.go +++ b/cli/node.go @@ -80,19 +80,19 @@ func listNodes(c *cli.Context) error { "OsqueryVersion", } // Prepare output - if jsonFlag { + if formatFlag == jsonFormat { jsonRaw, err := json.Marshal(nds) if err != nil { return fmt.Errorf("❌ error marshaling - %s", err) } fmt.Println(string(jsonRaw)) - } else if csvFlag { + } else if formatFlag == csvFormat { data := nodesToData(nds, header) w := csv.NewWriter(os.Stdout) if err := w.WriteAll(data); err != nil { return fmt.Errorf("❌ error writting csv - %s", err) } - } else if prettyFlag { + } else if formatFlag == prettyFormat { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) if len(nds) > 0 { @@ -211,19 +211,19 @@ func showNode(c *cli.Context) error { "OsqueryVersion", } // Prepare output - if jsonFlag { + if formatFlag == jsonFormat { jsonRaw, err := json.Marshal(node) if err != nil { return fmt.Errorf("❌ error marshaling - %s", err) } fmt.Println(string(jsonRaw)) - } else if csvFlag { + } else if formatFlag == csvFormat { data := nodeToData(node, nil) w := csv.NewWriter(os.Stdout) if err := w.WriteAll(data); err != nil { return fmt.Errorf("❌ error writting csv - %s", err) } - } else if prettyFlag { + } else if formatFlag == prettyFormat { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) data := nodeToData(node, nil) diff --git a/cli/permission.go b/cli/permission.go new file mode 100644 index 00000000..88162062 --- /dev/null +++ b/cli/permission.go @@ -0,0 +1,249 @@ +package main + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "os" + + "github.com/jmpsec/osctrl/users" + "github.com/olekukonko/tablewriter" + "github.com/urfave/cli/v2" +) + +// Helper function to convert user permissions into the data expected for output +func permissionsToData(perms users.UserAccess, header []string) [][]string { + var data [][]string + if header != nil { + data = append(data, header) + } + for e, p := range perms { + _p := []string{ + e, + stringifyBool(p.User), + stringifyBool(p.Admin), + stringifyBool(p.Query), + stringifyBool(p.Carve), + } + data = append(data, _p) + } + return data +} + +// Helper function +func accessToData(access users.EnvAccess, env string, header []string) [][]string { + var data [][]string + if header != nil { + data = append(data, header) + } + _p := []string{ + env, + stringifyBool(access.User), + stringifyBool(access.Admin), + stringifyBool(access.Query), + stringifyBool(access.Carve), + } + data = append(data, _p) + return data +} + +func changePermissions(c *cli.Context) error { + // Get values from flags + username := c.String("username") + if username == "" { + fmt.Println("❌ username is required") + os.Exit(1) + } + envName := c.String("environment") + if envName == "" { + fmt.Println("❌ environment is required") + os.Exit(1) + } + admin := c.Bool("admin") + user := c.Bool("user") + carve := c.Bool("carve") + query := c.Bool("query") + if dbFlag { + env, err := envs.Get(envName) + if err != nil { + return fmt.Errorf("❌ error getting environment - %s", err) + } + // If admin, then all permissions follow + if admin { + user = true + query = true + carve = true + } + // Reset permissions to regular user access + if user { + if err := adminUsers.SetEnvUser(username, env.UUID, user); err != nil { + return fmt.Errorf("❌ error setting user - %s", err) + } + } + if admin { + if err := adminUsers.SetEnvAdmin(username, env.UUID, admin); err != nil { + return fmt.Errorf("❌ error setting admin - %s", err) + } + } + if carve { + if err := adminUsers.SetEnvCarve(username, env.UUID, carve); err != nil { + return fmt.Errorf("❌ error setting carve - %s", err) + } + } + if query { + if err := adminUsers.SetEnvQuery(username, env.UUID, query); err != nil { + return fmt.Errorf("❌ error setting query - %s", err) + } + } + } else if apiFlag { + } + if !silentFlag { + fmt.Printf("✅ permissions changed for user %s successfully\n", username) + } + return nil +} + +func showPermissions(c *cli.Context) error { + // Get values from flags + username := c.String("username") + if username == "" { + fmt.Println("❌ username is required") + os.Exit(1) + } + envName := c.String("environment") + if envName == "" { + fmt.Println("❌ environment is required") + os.Exit(1) + } + // Retrieve data + var userAccess users.EnvAccess + if dbFlag { + env, err := envs.Get(envName) + if err != nil { + return fmt.Errorf("❌ error env get - %s", err) + } + // Show is just display user existing permissions and return + userAccess, err = adminUsers.GetEnvAccess(username, env.UUID) + if err != nil { + return fmt.Errorf("❌ error getting access - %s", err) + } + } else if apiFlag { + } + header := []string{ + "Environment", + "User access", + "Admin access", + "Query access", + "Carve access", + } + // Prepare output + if formatFlag == jsonFormat { + jsonRaw, err := json.Marshal(userAccess) + if err != nil { + return fmt.Errorf("❌ error serializing - %s", err) + } + fmt.Println(string(jsonRaw)) + } else if formatFlag == csvFormat { + data := accessToData(userAccess, envName, header) + w := csv.NewWriter(os.Stdout) + if err := w.WriteAll(data); err != nil { + return fmt.Errorf("❌ error WriteAll - %s", err) + } + } else if formatFlag == prettyFormat { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(header) + data := accessToData(userAccess, envName, nil) + table.AppendBulk(data) + table.Render() + } + return nil +} + +func resetPermissions(c *cli.Context) error { + // Get values from flags + username := c.String("username") + if username == "" { + fmt.Println("❌ username is required") + os.Exit(1) + } + envName := c.String("environment") + if envName == "" { + fmt.Println("❌ environment is required") + os.Exit(1) + } + admin := c.Bool("admin") + user := c.Bool("user") + carve := c.Bool("carve") + query := c.Bool("query") + if dbFlag { + env, err := envs.Get(envName) + if err != nil { + return err + } + // If admin, then all permissions follow + if admin { + user = true + query = true + carve = true + } + if err := adminUsers.DeletePermissions(username, env.UUID); err != nil { + return err + } + access := adminUsers.GenEnvUserAccess([]string{env.UUID}, user, query, carve, admin) + perms := adminUsers.GenPermissions(username, appName, access) + if err := adminUsers.CreatePermissions(perms); err != nil { + return err + } + } else if apiFlag { + } + if !silentFlag { + fmt.Printf("✅ permissions reset for user %s successfully\n", username) + } + return nil +} + +func allPermissions(c *cli.Context) error { + // Get values from flags + username := c.String("username") + if username == "" { + fmt.Println("❌ username is required") + os.Exit(1) + } + var existingAccess users.UserAccess + if dbFlag { + // Show is just display user existing permissions and return + existingAccess, err = adminUsers.GetAccess(username) + if err != nil { + return fmt.Errorf("❌ error getting access - %s", err) + } + } else if apiFlag { + } + header := []string{ + "Environment", + "User access", + "Admin access", + "Query access", + "Carve access", + } + // Prepare output + if formatFlag == jsonFormat { + jsonRaw, err := json.Marshal(existingAccess) + if err != nil { + return fmt.Errorf("❌ error serializing - %s", err) + } + fmt.Println(string(jsonRaw)) + } else if formatFlag == csvFormat { + data := permissionsToData(existingAccess, header) + w := csv.NewWriter(os.Stdout) + if err := w.WriteAll(data); err != nil { + return fmt.Errorf("❌ error WriteAll - %s", err) + } + } else if formatFlag == prettyFormat { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(header) + data := permissionsToData(existingAccess, nil) + table.AppendBulk(data) + table.Render() + } + return nil +} diff --git a/cli/query.go b/cli/query.go index f4336745..852f7317 100644 --- a/cli/query.go +++ b/cli/query.go @@ -98,19 +98,19 @@ func listQueries(c *cli.Context) error { "Deleted", } // Prepare output - if jsonFlag { + if formatFlag == jsonFormat { jsonRaw, err := json.Marshal(qs) if err != nil { return fmt.Errorf("❌ error json marshal - %s", err) } fmt.Println(string(jsonRaw)) - } else if csvFlag { + } else if formatFlag == csvFormat { data := queriesToData(qs, header) w := csv.NewWriter(os.Stdout) if err := w.WriteAll(data); err != nil { return fmt.Errorf("❌ error csv writeall - %s", err) } - } else if prettyFlag { + } else if formatFlag == prettyFormat { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) if len(qs) > 0 { diff --git a/cli/user.go b/cli/user.go index a7b9ee61..bcaea6f3 100644 --- a/cli/user.go +++ b/cli/user.go @@ -40,7 +40,6 @@ func userToData(u users.AdminUser, header []string) [][]string { u.DefaultEnv, u.LastIPAddress, u.LastUserAgent, - stringifyUserAccess(nil), } data = append(data, _u) return data @@ -60,7 +59,7 @@ func addUser(c *cli.Context) error { } env, err := envs.Get(defaultEnv) if err != nil { - return err + return fmt.Errorf("❌ error getting environment - %s", err) } password := c.String("password") email := c.String("email") @@ -68,17 +67,17 @@ func addUser(c *cli.Context) error { admin := c.Bool("admin") user, err := adminUsers.New(username, password, email, fullname, env.UUID, admin) if err != nil { - return err + return fmt.Errorf("❌ error with new user - %s", err) } // Create user if err := adminUsers.Create(user); err != nil { - return err + return fmt.Errorf("❌ error creating user - %s", err) } // Assign permissions to user access := adminUsers.GenEnvUserAccess([]string{env.UUID}, true, (admin == true), (admin == true), (admin == true)) perms := adminUsers.GenPermissions(username, appName, access) if err := adminUsers.CreatePermissions(perms); err != nil { - return err + return fmt.Errorf("❌ error creating permissions - %s", err) } if !silentFlag { fmt.Printf("✅ created user %s successfully", username) @@ -96,37 +95,37 @@ func editUser(c *cli.Context) error { password := c.String("password") if password != "" { if err := adminUsers.ChangePassword(username, password); err != nil { - return err + return fmt.Errorf("❌ error changing password - %s", err) } } email := c.String("email") if email != "" { if err := adminUsers.ChangeEmail(username, email); err != nil { - return err + return fmt.Errorf("❌ error changing email - %s", err) } } fullname := c.String("fullname") if fullname != "" { if err := adminUsers.ChangeFullname(username, fullname); err != nil { - return err + return fmt.Errorf("❌ error changing name - %s", err) } } admin := c.Bool("admin") if admin { if err := adminUsers.ChangeAdmin(username, admin); err != nil { - return err + return fmt.Errorf("❌ error changing admin - %s", err) } } notAdmin := c.Bool("non-admin") if notAdmin { if err := adminUsers.ChangeAdmin(username, false); err != nil { - return err + return fmt.Errorf("❌ error changing non-admin - %s", err) } } defaultEnv := c.String("environment") if defaultEnv != "" { if err := adminUsers.ChangeDefaultEnv(username, defaultEnv); err != nil { - return err + return fmt.Errorf("❌ error changing environment - %s", err) } } if !silentFlag { @@ -142,7 +141,19 @@ func deleteUser(c *cli.Context) error { fmt.Println("❌ username is required") os.Exit(1) } - return adminUsers.Delete(username) + if dbFlag { + if err := adminUsers.Delete(username); err != nil { + return fmt.Errorf("❌ error deleting - %s", err) + } + } else if apiFlag { + if err := osctrlAPI.DeleteUser(username); err != nil { + return fmt.Errorf("❌ error deleting user - %s", err) + } + } + if !silentFlag { + fmt.Println("✅ node was deleted successfully") + } + return nil } func listUsers(c *cli.Context) error { @@ -151,12 +162,12 @@ func listUsers(c *cli.Context) error { if dbFlag { usrs, err = adminUsers.All() if err != nil { - return err + return fmt.Errorf("❌ error getting users - %s", err) } } else if apiFlag { usrs, err = osctrlAPI.GetUsers() if err != nil { - return err + return fmt.Errorf("❌ error getting users - %s", err) } } header := []string{ @@ -166,22 +177,21 @@ func listUsers(c *cli.Context) error { "Default Environment", "Last IPAddress", "Last UserAgent", - "Permissions", } // Prepare output - if jsonFlag { + if formatFlag == jsonFormat { jsonRaw, err := json.Marshal(usrs) if err != nil { - return err + return fmt.Errorf("❌ error serializing - %s", err) } fmt.Println(string(jsonRaw)) - } else if csvFlag { + } else if formatFlag == csvFormat { data := usersToData(usrs, header) w := csv.NewWriter(os.Stdout) if err := w.WriteAll(data); err != nil { - return err + return fmt.Errorf("❌ error WriteAll - %s", err) } - } else if prettyFlag { + } else if formatFlag == prettyFormat { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) if len(usrs) > 0 { @@ -208,12 +218,12 @@ func showUser(c *cli.Context) error { if dbFlag { usr, err = adminUsers.Get(username) if err != nil { - return err + return fmt.Errorf("❌ error getting user - %s", err) } } else if apiFlag { usr, err = osctrlAPI.GetUser(username) if err != nil { - return err + return fmt.Errorf("❌ error getting user - %s", err) } } header := []string{ @@ -223,22 +233,21 @@ func showUser(c *cli.Context) error { "Default Environment", "Last IPAddress", "Last UserAgent", - "Permissions", } // Prepare output - if jsonFlag { + if formatFlag == jsonFormat { jsonRaw, err := json.Marshal(usr) if err != nil { - return err + return fmt.Errorf("❌ error serializing - %s", err) } fmt.Println(string(jsonRaw)) - } else if csvFlag { + } else if formatFlag == csvFormat { data := userToData(usr, nil) w := csv.NewWriter(os.Stdout) if err := w.WriteAll(data); err != nil { - return err + return fmt.Errorf("❌ error WriteAll - %s", err) } - } else if prettyFlag { + } else if formatFlag == prettyFormat { table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) data := userToData(usr, nil) @@ -247,103 +256,3 @@ func showUser(c *cli.Context) error { } return nil } - -func permissionsUser(c *cli.Context) error { - // Get values from flags - username := c.String("username") - if username == "" { - fmt.Println("❌ username is required") - os.Exit(1) - } - show := c.Bool("show") - // Show is just display user existing permissions and return - if show { - existingAccess, err := adminUsers.GetAccess(username) - if err != nil { - return err - } - table := tablewriter.NewWriter(os.Stdout) - table.SetHeader([]string{ - "Username", - "Environment", - "User access", - "Admin access", - "Query access", - "Carve access", - }) - data := [][]string{} - for e, p := range existingAccess { - env, err := envs.Get(e) - if err != nil { - return err - } - r := []string{ - username, - fmt.Sprintf("%s (%s)", e, env.Name), - stringifyBool(p.User), - stringifyBool(p.Admin), - stringifyBool(p.Query), - stringifyBool(p.Carve), - } - data = append(data, r) - } - table.AppendBulk(data) - table.Render() - return nil - } - envName := c.String("environment") - if envName == "" { - fmt.Println("❌ environment is required") - os.Exit(1) - } - env, err := envs.Get(envName) - if err != nil { - return err - } - admin := c.Bool("admin") - user := c.Bool("user") - carve := c.Bool("carve") - query := c.Bool("query") - // If admin, then all permissions follow - if admin { - user = true - query = true - carve = true - } - // Reset permissions to regular user access - reset := c.Bool("reset") - if reset { - if err := adminUsers.DeletePermissions(username, env.UUID); err != nil { - return err - } - access := adminUsers.GenEnvUserAccess([]string{env.UUID}, true, query, carve, admin) - perms := adminUsers.GenPermissions(username, appName, access) - if err := adminUsers.CreatePermissions(perms); err != nil { - return err - } - fmt.Printf("✅ permissions reset for user %s successfully", username) - } else { - if user { - if err := adminUsers.SetEnvUser(username, env.UUID, user); err != nil { - return err - } - } - if admin { - if err := adminUsers.SetEnvAdmin(username, env.UUID, admin); err != nil { - return err - } - } - if carve { - if err := adminUsers.SetEnvCarve(username, env.UUID, carve); err != nil { - return err - } - } - if query { - if err := adminUsers.SetEnvQuery(username, env.UUID, query); err != nil { - return err - } - } - fmt.Printf("✅ permissions changed for user %s successfully", username) - } - return nil -}