mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 17:43:35 -06:00
* remove placeholder image * improve http client options * add http clients options for webhook notifier * ensure http is only used in dev mode * cleanup errors
109 lines
2.7 KiB
Go
109 lines
2.7 KiB
Go
package secretscan
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var errWebHookURL = errors.New("webhook url must be https")
|
|
|
|
// webHookClient is a client for sending leak notifications.
|
|
type webHookClient struct {
|
|
httpClient *http.Client
|
|
version string
|
|
url string
|
|
}
|
|
|
|
var ErrInvalidWebHookStatusCode = errors.New("invalid webhook status code")
|
|
|
|
func newWebHookClient(url, version string, dev bool) (*webHookClient, error) {
|
|
if !strings.HasPrefix(url, "https://") && !dev {
|
|
return nil, errWebHookURL
|
|
}
|
|
|
|
return &webHookClient{
|
|
version: version,
|
|
url: url,
|
|
httpClient: &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
Renegotiation: tls.RenegotiateFreelyAsClient,
|
|
},
|
|
Proxy: http.ProxyFromEnvironment,
|
|
DialContext: (&net.Dialer{
|
|
Timeout: timeout,
|
|
KeepAlive: 15 * time.Second,
|
|
}).DialContext,
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|
ExpectContinueTimeout: 1 * time.Second,
|
|
MaxIdleConns: 100,
|
|
IdleConnTimeout: 30 * time.Second,
|
|
},
|
|
Timeout: time.Second * 30,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
func (wClient *webHookClient) Notify(ctx context.Context,
|
|
token *Token, tokenName string, revoked bool,
|
|
) error {
|
|
revokedMsg := ""
|
|
if revoked {
|
|
revokedMsg = " Grafana has revoked this token"
|
|
}
|
|
|
|
// create request body
|
|
values := map[string]interface{}{
|
|
"alert_uid": uuid.NewString(),
|
|
"title": "SecretScan Alert: Grafana Token leaked",
|
|
"state": "alerting",
|
|
"link_to_upstream_details": token.URL,
|
|
"message": "Token of type " +
|
|
token.Type + " with name " +
|
|
tokenName + " has been publicly exposed in " +
|
|
token.URL + "." + revokedMsg,
|
|
}
|
|
|
|
jsonValue, err := json.Marshal(values)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to marshal webhook request")
|
|
}
|
|
|
|
// Build URL
|
|
// Create request for secretscan server
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost,
|
|
wClient.url, bytes.NewReader(jsonValue))
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to make http request")
|
|
}
|
|
|
|
// Set headers
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Accept", "application/json")
|
|
req.Header.Set("User-Agent", "grafana-secretscan-webhook-client/"+wClient.version)
|
|
|
|
// make http POST request to check for leaked tokens.
|
|
resp, err := wClient.httpClient.Do(req)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to webhook request")
|
|
}
|
|
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("%w. status code %s", ErrInvalidWebHookStatusCode, resp.Status)
|
|
}
|
|
|
|
return nil
|
|
}
|