Skip to content

Commit 1bac2c7

Browse files
authored
Support all HTTP methods (#21)
1 parent 960d7ad commit 1bac2c7

6 files changed

Lines changed: 53 additions & 94 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## 0.11.2
4+
5+
- Key changes:
6+
- All HTTP methods are allowed now (previously, only POST/GET requests were supported due to technical reasons);
7+
- `VictoriaMetrics/metricsql` bumped from `0.40.0` to `0.41.0`.
8+
39
## 0.11.1
410

511
- Key changes:

Taskfile.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ tasks:
44
default:
55
- task: test
66
- task: lint
7+
- task: tidy
78

89
test:
910
cmds:
@@ -13,3 +14,7 @@ tasks:
1314
lint:
1415
cmds:
1516
- golangci-lint run
17+
18+
tidy:
19+
cmds:
20+
- go mod tidy

cmd/lfgw/helpers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func (app *application) isNotAPIRequest(path string) bool {
9292
// isUnsafePath returns true if the requested path targets a potentially dangerous endpoint (admin or remote write).
9393
func (app *application) isUnsafePath(path string) bool {
9494
// TODO: move to regexp?
95+
// TODO: more unsafe paths?
9596
return strings.Contains(path, "/admin/tsdb") || strings.Contains(path, "/api/v1/write")
9697
}
9798

cmd/lfgw/middleware.go

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func (app *application) logMiddleware(next http.Handler) http.Handler {
4141
return
4242
}
4343

44-
// Once r.ParseForm() is called, we need to update ContentLength, otherwise the request will fail
44+
// Once r.ParseForm() is called, we need to update ContentLength, otherwise the request will fail. r.PostForm contains data for PATCH, POST, and PUT requests.
4545
postForm := r.PostForm.Encode()
4646
newBody := strings.NewReader(postForm)
4747
r.ContentLength = newBody.Size()
@@ -68,27 +68,16 @@ func (app *application) logMiddleware(next http.Handler) http.Handler {
6868
})
6969
}
7070

71-
// prohibitedMethods forbids all the methods aside from "GET" and "POST".
72-
func (app *application) prohibitedMethodsMiddleware(next http.Handler) http.Handler {
73-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
74-
if r.Method != http.MethodGet && r.Method != http.MethodPost {
75-
w.Header().Set("Allow", fmt.Sprintf("%s, %s", http.MethodGet, http.MethodPost))
76-
app.clientError(w, http.StatusMethodNotAllowed)
77-
return
78-
}
79-
next.ServeHTTP(w, r)
80-
})
81-
}
82-
83-
// prohibitedPaths forbids access to some destinations that should not be proxied.
84-
func (app *application) prohibitedPathsMiddleware(next http.Handler) http.Handler {
71+
// safeModeMiddleware forbids access to some API endpoints if safe mode is enabled
72+
func (app *application) safeModeMiddleware(next http.Handler) http.Handler {
8573
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
8674
if app.SafeMode && app.isUnsafePath(r.URL.Path) {
8775
hlog.FromRequest(r).Error().Caller().
8876
Msgf("Blocked a request to %s", r.URL.Path)
8977
app.clientError(w, http.StatusForbidden)
9078
return
9179
}
80+
9281
next.ServeHTTP(w, r)
9382
})
9483
}
@@ -101,6 +90,7 @@ func (app *application) proxyHeadersMiddleware(next http.Handler) http.Handler {
10190
r.Header.Set("X-Forwarded-Proto", r.URL.Scheme)
10291
r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
10392
}
93+
10494
next.ServeHTTP(w, r)
10595
})
10696
}
@@ -205,21 +195,19 @@ func (app *application) rewriteRequestMiddleware(next http.Handler) http.Handler
205195
r.URL.RawQuery = newGetParams
206196
app.enrichDebugLogContext(r, "new_get_params", app.unescapedURLQuery(newGetParams))
207197

208-
// Adjust POST params
209-
// Partially inspired by https://github.com/bitsbeats/prometheus-acls/blob/master/internal/labeler/middleware.go
210-
if r.Method == http.MethodPost {
211-
newPostParams, err := app.prepareQueryParams(&r.PostForm, acl)
212-
if err != nil {
213-
hlog.FromRequest(r).Error().Caller().
214-
Err(err).Msg("")
215-
app.clientError(w, http.StatusBadRequest)
216-
return
217-
}
218-
newBody := strings.NewReader(newPostParams)
219-
r.ContentLength = newBody.Size()
220-
r.Body = io.NopCloser(newBody)
221-
app.enrichDebugLogContext(r, "new_post_params", app.unescapedURLQuery(newPostParams))
198+
// For PATCH, POST, and PUT requests
199+
newPostParams, err := app.prepareQueryParams(&r.PostForm, acl)
200+
if err != nil {
201+
hlog.FromRequest(r).Error().Caller().
202+
Err(err).Msg("")
203+
app.clientError(w, http.StatusBadRequest)
204+
return
222205
}
206+
newBody := strings.NewReader(newPostParams)
207+
r.ContentLength = newBody.Size()
208+
r.Body = io.NopCloser(newBody)
209+
// TODO: the field name is slightly misleading, should, probably, be renamed
210+
app.enrichDebugLogContext(r, "new_post_params", app.unescapedURLQuery(newPostParams))
223211

224212
next.ServeHTTP(w, r)
225213
})

