Remove the unauthenticated webhook and the SQLite migration script
Both existed to carry an upgrade across, and both upgrades are done. /api/alertmanager/webhook took no credential at all: anything able to reach the port could open an incident for anybody. v0.12.0 kept it, deprecated, so the teams release did not stop delivery while the Alertmanager config was edited, and logged a line per payload asking to be moved. The cluster's Alertmanager now posts on an integration key -- verified in the log, every two minutes, with no deprecation line since the rollout -- so the door can be shut rather than left ajar until somebody remembers. A sender still posting there gets the JSON 404 every unknown /api path gets. The tests move with it, which they should have done anyway: the harness mints an integration key for the default team and posts on that, so they exercise the path production uses rather than one only they still used. scripts/sqlite-to-postgres.go goes the same way. It was written to be temporary, it was the last thing needing modernc.org/sqlite, and this install migrated on 2026-09-20. `go mod tidy` drops the driver and its six transitive dependencies with it; the module graph is now chi, pgx, pgerrcode and x/crypto. Anyone still on v0.10.x can take the script out of the v0.12.0 tag, which the README now says. Claude-Session: https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7
This commit is contained in:
@@ -94,27 +94,6 @@ func handleIntegrationWebhook(db *sql.DB, notify NotifyConfig) http.HandlerFunc
|
||||
}
|
||||
}
|
||||
|
||||
// handleLegacyWebhook is the pre-teams unauthenticated endpoint, kept for one
|
||||
// release so an upgrade does not silently stop delivering while somebody edits
|
||||
// the Alertmanager config. It routes to the oldest team, which on an upgraded
|
||||
// install is the Default team everything was moved into.
|
||||
//
|
||||
// It is deprecated and unauthenticated — anything that can reach the port can
|
||||
// open an incident. Move senders to an integration key and this goes away.
|
||||
func handleLegacyWebhook(db *sql.DB, notify NotifyConfig) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
teamID, err := defaultTeamID(r.Context(), db)
|
||||
if err != nil {
|
||||
log.Printf("legacy webhook: no team to route to: %v", err)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
log.Printf("legacy webhook: unauthenticated payload routed to team %d; "+
|
||||
"move this sender to an integration key", teamID)
|
||||
receiveWebhook(w, r, db, notify, teamID)
|
||||
}
|
||||
}
|
||||
|
||||
func receiveWebhook(w http.ResponseWriter, r *http.Request, db *sql.DB, notify NotifyConfig, teamID int64) {
|
||||
var payload amPayload
|
||||
if err := decodeJSON(r, &payload); err != nil {
|
||||
|
||||
@@ -21,10 +21,14 @@ import (
|
||||
// tests can age rows directly — the sweeper's inputs are wall-clock timestamps.
|
||||
type ts struct {
|
||||
*httptest.Server
|
||||
key string
|
||||
db *sql.DB
|
||||
notify api.NotifyConfig
|
||||
deadman api.DeadmanConfig
|
||||
key string
|
||||
// ingestKey is an integration key for the default team: the only way in
|
||||
// since the unauthenticated webhook was removed, so the tests exercise the
|
||||
// same path production does.
|
||||
ingestKey string
|
||||
db *sql.DB
|
||||
notify api.NotifyConfig
|
||||
deadman api.DeadmanConfig
|
||||
}
|
||||
|
||||
// newTS builds a server over a fresh database. Notifications are off
|
||||
@@ -66,6 +70,16 @@ func newDeadmanTS(t *testing.T, deadman api.DeadmanConfig, notify ...api.NotifyC
|
||||
|
||||
s := &ts{Server: srv, key: key, db: database, notify: cfg, deadman: deadman}
|
||||
|
||||
var integration struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
decode(t, s.req(t, http.MethodPost, "/api/teams/"+defaultTeam+"/integrations",
|
||||
map[string]string{"name": "test"}), &integration)
|
||||
if integration.Key == "" {
|
||||
t.Fatal("no integration key was returned")
|
||||
}
|
||||
s.ingestKey = integration.Key
|
||||
|
||||
// Dead man's switches belong to a team now, so a test that wants them
|
||||
// configures the default team the way an owner would.
|
||||
if deadman.Timeout > 0 {
|
||||
@@ -234,7 +248,8 @@ func postWebhook(t *testing.T, s *ts, alerts []map[string]any, groupKey ...strin
|
||||
}
|
||||
}
|
||||
data, _ := json.Marshal(payload)
|
||||
resp, err := http.Post(s.URL+"/api/alertmanager/webhook", "application/json", bytes.NewReader(data))
|
||||
resp, err := http.Post(s.URL+"/api/integrations/"+s.ingestKey+"/alertmanager",
|
||||
"application/json", bytes.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatalf("post webhook: %v", err)
|
||||
}
|
||||
|
||||
@@ -31,14 +31,13 @@ func NewRouter(db *sql.DB, notify NotifyConfig) http.Handler {
|
||||
|
||||
// Alert ingestion. The key in the path says both that the sender may post
|
||||
// and which team the alerts belong to, which is why it needs no session.
|
||||
//
|
||||
// This is the only way in. The pre-teams /api/alertmanager/webhook, which
|
||||
// took no credential at all, was removed in v0.13.0 once the cluster's
|
||||
// Alertmanager had moved onto a key; a sender still posting there gets the
|
||||
// JSON 404 every unknown /api path gets.
|
||||
r.Post("/api/integrations/{key}/alertmanager", handleIntegrationWebhook(db, notify))
|
||||
|
||||
// DEPRECATED, and unauthenticated: anything that can reach the port can
|
||||
// open an incident here. Kept for one release so an upgrade does not stop
|
||||
// delivering while the Alertmanager config is edited; it routes everything
|
||||
// to the oldest team. Remove it once senders carry a key.
|
||||
r.Post("/api/alertmanager/webhook", handleLegacyWebhook(db, notify))
|
||||
|
||||
// Signing in to the web UI. Login trades a password for a session cookie,
|
||||
// which AuthMiddleware accepts in place of an API key.
|
||||
r.Post("/api/login", handleLogin(db, newLoginLimiter(), notify.PublicURL))
|
||||
|
||||
@@ -463,9 +463,10 @@ func teamParam(w http.ResponseWriter, r *http.Request) (int64, bool) {
|
||||
return id, true
|
||||
}
|
||||
|
||||
// defaultTeamID is the team the deprecated unauthenticated webhook routes to:
|
||||
// the oldest one, which on an upgraded install is the "Default" team every
|
||||
// pre-teams row was moved into.
|
||||
// defaultTeamID is the oldest team, which on an upgraded install is the
|
||||
// "Default" team every pre-teams row was moved into and on a fresh one is the
|
||||
// team migration 003 creates. Bootstrap puts the first user in it, so somebody
|
||||
// signing in to a new server lands somewhere rather than in no team at all.
|
||||
func defaultTeamID(ctx context.Context, db *sql.DB) (int64, error) {
|
||||
var id int64
|
||||
err := db.QueryRowContext(ctx, "SELECT id FROM teams ORDER BY id LIMIT 1").Scan(&id)
|
||||
|
||||
Reference in New Issue
Block a user