aboutsummaryrefslogtreecommitdiffstats
path: root/syz-cluster
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2025-07-03 15:22:09 +0200
committerAleksandr Nogikh <nogikh@google.com>2025-07-08 13:37:33 +0000
commit4ae963f81abf2889628b7d47dae833626fd0664a (patch)
treecc21403feb7c6b29f10db48bbfea2bf70de3e8f9 /syz-cluster
parenta7f206f0e4b264de8fb8b059d10c96beb8c6a3a4 (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.go37
-rw-r--r--syz-cluster/email-reporter/handler.go31
-rw-r--r--syz-cluster/email-reporter/handler_test.go1
-rw-r--r--syz-cluster/email-reporter/main.go49
-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.yaml4
-rw-r--r--syz-cluster/pkg/app/config.go70
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
}