diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2025-07-03 15:22:09 +0200 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2025-07-08 13:37:33 +0000 |
| commit | 4ae963f81abf2889628b7d47dae833626fd0664a (patch) | |
| tree | cc21403feb7c6b29f10db48bbfea2bf70de3e8f9 /syz-cluster | |
| parent | a7f206f0e4b264de8fb8b059d10c96beb8c6a3a4 (diff) | |
syz-cluster: support dashapi sender
Refactor the configuration to support both SMTP and dashapi-based email
sending functionality.
Diffstat (limited to 'syz-cluster')
| -rw-r--r-- | syz-cluster/email-reporter/dashapi_sender.go | 37 | ||||
| -rw-r--r-- | syz-cluster/email-reporter/handler.go | 31 | ||||
| -rw-r--r-- | syz-cluster/email-reporter/handler_test.go | 1 | ||||
| -rw-r--r-- | syz-cluster/email-reporter/main.go | 49 | ||||
| -rw-r--r-- | syz-cluster/email-reporter/smtp_sender.go (renamed from syz-cluster/email-reporter/sender.go) | 26 | ||||
| -rw-r--r-- | syz-cluster/email-reporter/smtp_sender_test.go (renamed from syz-cluster/email-reporter/sender_test.go) | 0 | ||||
| -rw-r--r-- | syz-cluster/overlays/minikube/global-config.yaml | 4 | ||||
| -rw-r--r-- | syz-cluster/pkg/app/config.go | 70 |
8 files changed, 170 insertions, 48 deletions
diff --git a/syz-cluster/email-reporter/dashapi_sender.go b/syz-cluster/email-reporter/dashapi_sender.go new file mode 100644 index 000000000..e342084e1 --- /dev/null +++ b/syz-cluster/email-reporter/dashapi_sender.go @@ -0,0 +1,37 @@ +// Copyright 2025 syzkaller project authors. All rights reserved. +// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +package main + +import ( + "context" + + "github.com/google/syzkaller/dashboard/dashapi" + "github.com/google/syzkaller/pkg/email" + "github.com/google/syzkaller/syz-cluster/pkg/app" +) + +func makeDashapiSender(cfg *app.EmailConfig) (SendEmailCb, error) { + dash, err := dashapi.New(cfg.Dashapi.Client, cfg.Dashapi.Addr, "") + if err != nil { + return nil, err + } + return func(_ context.Context, item *EmailToSend) (string, error) { + sender := cfg.Dashapi.From + if item.BugID != "" { + var err error + sender, err = email.AddAddrContext(sender, cfg.Dashapi.ContextPrefix+item.BugID) + if err != nil { + return "", err + } + } + return "", dash.SendEmail(&dashapi.SendEmailReq{ + Sender: sender, + To: item.To, + Cc: item.Cc, + Subject: cfg.SubjectPrefix + item.Subject, + InReplyTo: item.InReplyTo, + Body: string(item.Body), + }) + }, nil +} diff --git a/syz-cluster/email-reporter/handler.go b/syz-cluster/email-reporter/handler.go index 8b0ff6a6c..0f2d33a19 100644 --- a/syz-cluster/email-reporter/handler.go +++ b/syz-cluster/email-reporter/handler.go @@ -15,14 +15,6 @@ import ( "github.com/google/syzkaller/syz-cluster/pkg/report" ) -type EmailToSend struct { - To []string - Cc []string - Subject string - InReplyTo string - Body []byte -} - type SendEmailCb func(context.Context, *EmailToSend) (string, error) type Handler struct { @@ -93,16 +85,19 @@ func (h *Handler) report(ctx context.Context, rep *api.SessionReport) error { if err != nil { return fmt.Errorf("failed to send: %w", err) } - // Record MessageID so that we could later trace user replies back to it. - _, err = h.apiClient.RecordReply(ctx, &api.RecordReplyReq{ - // TODO: for Lore emails, set Link = lore.Link(msgID). - MessageID: msgID, - Time: time.Now(), - ReportID: rep.ID, - Reporter: h.reporter, - }) - if err != nil { - return fmt.Errorf("failed to update: %w", err) + // Senders may not always know the MessageID of the newly sent messages (that's the case of dashapi). + if msgID != "" { + // Record MessageID so that we could later trace user replies back to it. + _, err = h.apiClient.RecordReply(ctx, &api.RecordReplyReq{ + // TODO: for Lore emails, set Link = lore.Link(msgID). + MessageID: msgID, + Time: time.Now(), + ReportID: rep.ID, + Reporter: h.reporter, + }) + if err != nil { + return fmt.Errorf("failed to record the reply: %w", err) + } } return nil } diff --git a/syz-cluster/email-reporter/handler_test.go b/syz-cluster/email-reporter/handler_test.go index c2eabcaca..b01cae14b 100644 --- a/syz-cluster/email-reporter/handler_test.go +++ b/syz-cluster/email-reporter/handler_test.go @@ -163,7 +163,6 @@ func (f *fakeSender) email() *EmailToSend { var testEmailConfig = &app.EmailConfig{ Name: "name", DocsLink: "docs", - Sender: "a@b.com", ModerationList: "moderation@list.com", ArchiveList: "archive@list.com", } diff --git a/syz-cluster/email-reporter/main.go b/syz-cluster/email-reporter/main.go index 999d70cfc..da1ebb755 100644 --- a/syz-cluster/email-reporter/main.go +++ b/syz-cluster/email-reporter/main.go @@ -7,6 +7,7 @@ package main import ( "context" + "fmt" "log" "time" @@ -36,16 +37,16 @@ func main() { if cfg.EmailReporting == nil { app.Fatalf("reporting is not configured: %v", err) } - sender, err := newSender(ctx, cfg.EmailReporting) + sender, err := makeSender(ctx, cfg.EmailReporting) if err != nil { - app.Fatalf("failed to create an SMTP sender: %s", err) + app.Fatalf("failed to create a sender: %s", err) } reporterClient := app.DefaultReporterClient() handler := &Handler{ reporter: api.LKMLReporter, apiClient: reporterClient, emailConfig: cfg.EmailReporting, - sender: sender.Send, + sender: sender, } msgCh := make(chan *email.Email, 16) eg, loopCtx := errgroup.WithContext(ctx) @@ -78,3 +79,45 @@ func main() { }) eg.Wait() } + +func makeSender(ctx context.Context, cfg *app.EmailConfig) (SendEmailCb, error) { + if cfg.Sender == app.SenderSMTP { + sender, err := newSMTPSender(ctx, cfg) + if err != nil { + return nil, err + } + return sender.Send, nil + } else if cfg.Sender == app.SenderDashapi { + return makeDashapiSender(cfg) + } + return nil, fmt.Errorf("unsupported sender type: %q", cfg.Sender) +} + +type EmailToSend struct { + To []string + Cc []string + Subject string + InReplyTo string + Body []byte + BugID string // In case it's to be included into Sender. +} + +func (item *EmailToSend) recipients() []string { + var ret []string + ret = append(ret, item.To...) + ret = append(ret, item.Cc...) + return unique(ret) +} + +func unique(list []string) []string { + var ret []string + seen := map[string]struct{}{} + for _, str := range list { + if _, ok := seen[str]; ok { + continue + } + seen[str] = struct{}{} + ret = append(ret, str) + } + return ret +} diff --git a/syz-cluster/email-reporter/sender.go b/syz-cluster/email-reporter/smtp_sender.go index e19066ed2..76bc4b1c9 100644 --- a/syz-cluster/email-reporter/sender.go +++ b/syz-cluster/email-reporter/smtp_sender.go @@ -21,7 +21,7 @@ type smtpSender struct { projectName string // needed for querying credentials } -func newSender(ctx context.Context, cfg *app.EmailConfig) (*smtpSender, error) { +func newSMTPSender(ctx context.Context, cfg *app.EmailConfig) (*smtpSender, error) { project, err := gce.ProjectName(ctx) if err != nil { return nil, fmt.Errorf("failed to query project name: %w", err) @@ -42,33 +42,13 @@ func (sender *smtpSender) Send(ctx context.Context, item *EmailToSend) (string, msg := rawEmail(sender.cfg, item, msgID) auth := smtp.PlainAuth("", creds.host, creds.password, creds.host) smtpAddr := fmt.Sprintf("%s:%d", creds.host, creds.port) - return msgID, smtp.SendMail(smtpAddr, auth, sender.cfg.Sender, item.recipients(), msg) -} - -func (item *EmailToSend) recipients() []string { - var ret []string - ret = append(ret, item.To...) - ret = append(ret, item.Cc...) - return unique(ret) -} - -func unique(list []string) []string { - var ret []string - seen := map[string]struct{}{} - for _, str := range list { - if _, ok := seen[str]; ok { - continue - } - seen[str] = struct{}{} - ret = append(ret, str) - } - return ret + return msgID, smtp.SendMail(smtpAddr, auth, sender.cfg.SMTP.From, item.recipients(), msg) } func rawEmail(cfg *app.EmailConfig, item *EmailToSend, id string) []byte { var msg bytes.Buffer - fmt.Fprintf(&msg, "From: %s <%s>\r\n", cfg.Name, cfg.Sender) + fmt.Fprintf(&msg, "From: %s <%s>\r\n", cfg.Name, cfg.SMTP.From) fmt.Fprintf(&msg, "To: %s\r\n", strings.Join(item.To, ", ")) if len(item.Cc) > 0 { fmt.Fprintf(&msg, "Cc: %s\r\n", strings.Join(item.Cc, ", ")) diff --git a/syz-cluster/email-reporter/sender_test.go b/syz-cluster/email-reporter/smtp_sender_test.go index 184753a18..184753a18 100644 --- a/syz-cluster/email-reporter/sender_test.go +++ b/syz-cluster/email-reporter/smtp_sender_test.go diff --git a/syz-cluster/overlays/minikube/global-config.yaml b/syz-cluster/overlays/minikube/global-config.yaml index ff2f5051f..c0cfa0078 100644 --- a/syz-cluster/overlays/minikube/global-config.yaml +++ b/syz-cluster/overlays/minikube/global-config.yaml @@ -14,7 +14,9 @@ data: emailReporting: name: test-name docs: http://docs/ - sender: sender@email.com + sender: smtp supportEmail: name@email.com archiveList: archive@list.com moderationList: moderation@list.com + smtpConfig: + from: sender@email.com diff --git a/syz-cluster/pkg/app/config.go b/syz-cluster/pkg/app/config.go index 66a30bd23..92a3535e0 100644 --- a/syz-cluster/pkg/app/config.go +++ b/syz-cluster/pkg/app/config.go @@ -23,6 +23,11 @@ type AppConfig struct { EmailReporting *EmailConfig `yaml:"emailReporting"` } +const ( + SenderSMTP = "smtp" + SenderDashapi = "dashapi" +) + type EmailConfig struct { // The public name of the system. Name string `yaml:"name"` @@ -30,14 +35,37 @@ type EmailConfig struct { DocsLink string `yaml:"docs"` // Contact email. SupportEmail string `yaml:"supportEmail"` - // The email from which to send the reports. + // The means to send the emails ("smtp", "dashapi"). Sender string `yaml:"sender"` + // Will be used if Sender is "smtp". + SMTP *SMTPConfig `yaml:"smtpConfig"` + // Will be used if Sender is "dashapi". + Dashapi *DashapiConfig `yaml:"dashapiConfig"` // Moderation requests will be sent there. ModerationList string `yaml:"moderationList"` // The list we listen on. ArchiveList string `yaml:"archiveList"` // Lore git archive to poll for incoming messages. LoreArchiveURL string `yaml:"loreArchiveURL"` + // The prefix which will be added to all reports' titles. + SubjectPrefix string `yaml:"subjectPrefix"` +} + +type SMTPConfig struct { + // The email from which to send the reports. + From string `yaml:"from"` +} + +type DashapiConfig struct { + // The URI at which the dashboard is accessible. + Addr string `yaml:"addr"` + // Client name to be used for authorization. + // OAuth will be used instead of a key. + Client string `yaml:"client"` + // The email from which to send the reports. + From string `yaml:"from"` + // The emails will be sent from "name+" + contextPrefix + ID + "@domain". + ContextPrefix string `yaml:"contextPrefix"` } // The project configuration is expected to be mounted at /config/config.yaml. @@ -92,7 +120,6 @@ func (c EmailConfig) Validate() error { for _, err := range []error{ ensureNonEmpty("name", c.Name), ensureEmail("supportEmail", c.SupportEmail), - ensureEmail("sender", c.Sender), ensureEmail("moderationList", c.ModerationList), ensureEmail("archiveList", c.ArchiveList), } { @@ -100,6 +127,45 @@ func (c EmailConfig) Validate() error { return err } } + if c.SMTP != nil { + if err := c.SMTP.Validate(); err != nil { + return err + } + } + if c.Dashapi != nil { + if err := c.Dashapi.Validate(); err != nil { + return err + } + } + switch c.Sender { + case SenderSMTP: + if c.SMTP == nil { + return fmt.Errorf("sender is %q, but smtpConfig is empty", SenderSMTP) + } + case SenderDashapi: + if c.Dashapi == nil { + return fmt.Errorf("sender is %q, but dashapiConfig is empty", SenderDashapi) + } + default: + return fmt.Errorf("invalid sender value, must be %q or %q", SenderSMTP, SenderDashapi) + } + return nil +} + +func (c SMTPConfig) Validate() error { + return ensureEmail("from", c.From) +} + +func (c DashapiConfig) Validate() error { + for _, err := range []error{ + ensureNonEmpty("addr", c.Addr), + ensureNonEmpty("client", c.Client), + ensureEmail("from", c.From), + } { + if err != nil { + return err + } + } return nil } |
