httpd/webdav: add a list of hosts allowed to send proxy headers

X-Forwarded-For, X-Real-IP and X-Forwarded-Proto headers will be ignored
for hosts not included in this list.

This is a backward incompatible change, before the proxy headers were
always used
This commit is contained in:
Nicola Murino
2021-05-11 06:54:06 +02:00
parent f1b998ce16
commit c8f7fc9bc9
25 changed files with 669 additions and 383 deletions

View File

@@ -424,7 +424,7 @@ func TestCreateTokenError(t *testing.T) {
}
req, _ := http.NewRequest(http.MethodGet, tokenPath, nil)
server.checkAddrAndSendToken(rr, req, admin)
server.generateAndSendToken(rr, req, admin)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
rr = httptest.NewRecorder()
@@ -565,22 +565,6 @@ func TestJWTTokenValidation(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, rr.Code)
}
func TestAdminAllowListConnAddr(t *testing.T) {
server := httpdServer{}
admin := dataprovider.Admin{
Filters: dataprovider.AdminFilters{
AllowList: []string{"192.168.1.0/24"},
},
}
rr := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, tokenPath, nil)
ctx := context.WithValue(req.Context(), connAddrKey, "127.0.0.1:4567")
req.RemoteAddr = "192.168.1.16:1234"
server.checkAddrAndSendToken(rr, req.WithContext(ctx), admin)
assert.Equal(t, http.StatusForbidden, rr.Code, rr.Body.String())
assert.Equal(t, "context value connection address", connAddrKey.String())
}
func TestUpdateContextFromCookie(t *testing.T) {
server := httpdServer{
tokenAuth: jwtauth.New(jwa.HS256.String(), utils.GenerateRandomBytes(32), nil),
@@ -672,14 +656,6 @@ func TestCookieExpiration(t *testing.T) {
cookie = rr.Header().Get("Set-Cookie")
assert.Empty(t, cookie)
req, _ = http.NewRequest(http.MethodGet, tokenPath, nil)
req.RemoteAddr = "172.16.1.2:1234"
ctx = jwtauth.NewContext(req.Context(), token, nil)
ctx = context.WithValue(ctx, connAddrKey, "10.9.9.9")
server.checkCookieExpiration(rr, req.WithContext(ctx))
cookie = rr.Header().Get("Set-Cookie")
assert.Empty(t, cookie)
req, _ = http.NewRequest(http.MethodGet, tokenPath, nil)
req.RemoteAddr = "172.16.1.12:4567"
ctx = jwtauth.NewContext(req.Context(), token, nil)
@@ -749,17 +725,10 @@ func TestCookieExpiration(t *testing.T) {
cookie = rr.Header().Get("Set-Cookie")
assert.Empty(t, cookie)
req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
req.RemoteAddr = "172.16.4.12:4567"
ctx = jwtauth.NewContext(req.Context(), token, nil)
server.checkCookieExpiration(rr, req.WithContext(context.WithValue(ctx, connAddrKey, "172.16.0.1:4567")))
cookie = rr.Header().Get("Set-Cookie")
assert.Empty(t, cookie)
req, _ = http.NewRequest(http.MethodGet, webClientFilesPath, nil)
req.RemoteAddr = "172.16.4.16:4567"
ctx = jwtauth.NewContext(req.Context(), token, nil)
server.checkCookieExpiration(rr, req.WithContext(context.WithValue(ctx, connAddrKey, "172.16.4.18:4567")))
server.checkCookieExpiration(rr, req.WithContext(ctx))
cookie = rr.Header().Get("Set-Cookie")
assert.NotEmpty(t, cookie)
@@ -1014,6 +983,111 @@ func TestJWTTokenCleanup(t *testing.T) {
stopJWTTokensCleanupTicker()
}
func TestProxyHeaders(t *testing.T) {
username := "adminTest"
password := "testPwd"
admin := dataprovider.Admin{
Username: username,
Password: password,
Permissions: []string{dataprovider.PermAdminAny},
Status: 1,
Filters: dataprovider.AdminFilters{
AllowList: []string{"172.19.2.0/24"},
},
}
err := dataprovider.AddAdmin(&admin)
assert.NoError(t, err)
testIP := "10.29.1.9"
validForwardedFor := "172.19.2.6"
b := Binding{
Address: "",
Port: 8080,
EnableWebAdmin: true,
EnableWebClient: false,
ProxyAllowed: []string{testIP, "10.8.0.0/30"},
}
err = b.parseAllowedProxy()
assert.NoError(t, err)
server := newHttpdServer(b, "")
server.initializeRouter()
testServer := httptest.NewServer(server.router)
defer testServer.Close()
req, err := http.NewRequest(http.MethodGet, tokenPath, nil)
assert.NoError(t, err)
req.Header.Set("X-Forwarded-For", validForwardedFor)
req.Header.Set(xForwardedProto, "https")
req.RemoteAddr = "127.0.0.1:123"
req.SetBasicAuth(username, password)
rr := httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnauthorized, rr.Code)
assert.Contains(t, rr.Body.String(), "login from IP 127.0.0.1 not allowed")
req.RemoteAddr = testIP
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
req.RemoteAddr = "10.8.0.2"
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code)
form := make(url.Values)
form.Set("username", username)
form.Set("password", password)
form.Set(csrfFormToken, createCSRFToken())
req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err)
req.RemoteAddr = testIP
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "login from IP 10.29.1.9 not allowed")
req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err)
req.RemoteAddr = testIP
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("X-Forwarded-For", validForwardedFor)
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusFound, rr.Code, rr.Body.String())
cookie := rr.Header().Get("Set-Cookie")
assert.NotContains(t, cookie, "Secure")
req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err)
req.RemoteAddr = testIP
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("X-Forwarded-For", validForwardedFor)
req.Header.Set(xForwardedProto, "https")
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusFound, rr.Code, rr.Body.String())
cookie = rr.Header().Get("Set-Cookie")
assert.Contains(t, cookie, "Secure")
req, err = http.NewRequest(http.MethodPost, webLoginPath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err)
req.RemoteAddr = testIP
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("X-Forwarded-For", validForwardedFor)
req.Header.Set(xForwardedProto, "http")
rr = httptest.NewRecorder()
testServer.Config.Handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusFound, rr.Code, rr.Body.String())
cookie = rr.Header().Get("Set-Cookie")
assert.NotContains(t, cookie, "Secure")
err = dataprovider.DeleteAdmin(username)
assert.NoError(t, err)
}
func TestWebAdminRedirect(t *testing.T) {
b := Binding{
Address: "",
@@ -1306,3 +1380,16 @@ func TestManageKeysInvalidClaims(t *testing.T) {
assert.Equal(t, http.StatusInternalServerError, rr.Code)
assert.Contains(t, rr.Body.String(), "Invalid token claims")
}
func TestTLSReq(t *testing.T) {
req, err := http.NewRequest(http.MethodGet, webClientLoginPath, nil)
assert.NoError(t, err)
req.TLS = &tls.ConnectionState{}
assert.True(t, isTLS(req))
req.TLS = nil
ctx := context.WithValue(req.Context(), forwardedProtoKey, "https")
assert.True(t, isTLS(req.WithContext(ctx)))
ctx = context.WithValue(req.Context(), forwardedProtoKey, "http")
assert.False(t, isTLS(req.WithContext(ctx)))
assert.Equal(t, "context value forwarded proto", forwardedProtoKey.String())
}