mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SecretScan: Remove placeholder image and polish errors (#61785)
* remove placeholder image * improve http client options * add http clients options for webhook notifier * ensure http is only used in dev mode * cleanup errors
This commit is contained in:
parent
ed076adde5
commit
e2ec219f6a
@ -78,7 +78,13 @@ func ProvideServiceAccountsService(
|
|||||||
s.secretScanInterval = cfg.SectionWithEnvOverrides("secretscan").
|
s.secretScanInterval = cfg.SectionWithEnvOverrides("secretscan").
|
||||||
Key("interval").MustDuration(defaultSecretScanInterval)
|
Key("interval").MustDuration(defaultSecretScanInterval)
|
||||||
if s.secretScanEnabled {
|
if s.secretScanEnabled {
|
||||||
s.secretScanService = secretscan.NewService(s.store, cfg)
|
var errSecret error
|
||||||
|
s.secretScanService, errSecret = secretscan.NewService(s.store, cfg)
|
||||||
|
if errSecret != nil {
|
||||||
|
s.secretScanEnabled = false
|
||||||
|
s.log.Warn("failed to initialize secret scan service. secret scan is disabled",
|
||||||
|
"error", errSecret.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -3,9 +3,12 @@ package secretscan
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -34,19 +37,37 @@ type Token struct {
|
|||||||
ReportedAt string `json:"reported_at"` //nolint
|
ReportedAt string `json:"reported_at"` //nolint
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalidStatusCode = errors.New("invalid status code")
|
var (
|
||||||
|
ErrInvalidStatusCode = errors.New("invalid status code")
|
||||||
|
errSecretScanURL = errors.New("secretscan url must be https")
|
||||||
|
)
|
||||||
|
|
||||||
|
func newClient(url, version string, dev bool) (*client, error) {
|
||||||
|
if !strings.HasPrefix(url, "https://") && !dev {
|
||||||
|
return nil, errSecretScanURL
|
||||||
|
}
|
||||||
|
|
||||||
func newClient(url, version string) *client {
|
|
||||||
return &client{
|
return &client{
|
||||||
version: version,
|
version: version,
|
||||||
baseURL: url,
|
baseURL: url,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Timeout: timeout,
|
Transport: &http.Transport{
|
||||||
Transport: nil,
|
TLSClientConfig: &tls.Config{
|
||||||
CheckRedirect: nil,
|
Renegotiation: tls.RenegotiateFreelyAsClient,
|
||||||
Jar: nil,
|
},
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkTokens checks if any leaked tokens exist.
|
// checkTokens checks if any leaked tokens exist.
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultURL = "https://secretscan.grafana.com"
|
const defaultURL = "https://secret-scanning.grafana.net"
|
||||||
|
|
||||||
type Checker interface {
|
type Checker interface {
|
||||||
CheckTokens(ctx context.Context) error
|
CheckTokens(ctx context.Context) error
|
||||||
@ -40,20 +40,34 @@ type Service struct {
|
|||||||
revoke bool // whether to revoke leaked tokens
|
revoke bool // whether to revoke leaked tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(store SATokenRetriever, cfg *setting.Cfg) *Service {
|
func NewService(store SATokenRetriever, cfg *setting.Cfg) (*Service, error) {
|
||||||
secretscanBaseURL := cfg.SectionWithEnvOverrides("secretscan").Key("base_url").MustString(defaultURL)
|
secretscanBaseURL := cfg.SectionWithEnvOverrides("secretscan").Key("base_url").MustString(defaultURL)
|
||||||
// URL to send outgoing webhook when a token is leaked.
|
// URL to send outgoing webhook when a token is leaked.
|
||||||
oncallURL := cfg.SectionWithEnvOverrides("secretscan").Key("oncall_url").MustString("")
|
oncallURL := cfg.SectionWithEnvOverrides("secretscan").Key("oncall_url").MustString("")
|
||||||
revoke := cfg.SectionWithEnvOverrides("secretscan").Key("revoke").MustBool(true)
|
revoke := cfg.SectionWithEnvOverrides("secretscan").Key("revoke").MustBool(true)
|
||||||
|
|
||||||
|
client, err := newClient(secretscanBaseURL, cfg.BuildVersion, cfg.Env == setting.Dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create secretscan client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var webHookClient WebHookClient
|
||||||
|
if oncallURL != "" {
|
||||||
|
var errWebhook error
|
||||||
|
webHookClient, errWebhook = newWebHookClient(oncallURL, cfg.BuildVersion, cfg.Env == setting.Dev)
|
||||||
|
if errWebhook != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create secretscan webhook client: %w", errWebhook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
store: store,
|
store: store,
|
||||||
client: newClient(secretscanBaseURL, cfg.BuildVersion),
|
client: client,
|
||||||
webHookClient: newWebHookClient(oncallURL, cfg.BuildVersion),
|
webHookClient: webHookClient,
|
||||||
logger: log.New("secretscan"),
|
logger: log.New("secretscan"),
|
||||||
webHookNotify: oncallURL != "",
|
webHookNotify: oncallURL != "",
|
||||||
revoke: revoke,
|
revoke: revoke,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) RetrieveActiveTokens(ctx context.Context) ([]apikey.APIKey, error) {
|
func (s *Service) RetrieveActiveTokens(ctx context.Context) ([]apikey.APIKey, error) {
|
||||||
|
@ -3,14 +3,20 @@ package secretscan
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errWebHookURL = errors.New("webhook url must be https")
|
||||||
|
|
||||||
// webHookClient is a client for sending leak notifications.
|
// webHookClient is a client for sending leak notifications.
|
||||||
type webHookClient struct {
|
type webHookClient struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
@ -20,17 +26,32 @@ type webHookClient struct {
|
|||||||
|
|
||||||
var ErrInvalidWebHookStatusCode = errors.New("invalid webhook status code")
|
var ErrInvalidWebHookStatusCode = errors.New("invalid webhook status code")
|
||||||
|
|
||||||
func newWebHookClient(url, version string) *webHookClient {
|
func newWebHookClient(url, version string, dev bool) (*webHookClient, error) {
|
||||||
|
if !strings.HasPrefix(url, "https://") && !dev {
|
||||||
|
return nil, errWebHookURL
|
||||||
|
}
|
||||||
|
|
||||||
return &webHookClient{
|
return &webHookClient{
|
||||||
version: version,
|
version: version,
|
||||||
url: url,
|
url: url,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
Transport: nil,
|
Transport: &http.Transport{
|
||||||
CheckRedirect: nil,
|
TLSClientConfig: &tls.Config{
|
||||||
Jar: nil,
|
Renegotiation: tls.RenegotiateFreelyAsClient,
|
||||||
Timeout: timeout,
|
},
|
||||||
|
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,
|
func (wClient *webHookClient) Notify(ctx context.Context,
|
||||||
@ -45,7 +66,6 @@ func (wClient *webHookClient) Notify(ctx context.Context,
|
|||||||
values := map[string]interface{}{
|
values := map[string]interface{}{
|
||||||
"alert_uid": uuid.NewString(),
|
"alert_uid": uuid.NewString(),
|
||||||
"title": "SecretScan Alert: Grafana Token leaked",
|
"title": "SecretScan Alert: Grafana Token leaked",
|
||||||
"image_url": "https://images.pexels.com/photos/5119737/pexels-photo-5119737.jpeg?auto=compress&cs=tinysrgb&w=300", //nolint
|
|
||||||
"state": "alerting",
|
"state": "alerting",
|
||||||
"link_to_upstream_details": token.URL,
|
"link_to_upstream_details": token.URL,
|
||||||
"message": "Token of type " +
|
"message": "Token of type " +
|
||||||
|
Loading…
Reference in New Issue
Block a user