fix(security): phase 2 high-severity hardening
- Push heartbeat accepts Authorization: Bearer header (query string deprecated) - Gotify alerts use X-Gotify-Key header instead of token in URL - Per-IP rate limiting on all API endpoints (token-bucket) - /metrics gated behind cluster secret (UPTOP_METRICS_PUBLIC=true to opt out) - Config export redacts passwords/tokens by default (redact_secrets=false to override) - Fix rewritePlaceholders for 100+ SQL parameters - Fix AddSiteReturningID/AddAlertReturningID race with LastInsertId/RETURNING - HTTP server timeouts: read 30s, write 60s, idle 120s
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
package store
|
||||
|
||||
import "database/sql"
|
||||
import (
|
||||
"database/sql"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type Dialect interface {
|
||||
DriverName() string
|
||||
@@ -13,8 +16,6 @@ type Dialect interface {
|
||||
UpsertNodeSQL() string
|
||||
}
|
||||
|
||||
// rewritePlaceholders converts ? markers to $1, $2, etc. for Postgres.
|
||||
// For SQLite (or any dialect not needing rewrite), returns the input unchanged.
|
||||
func rewritePlaceholders(query string, dollarStyle bool) string {
|
||||
if !dollarStyle {
|
||||
return query
|
||||
@@ -25,10 +26,7 @@ func rewritePlaceholders(query string, dollarStyle bool) string {
|
||||
if query[i] == '?' {
|
||||
n++
|
||||
buf = append(buf, '$')
|
||||
if n >= 10 {
|
||||
buf = append(buf, byte('0'+n/10))
|
||||
}
|
||||
buf = append(buf, byte('0'+n%10))
|
||||
buf = append(buf, []byte(strconv.Itoa(n))...)
|
||||
} else {
|
||||
buf = append(buf, query[i])
|
||||
}
|
||||
|
||||
@@ -195,25 +195,47 @@ func (s *SQLStore) GetAlertByName(name string) (models.AlertConfig, error) {
|
||||
}
|
||||
|
||||
func (s *SQLStore) AddSiteReturningID(site models.Site) (int, error) {
|
||||
if err := s.AddSite(site); err != nil {
|
||||
return 0, err
|
||||
token := ""
|
||||
if site.Type == "push" {
|
||||
var err error
|
||||
token, err = generateToken()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("generate push token: %w", err)
|
||||
}
|
||||
}
|
||||
created, err := s.GetSiteByName(site.Name)
|
||||
if s.dollar {
|
||||
var id int
|
||||
err := s.db.QueryRow(s.q("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused, regions) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id"),
|
||||
site.Name, site.URL, site.Type, token, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries,
|
||||
site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused, site.Regions).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
result, err := s.db.Exec(s.q("INSERT INTO sites (name, url, type, token, interval, alert_id, check_ssl, threshold, max_retries, hostname, port, timeout, method, description, parent_id, accepted_codes, dns_resolve_type, dns_server, ignore_tls, paused, regions) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
|
||||
site.Name, site.URL, site.Type, token, site.Interval, site.AlertID, site.CheckSSL, site.ExpiryThreshold, site.MaxRetries,
|
||||
site.Hostname, site.Port, site.Timeout, site.Method, site.Description, site.ParentID, site.AcceptedCodes, site.DNSResolveType, site.DNSServer, site.IgnoreTLS, site.Paused, site.Regions)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return created.ID, nil
|
||||
id, err := result.LastInsertId()
|
||||
return int(id), err
|
||||
}
|
||||
|
||||
func (s *SQLStore) AddAlertReturningID(name, aType string, settings map[string]string) (int, error) {
|
||||
if err := s.AddAlert(name, aType, settings); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
created, err := s.GetAlertByName(name)
|
||||
stored, err := s.marshalSettings(settings)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return created.ID, nil
|
||||
if s.dollar {
|
||||
var id int
|
||||
err := s.db.QueryRow(s.q("INSERT INTO alerts (name, type, settings) VALUES (?, ?, ?) RETURNING id"), name, aType, stored).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
result, err := s.db.Exec(s.q("INSERT INTO alerts (name, type, settings) VALUES (?, ?, ?)"), name, aType, stored)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
return int(id), err
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetAllAlerts() ([]models.AlertConfig, error) {
|
||||
|
||||
Reference in New Issue
Block a user