cmd/lfgw/middleware_test.go

Lines changed: 21 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,96 +6,56 @@ import (
66
"testing"
77

88
"github.com/rs/zerolog"
9+
"github.com/stretchr/testify/assert"
910
)
1011

11-
func TestProhibitedMethodsMiddleware(t *testing.T) {
12-
logger := zerolog.New(nil)
13-
app := &application{
14-
logger: &logger,
15-
}
16-
17-
tests := []struct {
18-
name string
19-
method string
20-
want int
21-
}{
22-
{
23-
name: "GET",
24-
method: http.MethodGet,
25-
want: http.StatusOK,
26-
},
27-
{
28-
name: "POST",
29-
method: http.MethodGet,
30-
want: http.StatusOK,
31-
},
32-
{
33-
name: "PATCH",
34-
method: http.MethodPatch,
35-
want: http.StatusMethodNotAllowed,
36-
}}
37-
38-
for _, tt := range tests {
39-
t.Run(tt.name, func(t *testing.T) {
40-
rr := httptest.NewRecorder()
41-
r, err := http.NewRequest(tt.method, "/", nil)
42-
if err != nil {
43-
t.Fatal(err)
44-
}
45-
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
46-
_, _ = w.Write([]byte("OK"))
47-
})
48-
app.prohibitedMethodsMiddleware(next).ServeHTTP(rr, r)
49-
rs := rr.Result()
50-
51-
if rs.StatusCode != tt.want {
52-
t.Errorf("want %d; got %d", tt.want, rs.StatusCode)
53-
}
54-
defer rs.Body.Close()
55-
})
56-
}
57-
}
58-
59-
func TestProhibitedPathsMiddleware(t *testing.T) {
12+
func TestSafeModeMiddleware(t *testing.T) {
6013
tests := []struct {
6114
name string
6215
path string
16+
method string
6317
safeMode bool
6418
want int
6519
}{
6620
{
67-
name: "safe mode on tsdb",
21+
name: "tsdb (safe mode on)",
6822
path: "/admin/tsdb",
23+
method: http.MethodGet,
6924
safeMode: true,
7025
want: http.StatusForbidden,
7126
},
7227
{
73-
name: "safe mode off tsdb",
28+
name: "tsdb (safe mode off)",
7429
path: "/admin/tsdb",
30+
method: http.MethodGet,
7531
safeMode: false,
7632
want: http.StatusOK,
7733
},
7834
{
79-
name: "safe mode on api write",
35+
name: "api write (safe mode on)",
8036
path: "/api/v1/write",
37+
method: http.MethodGet,
8138
safeMode: true,
8239
want: http.StatusForbidden,
8340
},
8441
{
85-
name: "safe mode off api write",
42+
name: "api write (safe mode off)",
8643
path: "/api/v1/write",
44+
method: http.MethodGet,
8745
safeMode: false,
8846
want: http.StatusOK,
8947
},
9048
{
91-
name: "safe mode on random path",
49+
name: "random path (safe mode on)",
9250
path: "/api/v1/test",
93-
safeMode: false,
51+
method: http.MethodGet,
52+
safeMode: true,
9453
want: http.StatusOK,
9554
},
9655
{
97-
name: "safe mode off random path",
56+
name: "random path (safe mode off)",
9857
path: "/api/v1/test",
58+
method: http.MethodGet,
9959
safeMode: false,
10060
want: http.StatusOK,
10161
},
@@ -110,19 +70,19 @@ func TestProhibitedPathsMiddleware(t *testing.T) {
11070
}
11171

11272
rr := httptest.NewRecorder()
113-
r, err := http.NewRequest(http.MethodGet, tt.path, nil)
73+
r, err := http.NewRequest(tt.method, tt.path, nil)
11474
if err != nil {
11575
t.Fatal(err)
11676
}
11777
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
11878
_, _ = w.Write([]byte("OK"))
11979
})
120-
app.prohibitedPathsMiddleware(next).ServeHTTP(rr, r)
80+
app.safeModeMiddleware(next).ServeHTTP(rr, r)
12181
rs := rr.Result()
82+
got := rs.StatusCode
83+
84+
assert.Equal(t, tt.want, got)
12285

123-
if rs.StatusCode != tt.want {
124-
t.Errorf("want %d; got %d", tt.want, rs.StatusCode)
125-
}
12686
defer rs.Body.Close()
12787
})
12888
}

cmd/lfgw/routes.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ func (app *application) routes() *mux.Router {
1111
r.Use(app.nonProxiedEndpointsMiddleware)
1212
r.Use(hlog.NewHandler(*app.logger))
1313
r.Use(app.logMiddleware)
14-
r.Use(app.prohibitedMethodsMiddleware)
15-
r.Use(app.proxyHeadersMiddleware)
1614
r.Use(app.oidcModeMiddleware)
17-
// Better to keep it here to see user email in logs
18-
r.Use(app.prohibitedPathsMiddleware)
15+
// Better to keep it here to see user email in logs (for unsafe paths)
16+
r.Use(app.safeModeMiddleware)
17+
r.Use(app.proxyHeadersMiddleware)
1918
r.Use(app.rewriteRequestMiddleware)
2019
r.PathPrefix("/").Handler(app.proxy)
2120
return r

0 commit comments

Comments
 (0)