Handler returns the HTTP handler for the public SES-over-SNS notifications endpoint. Every request is fail-closed: the SNS signature is verified before anything is acted on (the endpoint is public). It auto-confirms a SubscriptionConfirmation (GET the allow-listed SubscribeURL) and feeds a Notificat
(v *Verifier, c *Consumer)
| 23 | // handled or safely-ignored message (so SES stops retrying); 500 only when the |
| 24 | // Consumer hits a real error worth a retry. |
| 25 | func Handler(v *Verifier, c *Consumer) http.HandlerFunc { |
| 26 | // No redirect-following: the SubscribeURL host is allow-listed |
| 27 | // (sns.*.amazonaws.com) before the GET, and we must not let a redirect carry |
| 28 | // the request to a non-allow-listed (internal) host — SSRF defense in depth. |
| 29 | client := &http.Client{ |
| 30 | Timeout: 10 * time.Second, |
| 31 | CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, |
| 32 | } |
| 33 | return func(w http.ResponseWriter, r *http.Request) { |
| 34 | if r.Method != http.MethodPost { |
| 35 | http.Error(w, "method not allowed", http.StatusMethodNotAllowed) |
| 36 | return |
| 37 | } |
| 38 | body, err := io.ReadAll(io.LimitReader(r.Body, maxSNSBody)) |
| 39 | if err != nil { |
| 40 | http.Error(w, "read error", http.StatusBadRequest) |
| 41 | return |
| 42 | } |
| 43 | var m SNSMessage |
| 44 | if err := json.Unmarshal(body, &m); err != nil { |
| 45 | http.Error(w, "bad json", http.StatusBadRequest) |
| 46 | return |
| 47 | } |
| 48 | // Fail-closed: verify the SNS signature before acting on anything. |
| 49 | if err := v.Verify(r.Context(), &m); err != nil { |
| 50 | log.Printf("[delivery] SNS signature verification failed: %v", err) |
| 51 | http.Error(w, "forbidden", http.StatusForbidden) |
| 52 | return |
| 53 | } |
| 54 | |
| 55 | switch m.Type { |
| 56 | case "SubscriptionConfirmation": |
| 57 | if url, ok := ConfirmSubscriptionURL(&m); ok { |
| 58 | if err := confirmSubscription(r.Context(), client, url); err != nil { |
| 59 | log.Printf("[delivery] subscription confirm failed: %v", err) |
| 60 | } else { |
| 61 | log.Printf("[delivery] confirmed SNS subscription for topic %s", m.TopicArn) |
| 62 | } |
| 63 | } |
| 64 | w.WriteHeader(http.StatusOK) |
| 65 | case "Notification": |
| 66 | ev, err := ParseSESNotification([]byte(m.Message)) |
| 67 | if err != nil { |
| 68 | // Malformed/unactionable SES payload — ack so SES stops retrying |
| 69 | // (retrying won't fix bad data); log for visibility. |
| 70 | log.Printf("[delivery] parse SES notification: %v", err) |
| 71 | w.WriteHeader(http.StatusOK) |
| 72 | return |
| 73 | } |
| 74 | if err := c.Process(r.Context(), ev); err != nil { |
| 75 | log.Printf("[delivery] process %s: %v", ev.Kind, err) |
| 76 | http.Error(w, "processing error", http.StatusInternalServerError) // SES retries |
| 77 | return |
| 78 | } |
| 79 | w.WriteHeader(http.StatusOK) |
| 80 | default: // UnsubscribeConfirmation, etc. |
| 81 | w.WriteHeader(http.StatusOK) |
| 82 | } |