Skip to content

Commit

Permalink
fix: verify precision parameter in write requests
Browse files Browse the repository at this point in the history
This change updates the HTTP endpoints that service v1 and v2 writes to
verify the values passed in the precision parameter.
  • Loading branch information
dgnorton committed Apr 7, 2020
1 parent 1c4986b commit 275b02e
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ v1.8.0 [unreleased]
- [#16762](https://github.com/influxdata/influxdb/pull/16762): Fix bugs in -compact-series-file.
- [#16944](https://github.com/influxdata/influxdb/pull/16944): Update to Go 1.13.8 and Go modules.
- [#17032](https://github.com/influxdata/influxdb/pull/17032): Fix a SIGSEGV when accessing tsi active log.
- [#17656](https://github.com/influxdata/influxdb/pull/17656): Verify precision in write requests.

v1.7.0 [unreleased]
-------------------
Expand Down
33 changes: 29 additions & 4 deletions services/httpd/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,22 +816,47 @@ func bucket2dbrp(bucket string) (string, string, error) {
// of an "org" and "bucket" are mapped to v1 "database" and "retention
// policies".
func (h *Handler) serveWriteV2(w http.ResponseWriter, r *http.Request, user meta.User) {
precision := r.URL.Query().Get("precision")
switch precision {
case "ns":
precision = "n"
case "us":
precision = "u"
case "ms", "s", "":
// same as v1 so do nothing
default:
err := fmt.Sprintf("invalid precision %q (use ns, us, ms or s)", precision)
h.httpError(w, err, http.StatusBadRequest)
}

db, rp, err := bucket2dbrp(r.URL.Query().Get("bucket"))
if err != nil {
h.httpError(w, err.Error(), http.StatusNotFound)
return
}
h.serveWrite(db, rp, w, r, user)
h.serveWrite(db, rp, precision, w, r, user)
}

// serveWriteV1 handles v1 style writes.
func (h *Handler) serveWriteV1(w http.ResponseWriter, r *http.Request, user meta.User) {
h.serveWrite(r.URL.Query().Get("db"), r.URL.Query().Get("rp"), w, r, user)
precision := r.URL.Query().Get("precision")
switch precision {
case "", "n", "ns", "u", "ms", "s", "m", "h":
// it's valid
default:
err := fmt.Sprintf("invalid precision %q (use n, u, ms, s, m or h)", precision)
h.httpError(w, err, http.StatusBadRequest)
}

db := r.URL.Query().Get("db")
rp := r.URL.Query().Get("rp")

h.serveWrite(db, rp, precision, w, r, user)
}

// serveWrite receives incoming series data in line protocol format and writes
// it to the database.
func (h *Handler) serveWrite(database string, retentionPolicy string, w http.ResponseWriter, r *http.Request, user meta.User) {
func (h *Handler) serveWrite(database, retentionPolicy, precision string, w http.ResponseWriter, r *http.Request, user meta.User) {
atomic.AddInt64(&h.stats.WriteRequests, 1)
atomic.AddInt64(&h.stats.ActiveWriteRequests, 1)
defer func(start time.Time) {
Expand Down Expand Up @@ -910,7 +935,7 @@ func (h *Handler) serveWrite(database string, retentionPolicy string, w http.Res
h.Logger.Info("Write body received by handler", zap.ByteString("body", buf.Bytes()))
}

points, parseError := models.ParsePointsWithPrecision(buf.Bytes(), time.Now().UTC(), r.URL.Query().Get("precision"))
points, parseError := models.ParsePointsWithPrecision(buf.Bytes(), time.Now().UTC(), precision)
// Not points parsed correctly so return the error now
if parseError != nil && len(points) == 0 {
if parseError.Error() == "EOF" {
Expand Down
81 changes: 81 additions & 0 deletions services/httpd/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,87 @@ func TestHandler_Write_NegativeMaxBodySize(t *testing.T) {
}
}

// TestHandler_Write_V1_Precision verifies v1 writes validate precision.
func TestHandler_Write_V1_Precision(t *testing.T) {
h := NewHandler(false)
h.MetaClient.DatabaseFn = func(name string) *meta.DatabaseInfo {
return &meta.DatabaseInfo{}
}
h.PointsWriter.WritePointsFn = func(_, _ string, _ models.ConsistencyLevel, _ meta.User, _ []models.Point) error {
return nil
}

tests := []struct {
url string
status int
}{
// Successful requests.
{"/write?db=foo", http.StatusNoContent},
{"/write?db=foo&precision=n", http.StatusNoContent},
{"/write?db=foo&precision=u", http.StatusNoContent},
{"/write?db=foo&precision=ms", http.StatusNoContent},
{"/write?db=foo&precision=s", http.StatusNoContent},
{"/write?db=foo&precision=m", http.StatusNoContent},
{"/write?db=foo&precision=h", http.StatusNoContent},
// Invalid requests.
{"/write?db=foo&precision=us", http.StatusBadRequest},
}

runTest := func(url string, status int) {
b := bytes.NewReader([]byte(`foo n=1`))
w := httptest.NewRecorder()
h.ServeHTTP(w, MustNewRequest("POST", url, b))
if w.Code != status {
t.Fatalf("unexpected result for: \"%s\"\n\texp: %d, got: %d\n\t%s", url, status, w.Code, w.Body)
}
}

for _, t := range tests {
runTest(t.url, t.status)
}
}

// TestHandler_Write_V2_Precision verifies v2 writes validate precision.
func TestHandler_Write_V2_Precision(t *testing.T) {
h := NewHandler(false)
h.MetaClient.DatabaseFn = func(name string) *meta.DatabaseInfo {
return &meta.DatabaseInfo{}
}
h.PointsWriter.WritePointsFn = func(_, _ string, _ models.ConsistencyLevel, _ meta.User, _ []models.Point) error {
return nil
}

tests := []struct {
url string
status int
}{
// Successful requests.
{"/api/v2/write?org=bar&bucket=foo", http.StatusNoContent},
{"/api/v2/write?org=bar&bucket=foo&precision=ns", http.StatusNoContent},
{"/api/v2/write?org=bar&bucket=foo&precision=us", http.StatusNoContent},
{"/api/v2/write?org=bar&bucket=foo&precision=ms", http.StatusNoContent},
{"/api/v2/write?org=bar&bucket=foo&precision=s", http.StatusNoContent},
// Invalid requests.
{"/api/v2/write?org=bar&bucket=foo&precision=n", http.StatusBadRequest},
{"/api/v2/write?org=bar&bucket=foo&precision=u", http.StatusBadRequest},
{"/api/v2/write?org=bar&bucket=foo&precision=m", http.StatusBadRequest},
{"/api/v2/write?org=bar&bucket=foo&precision=h", http.StatusBadRequest},
}

runTest := func(url string, status int) {
b := bytes.NewReader([]byte(`foo n=1`))
w := httptest.NewRecorder()
h.ServeHTTP(w, MustNewRequest("POST", url, b))
if w.Code != status {
t.Fatalf("unexpected result for: \"%s\"\n\texp: %d, got: %d\n\t%s", url, status, w.Code, w.Body)
}
}

for _, t := range tests {
runTest(t.url, t.status)
}
}

// Ensure X-Forwarded-For header writes the correct log message.
func TestHandler_XForwardedFor(t *testing.T) {
var buf bytes.Buffer
Expand Down

0 comments on commit 275b02e

Please sign in to comment.