diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b9a9a95..071ce77 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -11,13 +11,13 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v3 - id: get_version run: echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3) - name: Install dependencies run: | - go get github.com/mitchellh/gox - go get github.com/tcnksm/ghr + go install github.com/mitchellh/gox@latest + go install github.com/tcnksm/ghr@latest - name: Add GOPATH to search path run: echo `go env GOPATH`/bin >> $GITHUB_PATH - name: Build diff --git a/cmd/hunting.go b/cmd/hunting.go index 5392885..37effea 100644 --- a/cmd/hunting.go +++ b/cmd/hunting.go @@ -17,12 +17,13 @@ import ( "bufio" "errors" "fmt" - "github.com/VirusTotal/vt-cli/utils" "os" "regexp" "strconv" "sync" + "github.com/VirusTotal/vt-cli/utils" + vt "github.com/VirusTotal/vt-go" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -243,7 +244,7 @@ func NewHuntingRulesetSetLimitCmd() *cobra.Command { func NewHuntingRulesetUpdateCmd() *cobra.Command { return &cobra.Command{ Use: "update [ruleset id] [rules file]", - Short: "Change the rules for a ruleset.", + Short: "Change the rules for a ruleset", Args: cobra.MinimumNArgs(2), RunE: func(cmd *cobra.Command, args []string) error { @@ -341,6 +342,21 @@ func NewHuntingRulesetAddCmd() *cobra.Command { } +// NewHuntingRulesetSetNotificationEmailsCmd returns a command for setting +// notification emails to a ruleset. +func NewHuntingRulesetSetNotificationEmailsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "notification_emails [ruleset id] [email]...", + Short: "Set ruleset notification emails", + Args: cobra.MinimumNArgs(1), + + RunE: func(cmd *cobra.Command, args []string) error { + return patchRuleset(args[0], "notification_emails", args[1:]) + }, + } + return cmd +} + // NewHuntingRulesetCmd returns a new instance of the 'rulesets' command. func NewHuntingRulesetCmd() *cobra.Command { @@ -371,6 +387,7 @@ func NewHuntingRulesetCmd() *cobra.Command { cmd.AddCommand(NewHuntingRulesetDeleteCmd()) cmd.AddCommand(NewHuntingRulesetDisableCmd()) cmd.AddCommand(NewHuntingRulesetEnableCmd()) + cmd.AddCommand(NewHuntingRulesetSetNotificationEmailsCmd()) cmd.AddCommand(NewHuntingRulesetListCmd()) cmd.AddCommand(NewHuntingRulesetRenameCmd()) cmd.AddCommand(NewHuntingRulesetSetLimitCmd()) diff --git a/cmd/ioc_stream.go b/cmd/ioc_stream.go new file mode 100644 index 0000000..d5f9f56 --- /dev/null +++ b/cmd/ioc_stream.go @@ -0,0 +1,142 @@ +// Copyright © 2023 The VirusTotal CLI authors. All Rights Reserved. +// 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" + "regexp" + "strings" + + "github.com/spf13/viper" + "golang.org/x/sync/errgroup" + + "github.com/VirusTotal/vt-go" + + "github.com/VirusTotal/vt-cli/utils" + + "github.com/spf13/cobra" +) + +// NewIOCStreamCmd returns a new instance of the `ioc +func NewIOCStreamCmd() *cobra.Command { + cmd := &cobra.Command{ + Aliases: []string{"is"}, + Use: "iocstream [id]...", + Short: "Manage IoC Stream notifications", + Args: cobra.ExactArgs(1), + + RunE: func(cmd *cobra.Command, args []string) error { + re, _ := regexp.Compile("\\d+") + p, err := NewPrinter(cmd) + if err != nil { + return err + } + return p.GetAndPrintObjects( + "ioc_stream_notifications/%s", + utils.StringReaderFromCmdArgs(args), + re) + }, + } + + addThreadsFlag(cmd.Flags()) + addIDOnlyFlag(cmd.Flags()) + addIncludeExcludeFlags(cmd.Flags()) + + cmd.AddCommand(NewIOCStreamListCmd()) + cmd.AddCommand(NewIOCStreamDeleteCmd()) + + return cmd +} + +// NewIOCStreamListCmd returns a new instance of the `ioc_stream list` command. +func NewIOCStreamListCmd() *cobra.Command { + cmd := &cobra.Command{ + Aliases: []string{"il"}, + Use: "list", + Short: "List IoCs from notifications", + + RunE: func(cmd *cobra.Command, args []string) error { + p, err := NewPrinter(cmd) + if err != nil { + return err + } + return p.PrintCollection(vt.URL("ioc_stream")) + }, + } + + addIncludeExcludeFlags(cmd.Flags()) + addIDOnlyFlag(cmd.Flags()) + addFilterFlag(cmd.Flags()) + addLimitFlag(cmd.Flags()) + addCursorFlag(cmd.Flags()) + + return cmd +} + +var iocStreamNotificationsDeleteCmdHelp = `Delete notifications from the IoC Stream. + +The command accepts a list of IoC Stream notification IDs. If no IDs are provided, +then all the IoC Stream notifications matching the given filter are deleted. +` + +// NewIOCStreamDeleteCmd returns a new instance of the `ioc_stream delete` command. +func NewIOCStreamDeleteCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "delete [notification id]...", + Short: "Deletes notifications from the IoC Stream", + Long: iocStreamNotificationsDeleteCmdHelp, + + RunE: func(cmd *cobra.Command, args []string) error { + client, err := NewAPIClient() + if err != nil { + return err + } + if len(args) > 0 { + eg := &errgroup.Group{} + for _, arg := range args { + notificationId := arg + eg.Go(func() error { + targetUrl := vt.URL("ioc_stream_notifications/%s", notificationId) + _, err := client.Delete(targetUrl) + return err + }) + } + return eg.Wait() + } else { + filterFlag := viper.GetString("filter") + targetUrl := vt.URL("ioc_stream") + if strings.TrimSpace(filterFlag) == "" { + fmt.Println("This will delete all your IoC Stream notifications.") + fmt.Print("Confirm (y/n)? ") + var s string + fmt.Scanln(&s) + if s != "y" { + return nil + } + } else { + q := targetUrl.Query() + q.Set("filter", filterFlag) + targetUrl.RawQuery = q.Encode() + } + if _, err := client.Delete(targetUrl); err != nil { + return err + } + } + return nil + }, + } + + addFilterFlag(cmd.Flags()) + return cmd +} diff --git a/cmd/vt.go b/cmd/vt.go index c8ea8cd..d809858 100644 --- a/cmd/vt.go +++ b/cmd/vt.go @@ -72,6 +72,7 @@ func NewVTCommand() *cobra.Command { cmd.AddCommand(NewGenDocCmd()) cmd.AddCommand(NewGroupCmd()) cmd.AddCommand(NewHuntingCmd()) + cmd.AddCommand(NewIOCStreamCmd()) cmd.AddCommand(NewInitCmd()) cmd.AddCommand(NewIPCmd()) cmd.AddCommand(NewMetaCmd()) diff --git a/go.mod b/go.mod index 6a91f2f..e158a29 100644 --- a/go.mod +++ b/go.mod @@ -22,5 +22,6 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 github.com/stretchr/testify v1.7.0 + golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 // indirect ) diff --git a/go.sum b/go.sum index 7616c0a..7f33849 100644 --- a/go.sum +++ b/go.sum @@ -15,14 +15,6 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab h1:96tkQLYmgypA3W42fvC3UX3EoOP3hQZuT7d98lnnwyc= -github.com/VirusTotal/vt-go v0.0.0-20210528074736-45bbe34cc8ab/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= -github.com/VirusTotal/vt-go v0.0.0-20211116094520-07a92e6467b7 h1:1oIDITWcezvHQXrm2RDiYsVpCeRbvxY4MLBAFdoaPWQ= -github.com/VirusTotal/vt-go v0.0.0-20211116094520-07a92e6467b7/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= -github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678 h1:IVvLDz0INo1rn7wG4OQub9MkyNNaBp0sQgU1apho/mk= -github.com/VirusTotal/vt-go v0.0.0-20211209151516-855a1e790678/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= -github.com/VirusTotal/vt-go v0.0.0-20220413141716-fce0077b709b h1:YdiHY6VO3InYpfasvfpF6EolaNqKNv6pmGJfBQCVuL4= -github.com/VirusTotal/vt-go v0.0.0-20220413141716-fce0077b709b/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee h1:JDhi0dS8y9QLMJZA7ezLyXHxYaMlyzX6MDkq0SSc304= github.com/VirusTotal/vt-go v0.0.0-20220413144842-e010bf48aaee/go.mod h1:u1+HeRyl/gQs67eDgVEWNE7+x+zCyXhdtNVrRJR5YPE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -89,8 +81,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uitable v0.0.3 h1:9ZY4qCODg6JL1Ui4dL9LqCF4ghWnAOSV2h7xG98SkHE= -github.com/gosuri/uitable v0.0.3/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -108,8 +98,6 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -157,9 +145,6 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= -github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= @@ -281,6 +266,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -297,8 +283,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE= -golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2 h1:wM1k/lXfpc5HdkJJyW9GELpd8ERGdnh8sMGL6Gzq3Ho= golang.org/x/sys v0.0.0-20220909162455-aba9fc2a8ff2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/utils/printer.go b/utils/printer.go index b98ff6f..2e5f759 100644 --- a/utils/printer.go +++ b/utils/printer.go @@ -80,6 +80,15 @@ func ObjectToMap(obj *vt.Object) map[string]interface{} { m := make(map[string]interface{}) m["_id"] = obj.ID() m["_type"] = obj.Type() + + contextAttributes := make(map[string]interface{}) + for _, attr := range obj.ContextAttributes() { + contextAttributes[attr], _ = obj.GetContext(attr) + } + if len(contextAttributes) > 0 { + m["_context_attributes"] = contextAttributes + } + for _, attr := range obj.Attributes() { m[attr], _ = obj.Get(attr) }