From 6cd4255ed47a23f42e0bc194d3ea62cdfa71ffa1 Mon Sep 17 00:00:00 2001 From: "Baruch Odem (Rothkoff)" Date: Mon, 12 Jun 2023 10:03:19 +0300 Subject: [PATCH] feat: accept base64 encoded credentials (#84) In Jenkins, I sew users hosting their credentials as a `username:token` base64 encoded secret, so I want to support it as an argument. --- lib/http.go | 17 +++++++++++---- plugins/confluence.go | 4 ++++ plugins/paligo.go | 49 ++++++++++++++++++++++++------------------- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/lib/http.go b/lib/http.go index 15cfa52c..e8c75ba0 100644 --- a/lib/http.go +++ b/lib/http.go @@ -1,6 +1,7 @@ package lib import ( + "encoding/base64" "fmt" "io" "net/http" @@ -10,15 +11,23 @@ type ICredentials interface { GetCredentials() (string, string) } -func HttpRequest(method string, url string, credentials ICredentials) ([]byte, *http.Response, error) { +func CreateBasicAuthCredentials(credentials ICredentials) string { + username, password := credentials.GetCredentials() + return "Basic " + base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))) +} + +type IAuthorizationHeader interface { + GetAuthorizationHeader() string +} + +func HttpRequest(method string, url string, autherization IAuthorizationHeader) ([]byte, *http.Response, error) { request, err := http.NewRequest(method, url, nil) if err != nil { return nil, nil, fmt.Errorf("unexpected error creating an http request %w", err) } - username, password := credentials.GetCredentials() - if username != "" && password != "" { - request.SetBasicAuth(username, password) + if autherization.GetAuthorizationHeader() != "" { + request.Header.Set("Authorization", autherization.GetAuthorizationHeader()) } client := &http.Client{} diff --git a/plugins/confluence.go b/plugins/confluence.go index 9ca4e747..3ba3da45 100644 --- a/plugins/confluence.go +++ b/plugins/confluence.go @@ -39,6 +39,10 @@ func (p *ConfluencePlugin) GetCredentials() (string, string) { return p.Username, p.Token } +func (p *ConfluencePlugin) GetAuthorizationHeader() string { + return lib.CreateBasicAuthCredentials(p) +} + func (p *ConfluencePlugin) DefineCommand(channels Channels) (*cobra.Command, error) { var confluenceCmd = &cobra.Command{ Use: fmt.Sprintf("%s --%s URL", p.GetName(), argUrl), diff --git a/plugins/paligo.go b/plugins/paligo.go index 5c341c42..f221f36f 100644 --- a/plugins/paligo.go +++ b/plugins/paligo.go @@ -19,6 +19,7 @@ const ( paligoInstanceFlag = "instance" paligoUsernameFlag = "username" paligoTokenFlag = "token" + paligoAuthFlag = "auth" paligoFolderFlag = "folder" ) @@ -33,6 +34,7 @@ type PaligoPlugin struct { username string token string + auth string paligoApi *PaligoClient } @@ -41,6 +43,13 @@ func (p *PaligoPlugin) GetCredentials() (string, string) { return p.username, p.token } +func (p *PaligoPlugin) GetAuthorizationHeader() string { + if p.auth != "" { + return fmt.Sprintf("Basic %s", p.auth) + } + return lib.CreateBasicAuthCredentials(p) +} + func (p *PaligoPlugin) GetName() string { return "paligo" } @@ -57,6 +66,11 @@ func (p *PaligoPlugin) DefineCommand(channels Channels) (*cobra.Command, error) Short: "Scan Paligo instance", Long: "Scan Paligo instance for sensitive information.", Run: func(cmd *cobra.Command, args []string) { + // Waits for MarkFlagsMutuallyExclusiveAndRequired https://github.com/spf13/cobra/pull/1972 + if p.auth == "" && (p.username == "" || p.token == "") { + p.Channels.Errors <- fmt.Errorf("exactly one of the flags in the group %v must be set; none were set", []string{paligoAuthFlag, paligoUsernameFlag, paligoTokenFlag}) + return + } log.Info().Msg("Paligo plugin started") p.getItems() }, @@ -67,23 +81,22 @@ func (p *PaligoPlugin) DefineCommand(channels Channels) (*cobra.Command, error) if err != nil { return nil, fmt.Errorf("error while marking flag %s as required: %w", paligoInstanceFlag, err) } - command.Flags().StringVar(&p.username, paligoUsernameFlag, "", "Paligo username [required]") - err = command.MarkFlagRequired(paligoUsernameFlag) - if err != nil { - return nil, fmt.Errorf("error while marking flag %s as required: %w", paligoUsernameFlag, err) - } - command.Flags().StringVar(&p.token, paligoTokenFlag, "", "Paligo token [required]") - err = command.MarkFlagRequired(paligoTokenFlag) - if err != nil { - return nil, fmt.Errorf("error while marking flag %s as required: %w", paligoTokenFlag, err) - } + + command.Flags().StringVar(&p.username, paligoUsernameFlag, "", "Paligo username") + command.Flags().StringVar(&p.token, paligoTokenFlag, "", "Paligo token") + command.MarkFlagsRequiredTogether(paligoUsernameFlag, paligoTokenFlag) + + command.Flags().StringVar(&p.auth, paligoAuthFlag, "", "Paligo encoded username:password") + command.MarkFlagsMutuallyExclusive(paligoUsernameFlag, paligoAuthFlag) + command.MarkFlagsMutuallyExclusive(paligoTokenFlag, paligoAuthFlag) + command.Flags().IntVar(&paligoFolderArg, paligoFolderFlag, 0, "Paligo folder ID") return command, nil } func (p *PaligoPlugin) getItems() { - p.paligoApi = newPaligoApi(paligoInstanceArg, p.username, p.token) + p.paligoApi = newPaligoApi(paligoInstanceArg, p) foldersToProcess, err := p.getFirstProcessingFolders() if err != nil { @@ -238,8 +251,7 @@ type Document struct { type PaligoClient struct { Instance string - Username string - Token string + auth lib.IAuthorizationHeader foldersLimiter *rate.Limiter documentsLimiter *rate.Limiter @@ -264,10 +276,6 @@ func reserveRateLimit(response *http.Response, lim *rate.Limiter, err error) err return nil } -func (p *PaligoClient) GetCredentials() (string, string) { - return p.Username, p.Token -} - func (p *PaligoClient) request(endpoint string, lim *rate.Limiter) ([]byte, error) { if err := lim.Wait(context.Background()); err != nil { log.Error().Msgf("Error waiting for rate limiter: %s", err) @@ -275,7 +283,7 @@ func (p *PaligoClient) request(endpoint string, lim *rate.Limiter) ([]byte, erro } url := fmt.Sprintf("https://%s.paligoapp.com/api/v2/%s", p.Instance, endpoint) - body, response, err := lib.HttpRequest("GET", url, p) + body, response, err := lib.HttpRequest("GET", url, p.auth) if err != nil { if err := reserveRateLimit(response, lim, err); err != nil { return nil, err @@ -322,11 +330,10 @@ func (p *PaligoClient) showDocument(documentId int) (*Document, error) { return document, err } -func newPaligoApi(instance string, username string, token string) *PaligoClient { +func newPaligoApi(instance string, auth lib.IAuthorizationHeader) *PaligoClient { return &PaligoClient{ Instance: instance, - Username: username, - Token: token, + auth: auth, foldersLimiter: rate.NewLimiter(rateLimitPerSecond(PALIGO_FOLDER_SHOW_LIMIT), PALIGO_FOLDER_SHOW_LIMIT), documentsLimiter: rate.NewLimiter(rateLimitPerSecond(PALIGO_DOCUMENT_SHOW_LIMIT), PALIGO_DOCUMENT_SHOW_LIMIT),