Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): Manage Alert Rules in Lacework CLI #597

Merged
merged 7 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions api/alert_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ func NewAlertRule(name string, rule AlertRuleConfig) AlertRule {
}
}

func (rule AlertRuleFilter) Status() string {
if rule.Enabled == 1 {
return "Enabled"
}
return "Disabled"
}

// List returns a list of Alert Rules
func (svc *AlertRulesService) List() (response AlertRulesResponse, err error) {
err = svc.client.RequestDecoder("GET", apiV2AlertRules, nil, &response)
Expand Down
350 changes: 350 additions & 0 deletions cli/cmd/alert_rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
//
// Author:: Darren Murray(<[email protected]>)
// Copyright:: Copyright 2021, Lacework Inc.
// License:: Apache License, Version 2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package cmd

import (
"fmt"
"strconv"
"strings"
"time"

"github.com/AlecAivazis/survey/v2"
"github.com/lacework/go-sdk/api"
"github.com/olekukonko/tablewriter"
"github.com/pkg/errors"
"github.com/spf13/cobra"
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
)

var (
// alert-rules command is used to manage lacework alert rules
alertRulesCommand = &cobra.Command{
Use: "alert-rule",
Aliases: []string{"alert-rules", "ar"},
Short: "manage alert rules",
Long: "Manage routing events to channels using alert rules.",
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
}

// list command is used to list all lacework alert rules
alertRulesListCommand = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "list all alert rules",
Long: "List all alert rules configured in your Lacework account.",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
alertRules, err := cli.LwApi.V2.AlertRules.List()
if err != nil {
return errors.Wrap(err, "unable to get alert rules")
}
if len(alertRules.Data) == 0 {
msg := `There are no alert rules configured in your account.

Get started by integrating your alert rules to manage alerting using the command:

$ lacework alert-rule create
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved

If you prefer to configure alert rules via the WebUI, log in to your account at:

https://%s.lacework.net

Then navigate to Settings > Alert Rules.
`
cli.OutputHuman(fmt.Sprintf(msg, cli.Account))
return nil
}
if cli.JSONOutput() {
return cli.OutputJSON(alertRules)
}

var rows [][]string
for _, rule := range alertRules.Data {
rows = append(rows, []string{rule.Guid, rule.Filter.Name, rule.Filter.Description, rule.Filter.Status()})
}

cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "DESCRIPTION", "ENABLED"}, rows))
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
return nil
},
}
// show command is used to retrieve a lacework alert rule by resource id
alertRulesShowCommand = &cobra.Command{
Use: "show",
Short: "get an alert rule by id",
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
Long: "Get a single alert rule by it's ID.",
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
var response api.AlertRuleResponse
err := cli.LwApi.V2.AlertRules.Get(args[0], &response)
if err != nil {
return errors.Wrap(err, "unable to get alert rule")
}

if cli.JSONOutput() {
return cli.OutputJSON(response)
}

alertRule := response.Data
var headers [][]string
headers = append(headers, []string{alertRule.Guid, alertRule.Filter.Name, alertRule.Filter.Description, alertRule.Filter.Status()})
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved

cli.OutputHuman(renderSimpleTable([]string{"GUID", "NAME", "DESCRIPTION", "ENABLED"}, headers))
cli.OutputHuman("\n")
cli.OutputHuman(buildAlertRuleDetailsTable(alertRule))

return nil
},
}

// delete command is used to remove a lacework alert rule by resource id
alertRulesDeleteCommand = &cobra.Command{
Use: "delete",
Short: "delete a alert rule",
Long: "Delete a single alert rule by it's ID.",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
err := cli.LwApi.V2.AlertRules.Delete(args[0])
if err != nil {
return errors.Wrap(err, "unable to delete alert rule")
}
return nil
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
},
}

// create command is used to create a new lacework alert rule
alertRulesCreateCommand = &cobra.Command{
Use: "create",
Short: "create a new alert rule",
Long: "Creates a new single alert rule.",
RunE: func(_ *cobra.Command, args []string) error {
if !cli.InteractiveMode() {
return errors.New("interactive mode is disabled")
}

response, err := promptCreateAlertRule()
if err != nil {
return errors.Wrap(err, "unable to create alert rule")
}

cli.OutputHuman(fmt.Sprintf("The alert rule was created with ID %s \n", response.Data.Guid))
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
return nil
},
}
)

func init() {
// add the alert-rule command
rootCmd.AddCommand(alertRulesCommand)

// add sub-commands to the alert-rule command
alertRulesCommand.AddCommand(alertRulesListCommand)
alertRulesCommand.AddCommand(alertRulesShowCommand)
alertRulesCommand.AddCommand(alertRulesCreateCommand)
alertRulesCommand.AddCommand(alertRulesDeleteCommand)
}

