fix(security): harden TLS, timeouts, validation, logging, and token generation

- Default TLS verification on, opt-in UPKEEP_INSECURE_SKIP_VERIFY
- Alert webhooks use 10s timeout client, close response bodies
- URL input validates http/https scheme for HTTP monitors
- Stdlib logs route to stderr instead of discard
- Panic on crypto/rand failure in token generation
- Cluster startup warnings for non-HTTPS and missing secret
- Replace demo SMTP creds with obvious placeholders
- Color-coded log entries and scroll hints in logs tab
This commit is contained in:
2026-05-14 11:46:06 -04:00
parent b7592ee9e5
commit 11848ce674
7 changed files with 156 additions and 34 deletions
+29 -9
View File
@@ -7,8 +7,11 @@ import (
"go-upkeep/internal/models"
"net/http"
"net/smtp"
"time"
)
var alertClient = &http.Client{Timeout: 10 * time.Second}
type Provider interface {
Send(title, message string) error
}
@@ -24,7 +27,9 @@ func GetProvider(cfg models.AlertConfig) Provider {
return &WebhookProvider{URL: cfg.Settings["url"]}
case "email":
port := "25"
if p, ok := cfg.Settings["port"]; ok { port = p }
if p, ok := cfg.Settings["port"]; ok {
port = p
}
return &EmailProvider{
Host: cfg.Settings["host"],
Port: port,
@@ -40,40 +45,55 @@ func GetProvider(cfg models.AlertConfig) Provider {
// --- DISCORD ---
type DiscordProvider struct{ URL string }
func (d *DiscordProvider) Send(title, message string) error {
payload := map[string]string{"content": fmt.Sprintf("**%s**\n%s", title, message)}
jsonValue, _ := json.Marshal(payload)
_, err := http.Post(d.URL, "application/json", bytes.NewBuffer(jsonValue))
return err
resp, err := alertClient.Post(d.URL, "application/json", bytes.NewBuffer(jsonValue))
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// --- SLACK ---
type SlackProvider struct{ URL string }
func (s *SlackProvider) Send(title, message string) error {
payload := map[string]string{"text": fmt.Sprintf("*%s*\n%s", title, message)}
jsonValue, _ := json.Marshal(payload)
_, err := http.Post(s.URL, "application/json", bytes.NewBuffer(jsonValue))
return err
resp, err := alertClient.Post(s.URL, "application/json", bytes.NewBuffer(jsonValue))
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// --- GENERIC WEBHOOK ---
type WebhookProvider struct{ URL string }
func (w *WebhookProvider) Send(title, message string) error {
// Sends a standard JSON payload
payload := map[string]string{
"title": title,
"message": message,
"status": "alert",
}
jsonValue, _ := json.Marshal(payload)
_, err := http.Post(w.URL, "application/json", bytes.NewBuffer(jsonValue))
return err
resp, err := alertClient.Post(w.URL, "application/json", bytes.NewBuffer(jsonValue))
if err != nil {
return err
}
resp.Body.Close()
return nil
}
// --- EMAIL ---
type EmailProvider struct {
Host, Port, User, Pass, To, From string
}
func (e *EmailProvider) Send(title, message string) error {
auth := smtp.PlainAuth("", e.User, e.Pass, e.Host)
msg := []byte("To: " + e.To + "\r\n" +
@@ -81,4 +101,4 @@ func (e *EmailProvider) Send(title, message string) error {
"\r\n" +
message + "\r\n")
return smtp.SendMail(e.Host+":"+e.Port, auth, e.From, []string{e.To}, msg)
}
}