Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
2f306717
Commit
2f306717
authored
Mar 03, 2021
by
GitLab Bot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add latest changes from gitlab-org/security/gitlab@13-9-stable-ee
parent
b7a47b15
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
195 additions
and
46 deletions
+195
-46
GITLAB_WORKHORSE_VERSION
GITLAB_WORKHORSE_VERSION
+1
-1
changelogs/unreleased/security-jv-workhorse-router-13-9.yml
changelogs/unreleased/security-jv-workhorse-router-13-9.yml
+5
-0
workhorse/CHANGELOG
workhorse/CHANGELOG
+12
-0
workhorse/VERSION
workhorse/VERSION
+1
-1
workhorse/internal/staticpages/deploy_page_test.go
workhorse/internal/staticpages/deploy_page_test.go
+2
-2
workhorse/internal/staticpages/error_pages_test.go
workhorse/internal/staticpages/error_pages_test.go
+7
-7
workhorse/internal/staticpages/servefile.go
workhorse/internal/staticpages/servefile.go
+33
-14
workhorse/internal/staticpages/servefile_test.go
workhorse/internal/staticpages/servefile_test.go
+40
-6
workhorse/internal/staticpages/static.go
workhorse/internal/staticpages/static.go
+1
-0
workhorse/internal/staticpages/testdata/file1
workhorse/internal/staticpages/testdata/file1
+1
-0
workhorse/internal/staticpages/testdata/uploads/file2
workhorse/internal/staticpages/testdata/uploads/file2
+1
-0
workhorse/internal/upstream/routes.go
workhorse/internal/upstream/routes.go
+11
-9
workhorse/internal/upstream/upstream.go
workhorse/internal/upstream/upstream.go
+6
-2
workhorse/internal/upstream/upstream_test.go
workhorse/internal/upstream/upstream_test.go
+67
-0
workhorse/main_test.go
workhorse/main_test.go
+7
-4
No files found.
GITLAB_WORKHORSE_VERSION
View file @
2f306717
8.63.
0
8.63.
2
changelogs/unreleased/security-jv-workhorse-router-13-9.yml
0 → 100644
View file @
2f306717
---
title
:
'
Workhorse:
prevent
escaped
router
path
traversal'
merge_request
:
author
:
type
:
security
workhorse/CHANGELOG
View file @
2f306717
# Changelog for gitlab-workhorse
# Changelog for gitlab-workhorse
## v8.63.2
### Security
- Stop logging when path is excluded
https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/
## v8.63.1
### Security
- Use URL.EscapePath() in upstream router
https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/
## v8.63.0
## v8.63.0
### Added
### Added
...
...
workhorse/VERSION
View file @
2f306717
8.63.
0
8.63.
2
workhorse/internal/staticpages/deploy_page_test.go
View file @
2f306717
...
@@ -23,7 +23,7 @@ func TestIfNoDeployPageExist(t *testing.T) {
...
@@ -23,7 +23,7 @@ func TestIfNoDeployPageExist(t *testing.T) {
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
executed
:=
false
executed
:=
false
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
DeployPage
(
http
.
HandlerFunc
(
func
(
_
http
.
ResponseWriter
,
_
*
http
.
Request
)
{
st
.
DeployPage
(
http
.
HandlerFunc
(
func
(
_
http
.
ResponseWriter
,
_
*
http
.
Request
)
{
executed
=
true
executed
=
true
}))
.
ServeHTTP
(
w
,
nil
)
}))
.
ServeHTTP
(
w
,
nil
)
...
@@ -45,7 +45,7 @@ func TestIfDeployPageExist(t *testing.T) {
...
@@ -45,7 +45,7 @@ func TestIfDeployPageExist(t *testing.T) {
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
executed
:=
false
executed
:=
false
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
DeployPage
(
http
.
HandlerFunc
(
func
(
_
http
.
ResponseWriter
,
_
*
http
.
Request
)
{
st
.
DeployPage
(
http
.
HandlerFunc
(
func
(
_
http
.
ResponseWriter
,
_
*
http
.
Request
)
{
executed
=
true
executed
=
true
}))
.
ServeHTTP
(
w
,
nil
)
}))
.
ServeHTTP
(
w
,
nil
)
...
...
workhorse/internal/staticpages/error_pages_test.go
View file @
2f306717
...
@@ -32,7 +32,7 @@ func TestIfErrorPageIsPresented(t *testing.T) {
...
@@ -32,7 +32,7 @@ func TestIfErrorPageIsPresented(t *testing.T) {
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
len
(
upstreamBody
),
n
,
"bytes written"
)
require
.
Equal
(
t
,
len
(
upstreamBody
),
n
,
"bytes written"
)
})
})
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
...
@@ -54,7 +54,7 @@ func TestIfErrorPassedIfNoErrorPageIsFound(t *testing.T) {
...
@@ -54,7 +54,7 @@ func TestIfErrorPassedIfNoErrorPageIsFound(t *testing.T) {
w
.
WriteHeader
(
404
)
w
.
WriteHeader
(
404
)
fmt
.
Fprint
(
w
,
errorResponse
)
fmt
.
Fprint
(
w
,
errorResponse
)
})
})
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
...
@@ -78,7 +78,7 @@ func TestIfErrorPageIsIgnoredInDevelopment(t *testing.T) {
...
@@ -78,7 +78,7 @@ func TestIfErrorPageIsIgnoredInDevelopment(t *testing.T) {
w
.
WriteHeader
(
500
)
w
.
WriteHeader
(
500
)
fmt
.
Fprint
(
w
,
serverError
)
fmt
.
Fprint
(
w
,
serverError
)
})
})
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ErrorPagesUnless
(
true
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
true
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
require
.
Equal
(
t
,
500
,
w
.
Code
)
require
.
Equal
(
t
,
500
,
w
.
Code
)
...
@@ -102,7 +102,7 @@ func TestIfErrorPageIsIgnoredIfCustomError(t *testing.T) {
...
@@ -102,7 +102,7 @@ func TestIfErrorPageIsIgnoredIfCustomError(t *testing.T) {
w
.
WriteHeader
(
500
)
w
.
WriteHeader
(
500
)
fmt
.
Fprint
(
w
,
serverError
)
fmt
.
Fprint
(
w
,
serverError
)
})
})
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
require
.
Equal
(
t
,
500
,
w
.
Code
)
require
.
Equal
(
t
,
500
,
w
.
Code
)
...
@@ -137,7 +137,7 @@ func TestErrorPageInterceptedByContentType(t *testing.T) {
...
@@ -137,7 +137,7 @@ func TestErrorPageInterceptedByContentType(t *testing.T) {
w
.
WriteHeader
(
500
)
w
.
WriteHeader
(
500
)
fmt
.
Fprint
(
w
,
serverError
)
fmt
.
Fprint
(
w
,
serverError
)
})
})
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
false
,
ErrorFormatHTML
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
require
.
Equal
(
t
,
500
,
w
.
Code
)
require
.
Equal
(
t
,
500
,
w
.
Code
)
...
@@ -161,7 +161,7 @@ func TestIfErrorPageIsPresentedJSON(t *testing.T) {
...
@@ -161,7 +161,7 @@ func TestIfErrorPageIsPresentedJSON(t *testing.T) {
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
len
(
upstreamBody
),
n
,
"bytes written"
)
require
.
Equal
(
t
,
len
(
upstreamBody
),
n
,
"bytes written"
)
})
})
st
:=
&
Static
{
""
}
st
:=
&
Static
{}
st
.
ErrorPagesUnless
(
false
,
ErrorFormatJSON
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
false
,
ErrorFormatJSON
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
...
@@ -181,7 +181,7 @@ func TestIfErrorPageIsPresentedText(t *testing.T) {
...
@@ -181,7 +181,7 @@ func TestIfErrorPageIsPresentedText(t *testing.T) {
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
len
(
upstreamBody
),
n
,
"bytes written"
)
require
.
Equal
(
t
,
len
(
upstreamBody
),
n
,
"bytes written"
)
})
})
st
:=
&
Static
{
""
}
st
:=
&
Static
{}
st
.
ErrorPagesUnless
(
false
,
ErrorFormatText
,
h
)
.
ServeHTTP
(
w
,
nil
)
st
.
ErrorPagesUnless
(
false
,
ErrorFormatText
,
h
)
.
ServeHTTP
(
w
,
nil
)
w
.
Flush
()
w
.
Flush
()
...
...
workhorse/internal/staticpages/servefile.go
View file @
2f306717
package
staticpages
package
staticpages
import
(
import
(
"errors"
"fmt"
"net/http"
"net/http"
"os"
"os"
"path/filepath"
"path/filepath"
"strings"
"strings"
"time"
"time"
"gitlab.com/gitlab-org/labkit/log"
"gitlab.com/gitlab-org/labkit/mask"
"gitlab.com/gitlab-org/labkit/mask"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/helper"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/log"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/urlprefix"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/urlprefix"
)
)
...
@@ -26,21 +28,28 @@ const (
...
@@ -26,21 +28,28 @@ const (
// upstream.
// upstream.
func
(
s
*
Static
)
ServeExisting
(
prefix
urlprefix
.
Prefix
,
cache
CacheMode
,
notFoundHandler
http
.
Handler
)
http
.
Handler
{
func
(
s
*
Static
)
ServeExisting
(
prefix
urlprefix
.
Prefix
,
cache
CacheMode
,
notFoundHandler
http
.
Handler
)
http
.
Handler
{
return
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
return
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
file
:=
filepath
.
Join
(
s
.
DocumentRoot
,
prefix
.
Strip
(
r
.
URL
.
Path
))
if
notFoundHandler
==
nil
{
notFoundHandler
=
http
.
HandlerFunc
(
http
.
NotFound
)
}
// We intentionally use r.URL.Path instead of r.URL.EscaptedPath() below.
// This is to make it possible to serve static files with e.g. a space
// %20 in their name.
relativePath
,
err
:=
s
.
validatePath
(
prefix
.
Strip
(
r
.
URL
.
Path
))
if
err
!=
nil
{
notFoundHandler
.
ServeHTTP
(
w
,
r
)
return
}
// The filepath.Join does Clean traversing directories up
file
:=
filepath
.
Join
(
s
.
DocumentRoot
,
relativePath
)
if
!
strings
.
HasPrefix
(
file
,
s
.
DocumentRoot
)
{
if
!
strings
.
HasPrefix
(
file
,
s
.
DocumentRoot
)
{
helper
.
Fail500
(
w
,
r
,
&
os
.
PathError
{
log
.
WithRequest
(
r
)
.
WithError
(
errPathTraversal
)
.
Error
()
Op
:
"open"
,
notFoundHandler
.
ServeHTTP
(
w
,
r
)
Path
:
file
,
Err
:
os
.
ErrInvalid
,
})
return
return
}
}
var
content
*
os
.
File
var
content
*
os
.
File
var
fi
os
.
FileInfo
var
fi
os
.
FileInfo
var
err
error
// Serve pre-gzipped assets
// Serve pre-gzipped assets
if
acceptEncoding
:=
r
.
Header
.
Get
(
"Accept-Encoding"
);
strings
.
Contains
(
acceptEncoding
,
"gzip"
)
{
if
acceptEncoding
:=
r
.
Header
.
Get
(
"Accept-Encoding"
);
strings
.
Contains
(
acceptEncoding
,
"gzip"
)
{
...
@@ -55,11 +64,7 @@ func (s *Static) ServeExisting(prefix urlprefix.Prefix, cache CacheMode, notFoun
...
@@ -55,11 +64,7 @@ func (s *Static) ServeExisting(prefix urlprefix.Prefix, cache CacheMode, notFoun
content
,
fi
,
err
=
helper
.
OpenFile
(
file
)
content
,
fi
,
err
=
helper
.
OpenFile
(
file
)
}
}
if
err
!=
nil
{
if
err
!=
nil
{
if
notFoundHandler
!=
nil
{
notFoundHandler
.
ServeHTTP
(
w
,
r
)
notFoundHandler
.
ServeHTTP
(
w
,
r
)
}
else
{
http
.
NotFound
(
w
,
r
)
}
return
return
}
}
defer
content
.
Close
()
defer
content
.
Close
()
...
@@ -82,3 +87,17 @@ func (s *Static) ServeExisting(prefix urlprefix.Prefix, cache CacheMode, notFoun
...
@@ -82,3 +87,17 @@ func (s *Static) ServeExisting(prefix urlprefix.Prefix, cache CacheMode, notFoun
http
.
ServeContent
(
w
,
r
,
filepath
.
Base
(
file
),
fi
.
ModTime
(),
content
)
http
.
ServeContent
(
w
,
r
,
filepath
.
Base
(
file
),
fi
.
ModTime
(),
content
)
})
})
}
}
var
errPathTraversal
=
errors
.
New
(
"path traversal"
)
func
(
s
*
Static
)
validatePath
(
filename
string
)
(
string
,
error
)
{
filename
=
filepath
.
Clean
(
filename
)
for
_
,
exc
:=
range
s
.
Exclude
{
if
strings
.
HasPrefix
(
filename
,
exc
)
{
return
""
,
fmt
.
Errorf
(
"file is excluded: %s"
,
exc
)
}
}
return
filename
,
nil
}
workhorse/internal/staticpages/servefile_test.go
View file @
2f306717
...
@@ -20,7 +20,7 @@ func TestServingNonExistingFile(t *testing.T) {
...
@@ -20,7 +20,7 @@ func TestServingNonExistingFile(t *testing.T) {
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/file"
,
nil
)
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/file"
,
nil
)
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
require
.
Equal
(
t
,
404
,
w
.
Code
)
require
.
Equal
(
t
,
404
,
w
.
Code
)
}
}
...
@@ -34,7 +34,7 @@ func TestServingDirectory(t *testing.T) {
...
@@ -34,7 +34,7 @@ func TestServingDirectory(t *testing.T) {
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/file"
,
nil
)
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/file"
,
nil
)
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
require
.
Equal
(
t
,
404
,
w
.
Code
)
require
.
Equal
(
t
,
404
,
w
.
Code
)
}
}
...
@@ -44,7 +44,7 @@ func TestServingMalformedUri(t *testing.T) {
...
@@ -44,7 +44,7 @@ func TestServingMalformedUri(t *testing.T) {
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/../../../static/file"
,
nil
)
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/../../../static/file"
,
nil
)
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
require
.
Equal
(
t
,
404
,
w
.
Code
)
require
.
Equal
(
t
,
404
,
w
.
Code
)
}
}
...
@@ -54,7 +54,7 @@ func TestExecutingHandlerWhenNoFileFound(t *testing.T) {
...
@@ -54,7 +54,7 @@ func TestExecutingHandlerWhenNoFileFound(t *testing.T) {
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/file"
,
nil
)
httpRequest
,
_
:=
http
.
NewRequest
(
"GET"
,
"/file"
,
nil
)
executed
:=
false
executed
:=
false
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
http
.
HandlerFunc
(
func
(
_
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
http
.
HandlerFunc
(
func
(
_
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
executed
=
(
r
==
httpRequest
)
executed
=
(
r
==
httpRequest
)
}))
.
ServeHTTP
(
nil
,
httpRequest
)
}))
.
ServeHTTP
(
nil
,
httpRequest
)
...
@@ -76,7 +76,7 @@ func TestServingTheActualFile(t *testing.T) {
...
@@ -76,7 +76,7 @@ func TestServingTheActualFile(t *testing.T) {
ioutil
.
WriteFile
(
filepath
.
Join
(
dir
,
"file"
),
[]
byte
(
fileContent
),
0600
)
ioutil
.
WriteFile
(
filepath
.
Join
(
dir
,
"file"
),
[]
byte
(
fileContent
),
0600
)
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
require
.
Equal
(
t
,
200
,
w
.
Code
)
require
.
Equal
(
t
,
200
,
w
.
Code
)
if
w
.
Body
.
String
()
!=
fileContent
{
if
w
.
Body
.
String
()
!=
fileContent
{
...
@@ -84,6 +84,40 @@ func TestServingTheActualFile(t *testing.T) {
...
@@ -84,6 +84,40 @@ func TestServingTheActualFile(t *testing.T) {
}
}
}
}
func
TestExcludedPaths
(
t
*
testing
.
T
)
{
testCases
:=
[]
struct
{
desc
string
path
string
found
bool
contents
string
}{
{
"allowed file"
,
"/file1"
,
true
,
"contents1"
},
{
"path traversal is allowed"
,
"/uploads/../file1"
,
true
,
"contents1"
},
{
"files in /uploads/ are invisible"
,
"/uploads/file2"
,
false
,
""
},
{
"cannot use path traversal to get to /uploads/"
,
"/foobar/../uploads/file2"
,
false
,
""
},
{
"cannot use escaped path traversal to get to /uploads/"
,
"/foobar%2f%2e%2e%2fuploads/file2"
,
false
,
""
},
{
"cannot use double escaped path traversal to get to /uploads/"
,
"/foobar%252f%252e%252e%252fuploads/file2"
,
false
,
""
},
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
desc
,
func
(
t
*
testing
.
T
)
{
httpRequest
,
err
:=
http
.
NewRequest
(
"GET"
,
tc
.
path
,
nil
)
require
.
NoError
(
t
,
err
)
w
:=
httptest
.
NewRecorder
()
st
:=
&
Static
{
DocumentRoot
:
"testdata"
,
Exclude
:
[]
string
{
"/uploads/"
}}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
if
tc
.
found
{
require
.
Equal
(
t
,
200
,
w
.
Code
)
require
.
Equal
(
t
,
tc
.
contents
,
w
.
Body
.
String
())
}
else
{
require
.
Equal
(
t
,
404
,
w
.
Code
)
}
})
}
}
func
testServingThePregzippedFile
(
t
*
testing
.
T
,
enableGzip
bool
)
{
func
testServingThePregzippedFile
(
t
*
testing
.
T
,
enableGzip
bool
)
{
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"deploy"
)
dir
,
err
:=
ioutil
.
TempDir
(
""
,
"deploy"
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -108,7 +142,7 @@ func testServingThePregzippedFile(t *testing.T, enableGzip bool) {
...
@@ -108,7 +142,7 @@ func testServingThePregzippedFile(t *testing.T, enableGzip bool) {
ioutil
.
WriteFile
(
filepath
.
Join
(
dir
,
"file"
),
[]
byte
(
fileContent
),
0600
)
ioutil
.
WriteFile
(
filepath
.
Join
(
dir
,
"file"
),
[]
byte
(
fileContent
),
0600
)
w
:=
httptest
.
NewRecorder
()
w
:=
httptest
.
NewRecorder
()
st
:=
&
Static
{
dir
}
st
:=
&
Static
{
DocumentRoot
:
dir
}
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
st
.
ServeExisting
(
"/"
,
CacheDisabled
,
nil
)
.
ServeHTTP
(
w
,
httpRequest
)
require
.
Equal
(
t
,
200
,
w
.
Code
)
require
.
Equal
(
t
,
200
,
w
.
Code
)
if
enableGzip
{
if
enableGzip
{
...
...
workhorse/internal/staticpages/static.go
View file @
2f306717
...
@@ -2,4 +2,5 @@ package staticpages
...
@@ -2,4 +2,5 @@ package staticpages
type
Static
struct
{
type
Static
struct
{
DocumentRoot
string
DocumentRoot
string
Exclude
[]
string
}
}
workhorse/internal/staticpages/testdata/file1
0 → 100644
View file @
2f306717
contents1
\ No newline at end of file
workhorse/internal/staticpages/testdata/uploads/file2
0 → 100644
View file @
2f306717
contents2
\ No newline at end of file
workhorse/internal/upstream/routes.go
View file @
2f306717
...
@@ -62,6 +62,14 @@ const (
...
@@ -62,6 +62,14 @@ const (
importPattern
=
`^/import/`
importPattern
=
`^/import/`
)
)
var
(
// For legacy reasons, user uploads are stored in public/uploads. To
// prevent anybody who knows/guesses the URL of a user-uploaded file
// from downloading it we configure static.ServeExisting to treat files
// under public/uploads/ as if they do not exist.
staticExclude
=
[]
string
{
"/uploads/"
}
)
func
compileRegexp
(
regexpStr
string
)
*
regexp
.
Regexp
{
func
compileRegexp
(
regexpStr
string
)
*
regexp
.
Regexp
{
if
len
(
regexpStr
)
==
0
{
if
len
(
regexpStr
)
==
0
{
return
nil
return
nil
...
@@ -181,20 +189,20 @@ func buildProxy(backend *url.URL, version string, rt http.RoundTripper, cfg conf
...
@@ -181,20 +189,20 @@ func buildProxy(backend *url.URL, version string, rt http.RoundTripper, cfg conf
// We match against URI not containing the relativeUrlRoot:
// We match against URI not containing the relativeUrlRoot:
// see upstream.ServeHTTP
// see upstream.ServeHTTP
func
(
u
*
upstream
)
configureRoutes
(
)
{
func
configureRoutes
(
u
*
upstream
)
{
api
:=
apipkg
.
NewAPI
(
api
:=
apipkg
.
NewAPI
(
u
.
Backend
,
u
.
Backend
,
u
.
Version
,
u
.
Version
,
u
.
RoundTripper
,
u
.
RoundTripper
,
)
)
static
:=
&
staticpages
.
Static
{
DocumentRoot
:
u
.
DocumentRoot
}
static
:=
&
staticpages
.
Static
{
DocumentRoot
:
u
.
DocumentRoot
,
Exclude
:
staticExclude
}
proxy
:=
buildProxy
(
u
.
Backend
,
u
.
Version
,
u
.
RoundTripper
,
u
.
Config
)
proxy
:=
buildProxy
(
u
.
Backend
,
u
.
Version
,
u
.
RoundTripper
,
u
.
Config
)
cableProxy
:=
proxypkg
.
NewProxy
(
u
.
CableBackend
,
u
.
Version
,
u
.
CableRoundTripper
)
cableProxy
:=
proxypkg
.
NewProxy
(
u
.
CableBackend
,
u
.
Version
,
u
.
CableRoundTripper
)
assetsNotFoundHandler
:=
NotFoundUnless
(
u
.
DevelopmentMode
,
proxy
)
assetsNotFoundHandler
:=
NotFoundUnless
(
u
.
DevelopmentMode
,
proxy
)
if
u
.
AltDocumentRoot
!=
""
{
if
u
.
AltDocumentRoot
!=
""
{
altStatic
:=
&
staticpages
.
Static
{
DocumentRoot
:
u
.
AltDocumentRoot
}
altStatic
:=
&
staticpages
.
Static
{
DocumentRoot
:
u
.
AltDocumentRoot
,
Exclude
:
staticExclude
}
assetsNotFoundHandler
=
altStatic
.
ServeExisting
(
assetsNotFoundHandler
=
altStatic
.
ServeExisting
(
u
.
URLPrefix
,
u
.
URLPrefix
,
staticpages
.
CacheExpireMax
,
staticpages
.
CacheExpireMax
,
...
@@ -306,12 +314,6 @@ func (u *upstream) configureRoutes() {
...
@@ -306,12 +314,6 @@ func (u *upstream) configureRoutes() {
u
.
route
(
"POST"
,
snippetUploadPattern
,
upload
.
Accelerate
(
api
,
signingProxy
,
preparers
.
uploads
)),
u
.
route
(
"POST"
,
snippetUploadPattern
,
upload
.
Accelerate
(
api
,
signingProxy
,
preparers
.
uploads
)),
u
.
route
(
"POST"
,
userUploadPattern
,
upload
.
Accelerate
(
api
,
signingProxy
,
preparers
.
uploads
)),
u
.
route
(
"POST"
,
userUploadPattern
,
upload
.
Accelerate
(
api
,
signingProxy
,
preparers
.
uploads
)),
// For legacy reasons, user uploads are stored under the document root.
// To prevent anybody who knows/guesses the URL of a user-uploaded file
// from downloading it we make sure requests to /uploads/ do _not_ pass
// through static.ServeExisting.
u
.
route
(
""
,
`^/uploads/`
,
static
.
ErrorPagesUnless
(
u
.
DevelopmentMode
,
staticpages
.
ErrorFormatHTML
,
proxy
)),
// health checks don't intercept errors and go straight to rails
// health checks don't intercept errors and go straight to rails
// TODO: We should probably not return a HTML deploy page?
// TODO: We should probably not return a HTML deploy page?
// https://gitlab.com/gitlab-org/gitlab-workhorse/issues/230
// https://gitlab.com/gitlab-org/gitlab-workhorse/issues/230
...
...
workhorse/internal/upstream/upstream.go
View file @
2f306717
...
@@ -41,6 +41,10 @@ type upstream struct {
...
@@ -41,6 +41,10 @@ type upstream struct {
}
}
func
NewUpstream
(
cfg
config
.
Config
,
accessLogger
*
logrus
.
Logger
)
http
.
Handler
{
func
NewUpstream
(
cfg
config
.
Config
,
accessLogger
*
logrus
.
Logger
)
http
.
Handler
{
return
newUpstream
(
cfg
,
accessLogger
,
configureRoutes
)
}
func
newUpstream
(
cfg
config
.
Config
,
accessLogger
*
logrus
.
Logger
,
routesCallback
func
(
*
upstream
))
http
.
Handler
{
up
:=
upstream
{
up
:=
upstream
{
Config
:
cfg
,
Config
:
cfg
,
accessLogger
:
accessLogger
,
accessLogger
:
accessLogger
,
...
@@ -57,7 +61,7 @@ func NewUpstream(cfg config.Config, accessLogger *logrus.Logger) http.Handler {
...
@@ -57,7 +61,7 @@ func NewUpstream(cfg config.Config, accessLogger *logrus.Logger) http.Handler {
up
.
RoundTripper
=
roundtripper
.
NewBackendRoundTripper
(
up
.
Backend
,
up
.
Socket
,
up
.
ProxyHeadersTimeout
,
cfg
.
DevelopmentMode
)
up
.
RoundTripper
=
roundtripper
.
NewBackendRoundTripper
(
up
.
Backend
,
up
.
Socket
,
up
.
ProxyHeadersTimeout
,
cfg
.
DevelopmentMode
)
up
.
CableRoundTripper
=
roundtripper
.
NewBackendRoundTripper
(
up
.
CableBackend
,
up
.
CableSocket
,
up
.
ProxyHeadersTimeout
,
cfg
.
DevelopmentMode
)
up
.
CableRoundTripper
=
roundtripper
.
NewBackendRoundTripper
(
up
.
CableBackend
,
up
.
CableSocket
,
up
.
ProxyHeadersTimeout
,
cfg
.
DevelopmentMode
)
up
.
configureURLPrefix
()
up
.
configureURLPrefix
()
up
.
configureRoutes
(
)
routesCallback
(
&
up
)
var
correlationOpts
[]
correlation
.
InboundHandlerOption
var
correlationOpts
[]
correlation
.
InboundHandlerOption
if
cfg
.
PropagateCorrelationID
{
if
cfg
.
PropagateCorrelationID
{
...
@@ -96,7 +100,7 @@ func (u *upstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...
@@ -96,7 +100,7 @@ func (u *upstream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
// Check URL Root
// Check URL Root
URIPath
:=
urlprefix
.
CleanURIPath
(
r
.
URL
.
Path
)
URIPath
:=
urlprefix
.
CleanURIPath
(
r
.
URL
.
EscapedPath
()
)
prefix
:=
u
.
URLPrefix
prefix
:=
u
.
URLPrefix
if
!
prefix
.
Match
(
URIPath
)
{
if
!
prefix
.
Match
(
URIPath
)
{
helper
.
HTTPError
(
w
,
r
,
fmt
.
Sprintf
(
"Not found %q"
,
URIPath
),
http
.
StatusNotFound
)
helper
.
HTTPError
(
w
,
r
,
fmt
.
Sprintf
(
"Not found %q"
,
URIPath
),
http
.
StatusNotFound
)
...
...
workhorse/internal/upstream/upstream_test.go
0 → 100644
View file @
2f306717
package
upstream
import
(
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/gitlab-workhorse/internal/config"
)
func
TestRouting
(
t
*
testing
.
T
)
{
handle
:=
func
(
u
*
upstream
,
regex
string
)
routeEntry
{
handler
:=
http
.
HandlerFunc
(
func
(
w
http
.
ResponseWriter
,
_
*
http
.
Request
)
{
io
.
WriteString
(
w
,
regex
)
})
return
u
.
route
(
""
,
regex
,
handler
)
}
const
(
foobar
=
`\A/foobar\z`
quxbaz
=
`\A/quxbaz\z`
main
=
""
)
u
:=
newUpstream
(
config
.
Config
{},
logrus
.
StandardLogger
(),
func
(
u
*
upstream
)
{
u
.
Routes
=
[]
routeEntry
{
handle
(
u
,
foobar
),
handle
(
u
,
quxbaz
),
handle
(
u
,
main
),
}
})
ts
:=
httptest
.
NewServer
(
u
)
defer
ts
.
Close
()
testCases
:=
[]
struct
{
desc
string
path
string
route
string
}{
{
"main route works"
,
"/"
,
main
},
{
"foobar route works"
,
"/foobar"
,
foobar
},
{
"quxbaz route works"
,
"/quxbaz"
,
quxbaz
},
{
"path traversal works, ends up in quxbaz"
,
"/foobar/../quxbaz"
,
quxbaz
},
{
"escaped path traversal does not match any route"
,
"/foobar%2f%2e%2e%2fquxbaz"
,
main
},
{
"double escaped path traversal does not match any route"
,
"/foobar%252f%252e%252e%252fquxbaz"
,
main
},
}
for
_
,
tc
:=
range
testCases
{
t
.
Run
(
tc
.
desc
,
func
(
t
*
testing
.
T
)
{
resp
,
err
:=
http
.
Get
(
ts
.
URL
+
tc
.
path
)
require
.
NoError
(
t
,
err
)
defer
resp
.
Body
.
Close
()
body
,
err
:=
ioutil
.
ReadAll
(
resp
.
Body
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
200
,
resp
.
StatusCode
,
"response code"
)
require
.
Equal
(
t
,
tc
.
route
,
string
(
body
))
})
}
}
workhorse/main_test.go
View file @
2f306717
...
@@ -222,12 +222,15 @@ func TestDeniedPublicUploadsFile(t *testing.T) {
...
@@ -222,12 +222,15 @@ func TestDeniedPublicUploadsFile(t *testing.T) {
for
_
,
resource
:=
range
[]
string
{
for
_
,
resource
:=
range
[]
string
{
"/uploads/static.txt"
,
"/uploads/static.txt"
,
"/uploads%2Fstatic.txt"
,
"/uploads%2Fstatic.txt"
,
"/foobar%2F%2E%2E%2Fuploads/static.txt"
,
}
{
}
{
resp
,
body
:=
httpGet
(
t
,
ws
.
URL
+
resource
,
nil
)
t
.
Run
(
resource
,
func
(
t
*
testing
.
T
)
{
resp
,
body
:=
httpGet
(
t
,
ws
.
URL
+
resource
,
nil
)
require
.
Equal
(
t
,
404
,
resp
.
StatusCode
,
"GET %q: status code"
,
resource
)
require
.
Equal
(
t
,
404
,
resp
.
StatusCode
,
"GET %q: status code"
,
resource
)
require
.
Equal
(
t
,
""
,
body
,
"GET %q: response body"
,
resource
)
require
.
Equal
(
t
,
""
,
body
,
"GET %q: response body"
,
resource
)
require
.
True
(
t
,
proxied
,
"GET %q: never made it to backend"
,
resource
)
require
.
True
(
t
,
proxied
,
"GET %q: never made it to backend"
,
resource
)
})
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment