MM-18636: limit configuration writes to 4Mb (#12266)
* MM-18636: limit configuration writes to 4Mb By default, MySQL silently truncates writes that exceed the column type in question. Change the column type from `TEXT` to `MEDIUMTEXT` to allow writes to the `Configurations` and `ConfigurationFiles` table to exceed 65535 bytes. This is a backwards compatible migration, but does require a rewrite of the table. However, MySQL is further constrained by the default `max_allowed_packet` value of 4Mb, so limit writes accordingly. Fixes: https://mattermost.atlassian.net/browse/MM-18636 * simplify unit tests * fix import
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
975055a7e7
Коммит
340287890a
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
@@ -21,6 +22,14 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
)
|
||||
|
||||
// MaxWriteLength defines the maximum length accepted for write to the Configurations or
|
||||
// ConfigurationFiles table.
|
||||
//
|
||||
// It is imposed by MySQL's default max_allowed_packet value of 4Mb.
|
||||
const MaxWriteLength = 4 * 1024 * 1024
|
||||
|
||||
var tcpStripper = regexp.MustCompile(`@tcp\((.*)\)`)
|
||||
|
||||
// DatabaseStore is a config store backed by a database.
|
||||
type DatabaseStore struct {
|
||||
commonStore
|
||||
@@ -61,6 +70,8 @@ func NewDatabaseStore(dsn string) (ds *DatabaseStore, err error) {
|
||||
}
|
||||
|
||||
// initializeConfigurationsTable ensures the requisite tables in place to form the backing store.
|
||||
//
|
||||
// Uses MEDIUMTEXT on MySQL, and TEXT on sane databases.
|
||||
func initializeConfigurationsTable(db *sqlx.DB) error {
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS Configurations (
|
||||
@@ -86,6 +97,20 @@ func initializeConfigurationsTable(db *sqlx.DB) error {
|
||||
return errors.Wrap(err, "failed to create ConfigurationFiles table")
|
||||
}
|
||||
|
||||
// Change from TEXT (65535 limit) to MEDIUM TEXT (16777215) on MySQL. This is a
|
||||
// backwards-compatible migration for any existing schema.
|
||||
if db.DriverName() == "mysql" {
|
||||
_, err = db.Exec(`ALTER TABLE Configurations MODIFY Value MEDIUMTEXT`)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to alter Configurations table")
|
||||
}
|
||||
|
||||
_, err = db.Exec(`ALTER TABLE ConfigurationFiles MODIFY Data MEDIUMTEXT`)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to alter ConfigurationFiles table")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -126,6 +151,15 @@ func (ds *DatabaseStore) Set(newCfg *model.Config) (*model.Config, error) {
|
||||
return ds.commonStore.set(newCfg, true, ds.commonStore.validate, ds.persist)
|
||||
}
|
||||
|
||||
// maxLength identifies the maximum length of a configuration or configuration file
|
||||
func (ds *DatabaseStore) checkLength(length int) error {
|
||||
if ds.db.DriverName() == "mysql" && length > MaxWriteLength {
|
||||
return errors.Errorf("value is too long: %d > %d bytes", length, MaxWriteLength)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// persist writes the configuration to the configured database.
|
||||
func (ds *DatabaseStore) persist(cfg *model.Config) error {
|
||||
b, err := marshalConfig(cfg)
|
||||
@@ -137,6 +171,11 @@ func (ds *DatabaseStore) persist(cfg *model.Config) error {
|
||||
value := string(b)
|
||||
createAt := model.GetMillis()
|
||||
|
||||
err = ds.checkLength(len(value))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshalled configuration failed length check")
|
||||
}
|
||||
|
||||
tx, err := ds.db.Beginx()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to begin transaction")
|
||||
@@ -232,6 +271,11 @@ func (ds *DatabaseStore) GetFile(name string) ([]byte, error) {
|
||||
|
||||
// SetFile sets or replaces the contents of a configuration file.
|
||||
func (ds *DatabaseStore) SetFile(name string, data []byte) error {
|
||||
err := ds.checkLength(len(data))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "file data failed length check")
|
||||
}
|
||||
|
||||
params := map[string]interface{}{
|
||||
"name": name,
|
||||
"data": data,
|
||||
|
||||
Ссылка в новой задаче
Block a user