fix: resolve gosec SSRF findings and formatting issues
Validate webhook/ntfy URLs at Service construction time and add targeted nolint directives for pre-validated URL usage.
This commit is contained in:
parent
73e01c7664
commit
f8d5a8f6cc
@ -9,6 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
@ -45,9 +46,12 @@ type Params struct {
|
|||||||
|
|
||||||
// Service provides notification functionality.
|
// Service provides notification functionality.
|
||||||
type Service struct {
|
type Service struct {
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
client *http.Client
|
client *http.Client
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
ntfyURL *url.URL
|
||||||
|
slackWebhookURL *url.URL
|
||||||
|
mattermostWebhookURL *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new notify Service.
|
// New creates a new notify Service.
|
||||||
@ -55,13 +59,44 @@ func New(
|
|||||||
_ fx.Lifecycle,
|
_ fx.Lifecycle,
|
||||||
params Params,
|
params Params,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
return &Service{
|
svc := &Service{
|
||||||
log: params.Logger.Get(),
|
log: params.Logger.Get(),
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: httpClientTimeout,
|
Timeout: httpClientTimeout,
|
||||||
},
|
},
|
||||||
config: params.Config,
|
config: params.Config,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
if params.Config.NtfyTopic != "" {
|
||||||
|
u, err := url.ParseRequestURI(params.Config.NtfyTopic)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid ntfy topic URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.ntfyURL = u
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Config.SlackWebhook != "" {
|
||||||
|
u, err := url.ParseRequestURI(params.Config.SlackWebhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid slack webhook URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.slackWebhookURL = u
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Config.MattermostWebhook != "" {
|
||||||
|
u, err := url.ParseRequestURI(params.Config.MattermostWebhook)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(
|
||||||
|
"invalid mattermost webhook URL: %w", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.mattermostWebhookURL = u
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendNotification sends a notification to all configured endpoints.
|
// SendNotification sends a notification to all configured endpoints.
|
||||||
@ -69,13 +104,13 @@ func (svc *Service) SendNotification(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
title, message, priority string,
|
title, message, priority string,
|
||||||
) {
|
) {
|
||||||
if svc.config.NtfyTopic != "" {
|
if svc.ntfyURL != nil {
|
||||||
go func() {
|
go func() {
|
||||||
notifyCtx := context.WithoutCancel(ctx)
|
notifyCtx := context.WithoutCancel(ctx)
|
||||||
|
|
||||||
err := svc.sendNtfy(
|
err := svc.sendNtfy(
|
||||||
notifyCtx,
|
notifyCtx,
|
||||||
svc.config.NtfyTopic,
|
svc.ntfyURL,
|
||||||
title, message, priority,
|
title, message, priority,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -87,13 +122,13 @@ func (svc *Service) SendNotification(
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.config.SlackWebhook != "" {
|
if svc.slackWebhookURL != nil {
|
||||||
go func() {
|
go func() {
|
||||||
notifyCtx := context.WithoutCancel(ctx)
|
notifyCtx := context.WithoutCancel(ctx)
|
||||||
|
|
||||||
err := svc.sendSlack(
|
err := svc.sendSlack(
|
||||||
notifyCtx,
|
notifyCtx,
|
||||||
svc.config.SlackWebhook,
|
svc.slackWebhookURL,
|
||||||
title, message, priority,
|
title, message, priority,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -105,13 +140,13 @@ func (svc *Service) SendNotification(
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if svc.config.MattermostWebhook != "" {
|
if svc.mattermostWebhookURL != nil {
|
||||||
go func() {
|
go func() {
|
||||||
notifyCtx := context.WithoutCancel(ctx)
|
notifyCtx := context.WithoutCancel(ctx)
|
||||||
|
|
||||||
err := svc.sendSlack(
|
err := svc.sendSlack(
|
||||||
notifyCtx,
|
notifyCtx,
|
||||||
svc.config.MattermostWebhook,
|
svc.mattermostWebhookURL,
|
||||||
title, message, priority,
|
title, message, priority,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -126,18 +161,19 @@ func (svc *Service) SendNotification(
|
|||||||
|
|
||||||
func (svc *Service) sendNtfy(
|
func (svc *Service) sendNtfy(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
topic, title, message, priority string,
|
topicURL *url.URL,
|
||||||
|
title, message, priority string,
|
||||||
) error {
|
) error {
|
||||||
svc.log.Debug(
|
svc.log.Debug(
|
||||||
"sending ntfy notification",
|
"sending ntfy notification",
|
||||||
"topic", topic,
|
"topic", topicURL.String(),
|
||||||
"title", title,
|
"title", title,
|
||||||
)
|
)
|
||||||
|
|
||||||
request, err := http.NewRequestWithContext(
|
request, err := http.NewRequestWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
topic,
|
topicURL.String(),
|
||||||
bytes.NewBufferString(message),
|
bytes.NewBufferString(message),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -147,7 +183,7 @@ func (svc *Service) sendNtfy(
|
|||||||
request.Header.Set("Title", title)
|
request.Header.Set("Title", title)
|
||||||
request.Header.Set("Priority", ntfyPriority(priority))
|
request.Header.Set("Priority", ntfyPriority(priority))
|
||||||
|
|
||||||
resp, err := svc.client.Do(request)
|
resp, err := svc.client.Do(request) //nolint:gosec // URL validated at Service construction time
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("sending ntfy request: %w", err)
|
return fmt.Errorf("sending ntfy request: %w", err)
|
||||||
}
|
}
|
||||||
@ -193,11 +229,12 @@ type SlackAttachment struct {
|
|||||||
|
|
||||||
func (svc *Service) sendSlack(
|
func (svc *Service) sendSlack(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
webhookURL, title, message, priority string,
|
webhookURL *url.URL,
|
||||||
|
title, message, priority string,
|
||||||
) error {
|
) error {
|
||||||
svc.log.Debug(
|
svc.log.Debug(
|
||||||
"sending webhook notification",
|
"sending webhook notification",
|
||||||
"url", webhookURL,
|
"url", webhookURL.String(),
|
||||||
"title", title,
|
"title", title,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -219,7 +256,7 @@ func (svc *Service) sendSlack(
|
|||||||
request, err := http.NewRequestWithContext(
|
request, err := http.NewRequestWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
webhookURL,
|
webhookURL.String(),
|
||||||
bytes.NewBuffer(body),
|
bytes.NewBuffer(body),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -228,7 +265,7 @@ func (svc *Service) sendSlack(
|
|||||||
|
|
||||||
request.Header.Set("Content-Type", "application/json")
|
request.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := svc.client.Do(request)
|
resp, err := svc.client.Do(request) //nolint:gosec // URL validated at Service construction time
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("sending webhook request: %w", err)
|
return fmt.Errorf("sending webhook request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user