From 2cc2d2cccc93075567ee282f7bf838a1b5b5e165 Mon Sep 17 00:00:00 2001 From: jossef Date: Sun, 19 Mar 2023 19:00:56 +0000 Subject: [PATCH] refactor: implemented IPlugin interface --- cmd/main.go | 84 +++++++++++++++++---------------- plugins/confluence.go | 105 +++++++++++++++++++++++++++++++----------- plugins/plugins.go | 53 ++++++--------------- 3 files changed, 135 insertions(+), 107 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 80ec6741..dfe219cd 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "github.com/checkmarx/2ms/plugins" "github.com/checkmarx/2ms/reporting" "github.com/checkmarx/2ms/wrapper" @@ -15,21 +14,11 @@ import ( var rootCmd = &cobra.Command{ Use: "2ms", Short: "2ms Secrets Detection", - Run: runDetection, + Run: execute, } -func init() { - cobra.OnInitialize(initLog) - rootCmd.Flags().BoolP("all", "a", true, "scan all plugins") - - // TODO decouple and move to plugin level - rootCmd.Flags().StringP("confluence", "", "", "scan confluence url") - rootCmd.Flags().StringP("confluence-spaces", "", "", "confluence spaces") - rootCmd.Flags().StringP("confluence-user", "", "", "confluence username or email") - rootCmd.Flags().StringP("confluence-token", "", "", "confluence token") - - rootCmd.Flags().BoolP("all-rules", "r", true, "use all rules") - rootCmd.PersistentFlags().StringP("log-level", "l", "info", "log level (trace, debug, info, warn, error, fatal)") +var allPlugins = []plugins.IPlugin{ + &plugins.ConfluencePlugin{}, } func initLog() { @@ -57,54 +46,71 @@ func initLog() { } func Execute() { + cobra.OnInitialize(initLog) + rootCmd.Flags().BoolP("all", "", true, "scan all plugins") + rootCmd.Flags().BoolP("all-rules", "", true, "all rules") + + for _, plugin := range allPlugins { + err := plugin.DefineCommandLineArgs(rootCmd) + if err != nil { + log.Fatal().Msg(err.Error()) + } + } + + rootCmd.PersistentFlags().StringP("log-level", "", "info", "log level (trace, debug, info, warn, error, fatal)") + if err := rootCmd.Execute(); err != nil { log.Fatal().Msg(err.Error()) } } -func runDetection(cmd *cobra.Command, args []string) { +func execute(cmd *cobra.Command, args []string) { allRules, err := cmd.Flags().GetBool("all-rules") if err != nil { log.Fatal().Msg(err.Error()) } - // Get desired plugins content - plugins := plugins.NewPlugins() - - //allPlugins, _ := cmd.Flags().GetBool("all") + // ------------------------------------- + // Get content from plugins - confluenceUrl, _ := cmd.Flags().GetString("confluence") - - // TODO move to confluence file NewPlugin - if confluenceUrl != "" { - confluenceUser, _ := cmd.Flags().GetString("confluence-user") - // confluenceSpaces, _ := cmd.Flags().GetString("confluence-spaces") - confluenceToken, _ := cmd.Flags().GetString("confluence-token") - - if !strings.HasPrefix("https://", confluenceUrl) && !strings.HasPrefix("http://", confluenceUrl) { - confluenceUrl = fmt.Sprintf("https://%v", confluenceUrl) + for _, plugin := range allPlugins { + err := plugin.Initialize(cmd) + if err != nil { + log.Fatal().Msg(err.Error()) } - confluenceUrl = strings.TrimRight(confluenceUrl, "/") - plugins.AddPlugin("confluence", confluenceUrl, confluenceUser, confluenceToken) } - contents, err := plugins.RunPlugins() - if err != nil { - log.Fatal().Msg(err.Error()) + items := make([]plugins.Item, 0) + for _, plugin := range allPlugins { + if !plugin.IsEnabled() { + continue + } + + pluginItems, err := plugin.GetItems() + if err != nil { + log.Fatal().Msg(err.Error()) + } + items = append(items, *pluginItems...) } report := reporting.Report{} report.Results = make(map[string][]reporting.Secret) - // Run with default configuration + // ------------------------------------- + // Detect Secrets + if allRules { wrap := wrapper.NewWrapper() - for _, c := range contents { - secrets := wrap.Detect(c.Content) - report.Results[c.OriginalUrl] = append(report.Results[c.OriginalUrl], secrets...) + for _, item := range items { + secrets := wrap.Detect(item.Content) + report.Results[item.ID] = append(report.Results[item.ID], secrets...) } - report.TotalItemsScanned = len(contents) + report.TotalItemsScanned = len(items) } + + // ------------------------------------- + // Show Report + reporting.ShowReport(report) } diff --git a/plugins/confluence.go b/plugins/confluence.go index 9da60851..b21438b8 100644 --- a/plugins/confluence.go +++ b/plugins/confluence.go @@ -4,22 +4,71 @@ import ( "encoding/json" "fmt" "github.com/rs/zerolog/log" + "github.com/spf13/cobra" "io" "net/http" + "strings" ) -func (p *Plugin) RunPlugin() ([]Content, error) { - contents := []Content{} +const argConfluence = "confluence" +const argConfluenceSpaces = "confluence-spaces" +const argConfluenceUsername = "confluence-username" +const argConfluenceToken = "confluence-token" + +type ConfluencePlugin struct { + Plugin + URL string + Token string + Username string + Spaces []string +} + +func (p *ConfluencePlugin) IsEnabled() bool { + return p.Enabled +} + +func (p *ConfluencePlugin) DefineCommandLineArgs(cmd *cobra.Command) error { + flags := cmd.Flags() + flags.StringP(argConfluence, "", "", "scan confluence url") + flags.StringP(argConfluenceSpaces, "", "", "confluence spaces") + flags.StringP(argConfluenceUsername, "", "", "confluence username or email") + flags.StringP(argConfluenceToken, "", "", "confluence token") + return nil +} + +func (p *ConfluencePlugin) Initialize(cmd *cobra.Command) error { + flags := cmd.Flags() + confluenceUrl, _ := flags.GetString(argConfluence) + if confluenceUrl == "" { + return nil + } + if !strings.HasPrefix("https://", confluenceUrl) && !strings.HasPrefix("http://", confluenceUrl) { + confluenceUrl = fmt.Sprintf("https://%v", confluenceUrl) + } + confluenceUrl = strings.TrimRight(confluenceUrl, "/") + + confluenceSpaces, _ := flags.GetString(argConfluenceSpaces) + confluenceUsername, _ := flags.GetString(argConfluenceUsername) + confluenceToken, _ := flags.GetString(argConfluenceToken) + + p.Token = confluenceToken + p.Username = confluenceUsername + p.URL = confluenceUrl + p.Spaces = strings.Split(confluenceSpaces, ",") + p.Enabled = true + return nil +} + +func (p *ConfluencePlugin) GetItems() (*[]Item, error) { + items := make([]Item, 0) spaces, err := p.getTotalSpaces() if err != nil { return nil, err } for _, space := range spaces { - spacePages, err := p.getTotalPages(space) - if err != nil { return nil, err } @@ -30,15 +79,15 @@ func (p *Plugin) RunPlugin() ([]Content, error) { return nil, err } - contents = append(contents, *pageContent) + items = append(items, *pageContent) } } - log.Info().Msg("Confluence plugin completed successfully") - return contents, nil + log.Debug().Msg("Confluence plugin completed successfully") + return &items, nil } -func (p *Plugin) getTotalSpaces() ([]SpaceResult, error) { +func (p *ConfluencePlugin) getTotalSpaces() ([]ConfluenceSpaceResult, error) { totalSpaces, err := p.getSpaces(0) if err != nil { return nil, err @@ -62,14 +111,14 @@ func (p *Plugin) getTotalSpaces() ([]SpaceResult, error) { return totalSpaces.Results, nil } -func (p *Plugin) getSpaces(start int) (*SpaceResponse, error) { +func (p *ConfluencePlugin) getSpaces(start int) (*ConfluenceSpaceResponse, error) { url := fmt.Sprintf("%s/rest/api/space?start=%d", p.URL, start) body, err := p.httpRequest(http.MethodGet, url) if err != nil { return nil, fmt.Errorf("unexpected error creating an http request %w", err) } - response := &SpaceResponse{} + response := &ConfluenceSpaceResponse{} jsonErr := json.Unmarshal(body, response) if jsonErr != nil { return nil, fmt.Errorf("could not unmarshal response %w", err) @@ -78,7 +127,7 @@ func (p *Plugin) getSpaces(start int) (*SpaceResponse, error) { return response, nil } -func (p *Plugin) getTotalPages(space SpaceResult) (*PageResult, error) { +func (p *ConfluencePlugin) getTotalPages(space ConfluenceSpaceResult) (*ConfluencePageResult, error) { totalPages, err := p.getPages(space, 0) if err != nil { @@ -103,7 +152,7 @@ func (p *Plugin) getTotalPages(space SpaceResult) (*PageResult, error) { return totalPages, nil } -func (p *Plugin) getPages(space SpaceResult, start int) (*PageResult, error) { +func (p *ConfluencePlugin) getPages(space ConfluenceSpaceResult, start int) (*ConfluencePageResult, error) { url := fmt.Sprintf("%s/rest/api/space/%s/content?start=%d", p.URL, space.Key, start) body, err := p.httpRequest(http.MethodGet, url) @@ -120,7 +169,7 @@ func (p *Plugin) getPages(space SpaceResult, start int) (*PageResult, error) { return &response.Results, nil } -func (p *Plugin) getContent(page Page, space SpaceResult) (*Content, error) { +func (p *ConfluencePlugin) getContent(page ConfluencePage, space ConfluenceSpaceResult) (*Item, error) { url := p.URL + "/rest/api/content/" + page.ID + "?expand=body.storage,body.view.value,version,history.previousVersion" originalUrl := p.URL + "/spaces/" + space.Key + "/pages/" + page.ID request, err := p.httpRequest(http.MethodGet, url) @@ -129,15 +178,15 @@ func (p *Plugin) getContent(page Page, space SpaceResult) (*Content, error) { return nil, fmt.Errorf("unexpected error creating an http request %w", err) } - content := &Content{ - Content: string(request), - Source: url, - OriginalUrl: originalUrl, + content := &Item{ + Content: string(request), + Source: url, + ID: originalUrl, } return content, nil } -func (p *Plugin) httpRequest(method string, url string) ([]byte, error) { +func (p *ConfluencePlugin) httpRequest(method string, url string) ([]byte, error) { var err error request, err := http.NewRequest(method, url, nil) @@ -151,8 +200,8 @@ func (p *Plugin) httpRequest(method string, url string) ([]byte, error) { return nil, fmt.Errorf("unable to send http request %w", err) } - if p.Email == "" && p.Token == "" { - request.SetBasicAuth(p.Email, p.Token) + if p.Username == "" && p.Token == "" { + request.SetBasicAuth(p.Username, p.Token) } if err != nil { @@ -173,28 +222,28 @@ func (p *Plugin) httpRequest(method string, url string) ([]byte, error) { return body, nil } -type SpaceResult struct { +type ConfluenceSpaceResult struct { ID int `json:"id"` Key string `json:"key"` Name string `json:"Name"` Links map[string]string `json:"_links"` } -type SpaceResponse struct { - Results []SpaceResult `json:"results"` - Size int `json:"size"` +type ConfluenceSpaceResponse struct { + Results []ConfluenceSpaceResult `json:"results"` + Size int `json:"size"` } -type Page struct { +type ConfluencePage struct { ID string `json:"id"` Type string `json:"type"` Title string `json:"title"` } -type PageResult struct { - Pages []Page `json:"results"` +type ConfluencePageResult struct { + Pages []ConfluencePage `json:"results"` } type PageResponse struct { - Results PageResult `json:"page"` + Results ConfluencePageResult `json:"page"` } diff --git a/plugins/plugins.go b/plugins/plugins.go index a249747a..47879757 100644 --- a/plugins/plugins.go +++ b/plugins/plugins.go @@ -1,48 +1,21 @@ package plugins -type Plugin struct { - Name string - // TODO missing ID field - URL string // TODO remove as this is confluence coupled - Email string // TODO remove as this is confluence coupled - Token string // TODO remove as this is confluence coupled -} - -type Plugins struct { - plugins map[string]Plugin -} +import "github.com/spf13/cobra" -type Content struct { - Content string - Source string - OriginalUrl string // TODO remove as this is confluence coupled +type Item struct { + Content string + Source string + ID string } -func NewPlugins() *Plugins { - plugins := make(map[string]Plugin) - return &Plugins{plugins: plugins} -} - -func (P *Plugins) AddPlugin(name string, url string, email string, token string) { - P.plugins["Name"] = Plugin{ - Name: name, - URL: url, Email: email, - Token: token, - } +type Plugin struct { + ID string + Enabled bool } -func (P *Plugins) RunPlugins() ([]Content, error) { - contents := []Content{} - for _, p := range P.plugins { - switch p.Name { - case "confluence": - plugin, err := p.RunPlugin() - if err != nil { - return nil, err - } - contents = append(contents, plugin...) - default: - } - } - return contents, nil +type IPlugin interface { + DefineCommandLineArgs(cmd *cobra.Command) error + Initialize(cmd *cobra.Command) error + GetItems() (*[]Item, error) + IsEnabled() bool }