From cc151d014e2b560ccb07c96d4d5adfb465991b1d Mon Sep 17 00:00:00 2001 From: Joshua Blum Date: Thu, 2 Jul 2026 12:25:04 -0400 Subject: [PATCH] reject RO param to mysql conn --- base/options.go | 48 ++++++++++++++++++++++++++++----------------- base/server.go | 9 +++++---- elastiwatch/main.go | 4 ++-- gcalbot/main.go | 4 ++-- githubbot/main.go | 4 ++-- gitlabbot/main.go | 4 ++-- macrobot/main.go | 4 ++-- meetbot/main.go | 4 ++-- pollbot/main.go | 4 ++-- triviabot/main.go | 4 ++-- webhookbot/main.go | 4 ++-- zoombot/main.go | 4 ++-- 12 files changed, 55 insertions(+), 42 deletions(-) diff --git a/base/options.go b/base/options.go index b6a50383..2842d054 100644 --- a/base/options.go +++ b/base/options.go @@ -8,7 +8,6 @@ import ( "fmt" "os" "os/exec" - "strings" "github.com/go-sql-driver/mysql" "github.com/keybase/go-keybase-chat-bot/kbchat" @@ -32,9 +31,9 @@ type Options struct { Announcement string // Conversation name or ID to report bot errors to ErrReportConv string - // Database Source Name - DSN string - MultiDSN string + // Parsed database configs + DSN *mysql.Config + MultiDSN *mysql.Config // Allow the bot to read it's own messages (default: false) ReadSelf bool AWSOpts *AWSOptions @@ -50,15 +49,15 @@ func NewOptions() *Options { } func (o *Options) Parse(fs *flag.FlagSet, argv []string) error { - var mysqlTLSCA string + var dsnStr, multiDSNStr, mysqlTLSCA string fs.StringVar(&o.KeybaseLocation, "keybase", "keybase", "keybase command") fs.StringVar(&o.Home, "home", "", "Home directory") fs.StringVar(&o.Announcement, "announcement", os.Getenv("BOT_ANNOUNCEMENT"), "Conversation name or ID to announce we are running") fs.StringVar(&o.ErrReportConv, "err-report-conv", os.Getenv("BOT_ERR_REPORT_CONV"), "Conversation name or ID to report errors to") - fs.StringVar(&o.DSN, "dsn", os.Getenv("BOT_DSN"), "Bot database DSN") - fs.StringVar(&o.MultiDSN, "multi-dsn", os.Getenv("BOT_MULTI_DSN"), "Bot multi coordination database DSN") + fs.StringVar(&dsnStr, "dsn", os.Getenv("BOT_DSN"), "Bot database DSN") + fs.StringVar(&multiDSNStr, "multi-dsn", os.Getenv("BOT_MULTI_DSN"), "Bot multi coordination database DSN") fs.StringVar(&mysqlTLSCA, "mysql-tls-ca", os.Getenv("BOT_MYSQL_TLS_CA"), "Bot MySQL TLS CA") fs.BoolVar(&o.ReadSelf, "read-self", false, "Allow the bot to read it's own messages") fs.StringVar(&o.DebugTag, "debug-tag", os.Getenv("BOT_DEBUG_TAG"), "Bot debug tag") @@ -74,20 +73,31 @@ func (o *Options) Parse(fs *flag.FlagSet, argv []string) error { return err } - // configure TLS CAs if specified + var err error + if dsnStr != "" { + if o.DSN, err = mysql.ParseDSN(dsnStr); err != nil { + return fmt.Errorf("error parsing DSN: %s", err) + } + } + if multiDSNStr != "" { + if o.MultiDSN, err = mysql.ParseDSN(multiDSNStr); err != nil { + return fmt.Errorf("error parsing MultiDSN: %s", err) + } + } + if mysqlTLSCA != "" { if err := o.configureMySQLTLS(mysqlTLSCA); err != nil { return err } } - return nil -} -func (o *Options) addTLSToDSN(dsn string, tlsConfigName string) string { - if strings.Contains(dsn, "?") { - return dsn + "&tls=" + tlsConfigName + if o.DSN != nil { + o.DSN.RejectReadOnly = true } - return dsn + "?tls=" + tlsConfigName + if o.MultiDSN != nil { + o.MultiDSN.RejectReadOnly = true + } + return nil } func (o *Options) configureMySQLTLS(mysqlTLSCA string) error { @@ -103,10 +113,12 @@ func (o *Options) configureMySQLTLS(mysqlTLSCA string) error { if err := mysql.RegisterTLSConfig(configName, tlsConfig); err != nil { return fmt.Errorf("error registering MySQL TLS config: %s", err) } - - o.DSN = o.addTLSToDSN(o.DSN, configName) - o.MultiDSN = o.addTLSToDSN(o.MultiDSN, configName) - + if o.DSN != nil { + o.DSN.TLSConfig = configName + } + if o.MultiDSN != nil { + o.MultiDSN.TLSConfig = configName + } return nil } diff --git a/base/server.go b/base/server.go index 5ae56eab..3c71a628 100644 --- a/base/server.go +++ b/base/server.go @@ -14,6 +14,7 @@ import ( "syscall" "time" + "github.com/go-sql-driver/mysql" "github.com/keybase/go-keybase-chat-bot/kbchat" "github.com/keybase/go-keybase-chat-bot/kbchat/types/chat1" "golang.org/x/sync/errgroup" @@ -39,7 +40,7 @@ type Server struct { awsOpts *AWSOptions kbc *kbchat.API botAdmins []string - multiDBDSN string + multiDBDSN *mysql.Config multi *multi readSelf bool @@ -47,7 +48,7 @@ type Server struct { } func NewServer( - name, announcement string, awsOpts *AWSOptions, multiDBDSN string, readSelf bool, + name, announcement string, awsOpts *AWSOptions, multiDBDSN *mysql.Config, readSelf bool, runOptions kbchat.RunOptions, ) *Server { return &Server{ @@ -124,8 +125,8 @@ func (s *Server) Start(errReportConv string) (kbc *kbchat.API, err error) { } debugConfig := NewChatDebugOutputConfig(s.kbc, errReportConv) s.DebugOutput = NewDebugOutput("Server", debugConfig) - if s.multiDBDSN != "" { - db, err := sql.Open("mysql", s.multiDBDSN) + if s.multiDBDSN != nil { + db, err := sql.Open("mysql", s.multiDBDSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to Multi MySQL: %s", err) return nil, err diff --git a/elastiwatch/main.go b/elastiwatch/main.go index 6c3d8ae6..04d47d73 100644 --- a/elastiwatch/main.go +++ b/elastiwatch/main.go @@ -112,7 +112,7 @@ func (s *BotServer) Go() (err error) { if s.kbc, err = s.Start(s.opts.ErrReportConv); err != nil { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -187,7 +187,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/gcalbot/main.go b/gcalbot/main.go index a65a8c7d..fc8dee3f 100644 --- a/gcalbot/main.go +++ b/gcalbot/main.go @@ -204,7 +204,7 @@ func (s *BotServer) Go() (err error) { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -254,7 +254,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/githubbot/main.go b/githubbot/main.go index 5c81295b..1bf1d0f6 100644 --- a/githubbot/main.go +++ b/githubbot/main.go @@ -208,7 +208,7 @@ func (s *BotServer) Go() (err error) { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -285,7 +285,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/gitlabbot/main.go b/gitlabbot/main.go index 10096be2..3c4a01e1 100644 --- a/gitlabbot/main.go +++ b/gitlabbot/main.go @@ -133,7 +133,7 @@ func (s *BotServer) Go() (err error) { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -184,7 +184,7 @@ func mainInner() int { if err := opts.Parse(fs, os.Args); err != nil { return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/macrobot/main.go b/macrobot/main.go index 3911fdf9..7775658b 100644 --- a/macrobot/main.go +++ b/macrobot/main.go @@ -89,7 +89,7 @@ func (s *BotServer) Go() (err error) { if s.kbc, err = s.Start(s.opts.ErrReportConv); err != nil { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -134,7 +134,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/meetbot/main.go b/meetbot/main.go index f2df49dc..adbcd6e0 100644 --- a/meetbot/main.go +++ b/meetbot/main.go @@ -100,7 +100,7 @@ func (s *BotServer) Go() (err error) { return fmt.Errorf("failed to get config %v", err) } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -145,7 +145,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/pollbot/main.go b/pollbot/main.go index 8323bfd8..30a78240 100644 --- a/pollbot/main.go +++ b/pollbot/main.go @@ -99,7 +99,7 @@ func (s *BotServer) Go() (err error) { if s.kbc, err = s.Start(s.opts.ErrReportConv); err != nil { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -151,7 +151,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/triviabot/main.go b/triviabot/main.go index 7fe42a62..f2382e35 100644 --- a/triviabot/main.go +++ b/triviabot/main.go @@ -68,7 +68,7 @@ func (s *BotServer) Go() (err error) { if s.kbc, err = s.Start(s.opts.ErrReportConv); err != nil { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -111,7 +111,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/webhookbot/main.go b/webhookbot/main.go index fcbf7b50..400500ab 100644 --- a/webhookbot/main.go +++ b/webhookbot/main.go @@ -102,7 +102,7 @@ func (s *BotServer) Go() (err error) { if s.kbc, err = s.Start(s.opts.ErrReportConv); err != nil { return err } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -149,7 +149,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 } diff --git a/zoombot/main.go b/zoombot/main.go index b6dbdc43..7bd50a99 100644 --- a/zoombot/main.go +++ b/zoombot/main.go @@ -130,7 +130,7 @@ func (s *BotServer) Go() (err error) { // https://devforum.zoom.us/t/invalid-scope-errors/52654/14 } - sdb, err := sql.Open("mysql", s.opts.DSN) + sdb, err := sql.Open("mysql", s.opts.DSN.FormatDSN()) if err != nil { s.Errorf("failed to connect to MySQL: %s", err) return err @@ -180,7 +180,7 @@ func mainInner() int { fmt.Printf("Unable to parse options: %v\n", err) return 3 } - if len(opts.DSN) == 0 { + if opts.DSN == nil { fmt.Printf("must specify a database DSN\n") return 3 }