Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-workhorse
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
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kirill Smelkov
gitlab-workhorse
Commits
9dedc0ce
Commit
9dedc0ce
authored
Dec 09, 2015
by
Kirill Smelkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
.
parent
1a864ebd
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
40 additions
and
21 deletions
+40
-21
blob.go
blob.go
+40
-21
No files found.
blob.go
View file @
9dedc0ce
// Handler for raw blob downloads
// Handler for raw blob downloads
//
//
// Blobs are read via `git cat-file ...` with first querying authentication
// Blobs are read via `git cat-file ...` with first querying authentication
// backend about download-access p
re
mission for containing repository.
// backend about download-access p
er
mission for containing repository.
// Replies from authentication backend are cached for 30 seconds to keep
// Replies from authentication backend are cached for 30 seconds to keep
// access-to-blobs latency to minimum.
// access-to-blobs latency to minimum.
...
@@ -39,7 +39,7 @@ type AuthCacheEntry struct {
...
@@ -39,7 +39,7 @@ type AuthCacheEntry struct {
// read side, but we can tolerate some looses in Nhit and update it
// read side, but we can tolerate some looses in Nhit and update it
// without mutex or atomic. Only -race complains...
// without mutex or atomic. Only -race complains...
// ( we could use atomic.Value for atomic whole cache entry updates from
// ( we could use atomic.Value for atomic whole cache entry updates from
// refresher withot need for locks on readers side, but the need to do
// refresher witho
u
t need for locks on readers side, but the need to do
// .Nhit++ on readers side ruins that )
// .Nhit++ on readers side ruins that )
sync
.
Mutex
sync
.
Mutex
...
@@ -54,11 +54,11 @@ type AuthCacheEntry struct {
...
@@ -54,11 +54,11 @@ type AuthCacheEntry struct {
type
AuthCacheKey
struct
{
type
AuthCacheKey
struct
{
project
string
project
string
query
string
// e.g. with passing in private_token=...
query
string
// e.g. with passing in private_token=...
// TODO headers
header
string
// request header url-encoded, e.g. PRIVATE-TOKEN=...
}
}
// Authorization reply cache
// Authorization reply cache
// {}
project ->
AuthCacheEntry
// {}
AuthCacheKey -> *
AuthCacheEntry
type
AuthCache
struct
{
type
AuthCache
struct
{
u
*
upstream
// for which upstream we cache auth
u
*
upstream
// for which upstream we cache auth
...
@@ -113,14 +113,14 @@ have_entry:
...
@@ -113,14 +113,14 @@ have_entry:
c
.
cached
[
key
]
=
auth
c
.
cached
[
key
]
=
auth
c
.
mu
.
Unlock
()
c
.
mu
.
Unlock
()
// this goroutine becomes responsible for quering auth backend
// this goroutine becomes responsible for quer
y
ing auth backend
auth
.
AuthReply
=
askAuthBackend
(
c
.
u
,
key
.
project
,
key
.
quer
y
)
auth
.
AuthReply
=
c
.
askAuthBackend
(
ke
y
)
auth
.
Tauth
=
time
.
Now
()
.
Unix
()
auth
.
Tauth
=
time
.
Now
()
.
Unix
()
auth
.
Nhit
=
0
auth
.
Nhit
=
0
authReply
=
auth
.
AuthReply
authReply
=
auth
.
AuthReply
// broadcast to other goroutines this entry is ready
// broadcast to other goroutines th
at th
is entry is ready
close
(
auth
.
ready
)
close
(
auth
.
ready
)
// launch entry refresher
// launch entry refresher
...
@@ -131,7 +131,7 @@ have_entry:
...
@@ -131,7 +131,7 @@ have_entry:
}
}
// Time period for refreshing / removing unused entires in authCache
// Time period for refreshing / removing unused entires in authCache
const
authCacheRefresh
=
5
*
time
.
Second
const
authCacheRefresh
=
30
*
time
.
Second
// Goroutine to refresh auth cache entry periodically while it is used.
// Goroutine to refresh auth cache entry periodically while it is used.
// if the entry is detected to be not used - remove it from cache and stop refreshing.
// if the entry is detected to be not used - remove it from cache and stop refreshing.
...
@@ -144,9 +144,9 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
...
@@ -144,9 +144,9 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
auth
.
Unlock
()
auth
.
Unlock
()
// clear cache entry if it is not used
// clear cache entry if it is not used
log
.
Printf
(
"AUTH refresh - %v #hit: %v"
,
key
,
nhit
)
//
log.Printf("AUTH refresh - %v #hit: %v", key, nhit)
if
nhit
==
0
{
// not used - we can remove and stop refreshing
if
nhit
==
0
{
// not used - we can remove and stop refreshing
log
.
Printf
(
"AUTH - removing %v"
,
key
)
//
log.Printf("AUTH - removing %v", key)
// NOTE it is ok even if someone gets this auth in this time window
// NOTE it is ok even if someone gets this auth in this time window
// and use it for some time
// and use it for some time
c
.
mu
.
Lock
()
c
.
mu
.
Lock
()
...
@@ -155,8 +155,8 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
...
@@ -155,8 +155,8 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
break
break
}
}
log
.
Printf
(
"AUTH - refreshing %v"
,
key
)
//
log.Printf("AUTH - refreshing %v", key)
authReply
:=
askAuthBackend
(
c
.
u
,
key
.
project
,
key
.
quer
y
)
authReply
:=
c
.
askAuthBackend
(
ke
y
)
auth
.
Lock
()
auth
.
Lock
()
auth
.
AuthReply
=
authReply
auth
.
AuthReply
=
authReply
...
@@ -166,10 +166,25 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
...
@@ -166,10 +166,25 @@ func (c *AuthCache) refreshEntry(auth *AuthCacheEntry, key AuthCacheKey) {
}
}
}
}
func
(
c
*
AuthCache
)
askAuthBackend
(
key
AuthCacheKey
)
AuthReply
{
// key.header -> url.Values -> http.Header
hv
,
err
:=
url
.
ParseQuery
(
key
.
header
)
if
err
!=
nil
{
// we prepared key.header ourselves via url-encoding in XXX. It must be ok
panic
(
err
)
}
header
:=
make
(
http
.
Header
)
for
k
,
v
:=
range
hv
{
header
[
k
]
=
v
}
return
askAuthBackend
(
c
.
u
,
key
.
project
,
key
.
query
,
&
header
)
}
// Ask auth backend about whether download is ok for a project.
// Ask auth backend about whether download is ok for a project.
// Authorization is approved if AuthReply.RepoPath != "" on return
// Authorization is approved if AuthReply.RepoPath != "" on return
// Raw auth backend response is emitted to AuthReply.RawReply
// Raw auth backend response is emitted to AuthReply.RawReply
func
askAuthBackend
(
u
*
upstream
,
project
,
query
string
)
AuthReply
{
func
askAuthBackend
(
u
*
upstream
,
project
,
query
string
,
header
*
http
.
Header
)
AuthReply
{
authReply
:=
AuthReply
{
authReply
:=
AuthReply
{
RawReply
:
httptest
.
NewRecorder
(),
RawReply
:
httptest
.
NewRecorder
(),
}
}
...
@@ -178,8 +193,7 @@ func askAuthBackend(u *upstream, project, query string) AuthReply {
...
@@ -178,8 +193,7 @@ func askAuthBackend(u *upstream, project, query string) AuthReply {
// - first option is via asking as `git fetch` would do, but on Rails
// - first option is via asking as `git fetch` would do, but on Rails
// side this supports only basic auth, not private token.
// side this supports only basic auth, not private token.
// - that's why we auth backend to authenticate as if it was request to
// - that's why we auth backend to authenticate as if it was request to
// get repo archive.
// get repo archive and propagate request query and header.
// XXX PRIVATE-TOKEN (in header)
// url := project+".git/info/refs?service=git-upload-pack"
// url := project+".git/info/refs?service=git-upload-pack"
url
:=
project
+
"/repository/archive.zip"
url
:=
project
+
"/repository/archive.zip"
if
query
!=
""
{
if
query
!=
""
{
...
@@ -190,6 +204,9 @@ func askAuthBackend(u *upstream, project, query string) AuthReply {
...
@@ -190,6 +204,9 @@ func askAuthBackend(u *upstream, project, query string) AuthReply {
fail500
(
authReply
.
RawReply
,
fmt
.
Errorf
(
"GET git-upload-pack: %v"
,
err
))
fail500
(
authReply
.
RawReply
,
fmt
.
Errorf
(
"GET git-upload-pack: %v"
,
err
))
return
authReply
return
authReply
}
}
for
k
,
v
:=
range
*
header
{
reqDownloadAccess
.
Header
[
k
]
=
v
}
// Prepare everything and go through preAuthorizeHandler() that will send
// Prepare everything and go through preAuthorizeHandler() that will send
// request to auth backend and analyze/parse the reply into r.authorizationResponse.
// request to auth backend and analyze/parse the reply into r.authorizationResponse.
...
@@ -209,8 +226,8 @@ func askAuthBackend(u *upstream, project, query string) AuthReply {
...
@@ -209,8 +226,8 @@ func askAuthBackend(u *upstream, project, query string) AuthReply {
return
authReply
return
authReply
}
}
func
verifyDownloadAccess
(
u
*
upstream
,
project
,
query
string
)
AuthReply
{
func
verifyDownloadAccess
(
u
*
upstream
,
project
,
query
string
,
header
string
)
AuthReply
{
return
u
.
authCache
.
VerifyDownloadAccess
(
AuthCacheKey
{
project
,
query
})
return
u
.
authCache
.
VerifyDownloadAccess
(
AuthCacheKey
{
project
,
query
,
header
})
}
}
// HTTP handler for `.../raw/<ref>/path`
// HTTP handler for `.../raw/<ref>/path`
...
@@ -228,6 +245,7 @@ func handleGetBlobRaw(w http.ResponseWriter, r *gitRequest) {
...
@@ -228,6 +245,7 @@ func handleGetBlobRaw(w http.ResponseWriter, r *gitRequest) {
project
:=
u
.
Path
[
:
rawLoc
[
0
]]
project
:=
u
.
Path
[
:
rawLoc
[
0
]]
refpath
:=
u
.
Path
[
rawLoc
[
1
]
:
]
refpath
:=
u
.
Path
[
rawLoc
[
1
]
:
]
// XXX should be in cache
// Extract only tokens from query
// Extract only tokens from query
query
:=
url
.
Values
{}
query
:=
url
.
Values
{}
for
k
,
v
:=
range
u
.
Query
()
{
for
k
,
v
:=
range
u
.
Query
()
{
...
@@ -236,16 +254,17 @@ func handleGetBlobRaw(w http.ResponseWriter, r *gitRequest) {
...
@@ -236,16 +254,17 @@ func handleGetBlobRaw(w http.ResponseWriter, r *gitRequest) {
}
}
}
}
// Extract only tokens from headers
// XXX should be in cache
h
:=
url
.
Values
{}
// Extract only token headers
header
:=
url
.
Values
{}
for
k
,
v
:=
range
r
.
Request
.
Header
{
for
k
,
v
:=
range
r
.
Request
.
Header
{
if
strings
.
HasSuffix
(
strings
.
ToUpper
(
k
),
"-TOKEN"
)
{
if
strings
.
HasSuffix
(
strings
.
ToUpper
(
k
),
"-TOKEN"
)
{
h
[
k
]
=
v
h
eader
[
k
]
=
v
}
}
}
}
// Query download access auth for this project
// Query download access auth for this project
authReply
:=
verifyDownloadAccess
(
r
.
u
,
project
,
query
.
Encode
(),
h
)
authReply
:=
verifyDownloadAccess
(
r
.
u
,
project
,
query
.
Encode
(),
h
eader
.
Encode
()
)
if
authReply
.
RepoPath
==
""
{
if
authReply
.
RepoPath
==
""
{
// access denied - copy auth reply to client in full -
// access denied - copy auth reply to client in full -
// there are HTTP code and other headers / body relevant for
// there are HTTP code and other headers / body relevant for
...
...
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