func buildAlertRuleDetailsTable(rule api.AlertRule) string {
var (
details [][]string
updatedTime string
)
severities := api.NewAlertRuleSeveritiesFromIntSlice(rule.Filter.Severity).ToStringSlice()

if nano, err := strconv.ParseInt(rule.Filter.CreatedOrUpdatedTime, 10, 64); err == nil {
updatedTime = time.Unix(nano/1000, 0).Format(time.RFC3339)
}
details = append(details, []string{"SEVERITIES", strings.Join(severities, ", ")})
details = append(details, []string{"EVENT CATEGORIES", strings.Join(rule.Filter.EventCategories, ", ")})
details = append(details, []string{"DESCRIPTION", rule.Filter.Description})
details = append(details, []string{"UPDATED BY", rule.Filter.CreatedOrUpdatedBy})
details = append(details, []string{"LAST UPDATED", updatedTime})
IDsList := [][]string{{strings.Join(rule.Channels, "\n")}, {strings.Join(rule.Filter.ResourceGroups, "\n")}}

detailsTable := &strings.Builder{}
detailsTable.WriteString(renderOneLineCustomTable("ALERT RULE DETAILS",
renderCustomTable([]string{}, details,
tableFunc(func(t *tablewriter.Table) {
t.SetBorder(false)
t.SetColumnSeparator(" ")
t.SetAutoWrapText(false)
t.SetAlignment(tablewriter.ALIGN_LEFT)
}),
),
tableFunc(func(t *tablewriter.Table) {
t.SetBorder(false)
t.SetAutoWrapText(false)
}),
),
)

detailsTable.WriteString(renderCustomTable([]string{"CHANNELS", "RESOURCE GROUPS"}, IDsList,
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
tableFunc(func(t *tablewriter.Table) {
t.SetBorder(false)
t.SetColumnSeparator(" ")
}),
),
)

return detailsTable.String()
}

func promptCreateAlertRule() (api.AlertRuleResponse, error) {
channelList, channelMap := getAlertChannels()

if len(channelList) < 1 {
return api.AlertRuleResponse{}, errors.New("No Alert Channels found.")
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved
}

questions := []*survey.Question{
{
Name: "name",
Prompt: &survey.Input{Message: "Name: "},
Validate: survey.Required,
},
{
Name: "description",
Prompt: &survey.Input{Message: "Description: "},
Validate: survey.Required,
},
{
Name: "channels",
Prompt: &survey.MultiSelect{
Message: "Select alert channels:",
Options: channelList,
},
Validate: survey.Required,
},
{
Name: "severities",
Prompt: &survey.MultiSelect{
Message: "Select severities:",
Options: []string{"Critical", "High", "Medium", "Low", "Info"},
},
},
{
Name: "eventCategories",
Prompt: &survey.MultiSelect{
Message: "Select event categories:",
Options: []string{"Compliance", "App", "Cloud", "File", "Machine", "User", "Platform"},
},
},
}

answers := struct {
Name string
Description string `survey:"description"`
Channels []string `survey:"channels"`
Severities []string `survey:"severities"`
EventCategories []string `survey:"eventCategories"`
ResourceGroups []string `survey:"resourceGroups"`
}{}

err := survey.Ask(questions, &answers,
survey.WithIcons(promptIconsFunc),
)
if err != nil {
return api.AlertRuleResponse{}, err
}

var channels []string
for _, channel := range answers.Channels {
channels = append(channels, channelMap[channel])
}

resourceGroups, resourceGroupMap := promptAddResourceGroupsToAlertRule()
var groups []string
for _, group := range resourceGroups {
groups = append(groups, resourceGroupMap[group])
}

alertRule := api.NewAlertRule(
answers.Name,
api.AlertRuleConfig{
Description: answers.Description,
Channels: channels,
Severities: api.NewAlertRuleSeverities(answers.Severities),
EventCategories: answers.EventCategories,
ResourceGroups: groups,
})

cli.StartProgress(" Creating alert rule...")
response, err := cli.LwApi.V2.AlertRules.Create(alertRule)

cli.StopProgress()
return response, err
}

func getAlertChannels() ([]string, map[string]string) {
response, err := cli.LwApi.V2.AlertChannels.List()
var channels []string
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return nil, nil
}
var items = make(map[string]string)
for _, i := range response.Data {
displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name)
channels = append(channels, displayName)
items[displayName] = i.ID()
}

return channels, items
}

func getResourceGroups() ([]string, map[string]string) {
response, err := cli.LwApi.V2.ResourceGroups.List()
var groups []string
dmurray-lacework marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return nil, nil
}
var items = make(map[string]string)
for _, i := range response.Data {
displayName := fmt.Sprintf("%s - %s", i.ID(), i.Name)
groups = append(groups, displayName)
items[displayName] = i.ID()
}

return groups, items
}

func promptAddResourceGroupsToAlertRule() ([]string, map[string]string) {
addResourceGroups := false
err := survey.AskOne(&survey.Confirm{
Message: "Add Resource Groups to Alert Rule?",
}, &addResourceGroups)

if err != nil {
return nil, nil
}

if addResourceGroups {
var groups []string
groupList, groupMap := getResourceGroups()

err = survey.AskOne(&survey.MultiSelect{
Message: "Select Resource Groups:",
Options: groupList,
}, &groups)

if err != nil {
return nil, nil
}
return groups, groupMap
}
return nil, nil
}
Loading