Skip to content

Commit 0dae104

Browse files
committed
Generate URI of token service via Host in request
This commit update the flow to generate URL of token service, which will first try to use the Host in request. This will help the situation when Harbor is configured to serve via a hostname but some client needs to pull artifacts from Harbor via IP due to limitations in the environment. Signed-off-by: Daniel Jiang <daniel.jiang@broadcom.com>
1 parent 9850f14 commit 0dae104

2 files changed

Lines changed: 93 additions & 41 deletions

File tree

src/server/middleware/v2auth/auth.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,10 @@ func getChallenge(req *http.Request, accessList []access) string {
9797
return `Basic realm="harbor"`
9898
}
9999
// No auth header, treat it as CLI and redirect to token service
100-
ep, err := tokenSvcEndpoint(req)
100+
tokenSvc, err := tokenSvcURL(req)
101101
if err != nil {
102102
logger.Errorf("failed to get the endpoint for token service, error: %v", err)
103103
}
104-
tokenSvc := fmt.Sprintf("%s/service/token", strings.TrimSuffix(ep, "/"))
105104
scope := ""
106105
for _, a := range accessList {
107106
if len(scope) > 0 {
@@ -116,12 +115,28 @@ func getChallenge(req *http.Request, accessList []access) string {
116115
return challenge
117116
}
118117

119-
func tokenSvcEndpoint(req *http.Request) (string, error) {
118+
func tokenSvcURL(req *http.Request) (string, error) {
119+
getURL := func(ep string) string {
120+
return fmt.Sprintf("%s/service/token", strings.TrimSuffix(ep, "/"))
121+
}
122+
// TODO: Double check if the internal core URL can be removed, after the token service URI is built according to the Host info in request.
120123
rawCoreURL := config.InternalCoreURL()
121124
if match(req.Context(), req.Host, rawCoreURL) {
122-
return rawCoreURL, nil
125+
return getURL(rawCoreURL), nil
126+
}
127+
extEp, err := config.ExtEndpoint()
128+
if err != nil {
129+
return "", err
130+
}
131+
if len(req.Host) > 0 {
132+
l := strings.Split(extEp, "://")
133+
if len(l) > 1 {
134+
return getURL(l[0] + "://" + req.Host), nil
135+
}
136+
return getURL(req.Host), nil
123137
}
124-
return config.ExtEndpoint()
138+
log.Infof("The Host is empty in the request, forming the URL via the configured external endpoint: %s", extEp)
139+
return getURL(extEp), nil
125140
}
126141

127142
func match(ctx context.Context, reqHost, rawURL string) bool {

src/server/middleware/v2auth/auth_test.go

Lines changed: 73 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -222,70 +222,107 @@ func TestMiddleware(t *testing.T) {
222222
}
223223

224224
func TestGetChallenge(t *testing.T) {
225-
req1, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/", nil)
226-
req1x := req1.Clone(req1.Context())
227-
req1x.SetBasicAuth("u", "p")
228-
req2, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/_catalog", nil)
229-
req2x := req2.Clone(req2.Context())
230-
req2x.Header.Set("Authorization", "Bearer xx")
231-
req3, _ := http.NewRequest(http.MethodPost, "https://registry.test/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_2/ubuntu", nil)
232-
req3 = req3.WithContext(lib.WithArtifactInfo(context.Background(), lib.ArtifactInfo{
233-
Repository: "project_1/ubuntu",
234-
Reference: "14.04",
235-
ProjectName: "project_1",
236-
BlobMountRepository: "project_2/ubuntu",
237-
BlobMountProjectName: "project_2",
238-
BlobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
239-
}))
240-
req3x := req3.Clone(req3.Context())
241-
req3x.SetBasicAuth("", "")
242-
req3x.Host = "harbor.test"
243-
req4, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/project_1/hello-world/manifests/v1", nil)
244-
req4 = req4.WithContext(lib.WithArtifactInfo(context.Background(), lib.ArtifactInfo{
245-
Repository: "project_1/hello-world",
246-
Reference: "v1",
247-
ProjectName: "project_1",
248-
}))
249-
req4.Host = "harbor.core:8443"
250-
251225
cases := []struct {
226+
name string
252227
request *http.Request
253228
challenge string
254229
}{
255230
{
256-
request: req1,
231+
name: "Regular login request to '/v2' should return challenge whose realm is token URL with the Host header in Request",
232+
request: func() *http.Request {
233+
req, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/", nil)
234+
return req
235+
}(),
236+
challenge: `Bearer realm="https://registry.test/service/token",service="harbor-registry"`,
237+
},
238+
{
239+
name: "Regular login request to '/v2' without 'Host', should return challenge whose realm is token URL with Ext endpoint",
240+
request: func() *http.Request {
241+
req, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/", nil)
242+
req.Host = ""
243+
return req
244+
}(),
257245
challenge: `Bearer realm="https://harbor.test/service/token",service="harbor-registry"`,
258246
},
259247
{
260-
request: req1x,
248+
name: "Request to 'v2' carrying basic auth header, the challenge should not have token service URI as realm b/c it's not from OCI client",
249+
request: func() *http.Request {
250+
req, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/", nil)
251+
req.SetBasicAuth("u", "p")
252+
return req
253+
}(),
261254
challenge: `Basic realm="harbor"`,
262255
},
263256
{
264-
request: req2,
257+
name: "Request to '/v2/_catalog' should return the challenge should not have token service URI as realm",
258+
request: func() *http.Request {
259+
req, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/_catalog", nil)
260+
return req
261+
}(),
265262
challenge: `Basic realm="harbor"`,
266263
},
267264
{
268-
request: req2x,
265+
name: "Request to '/v2/_catalog' should return the challenge should not have token service URI as realm, disregarding the auth header in request",
266+
request: func() *http.Request {
267+
req, _ := http.NewRequest(http.MethodGet, "https://registry.test/v2/_catalog", nil)
268+
req.Header.Set("Authorization", "Bearer xx")
269+
return req
270+
}(),
269271
challenge: `Basic realm="harbor"`,
270272
},
271273
{
272-
request: req3,
274+
name: "Request to mount a blob from one repo to another should return challenge with scope according to the artifact info in the context of the request",
275+
request: func() *http.Request {
276+
req, _ := http.NewRequest(http.MethodPost, "https://harbor.test/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_2/ubuntu", nil)
277+
req = req.WithContext(lib.WithArtifactInfo(context.Background(), lib.ArtifactInfo{
278+
Repository: "project_1/ubuntu",
279+
Reference: "14.04",
280+
ProjectName: "project_1",
281+
BlobMountRepository: "project_2/ubuntu",
282+
BlobMountProjectName: "project_2",
283+
BlobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
284+
}))
285+
return req
286+
}(),
273287
challenge: `Bearer realm="https://harbor.test/service/token",service="harbor-registry",scope="repository:project_1/ubuntu:pull,push repository:project_2/ubuntu:pull"`,
274288
},
275289
{
276-
request: req3x,
290+
name: "Request to be passed to registry, if it has basic auth header, it should return challenge without token URI as realm",
291+
request: func() *http.Request {
292+
req, _ := http.NewRequest(http.MethodPost, "https://harbor.test/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_2/ubuntu", nil)
293+
req = req.WithContext(lib.WithArtifactInfo(context.Background(), lib.ArtifactInfo{
294+
Repository: "project_1/ubuntu",
295+
Reference: "14.04",
296+
ProjectName: "project_1",
297+
BlobMountRepository: "project_2/ubuntu",
298+
BlobMountProjectName: "project_2",
299+
BlobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
300+
}))
301+
req.SetBasicAuth("user", "password")
302+
return req
303+
}(),
277304
challenge: `Basic realm="harbor"`,
278305
},
279306
{
280-
request: req4,
307+
name: "Request to be passed to registry, if it is sent from internal, the token service URI in the realm of the challenge should also point to the internal URI",
308+
request: func() *http.Request {
309+
req, _ := http.NewRequest(http.MethodGet, "https://harbor.core:8443/v2/project_1/hello-world/manifests/v1", nil)
310+
req = req.WithContext(lib.WithArtifactInfo(context.Background(), lib.ArtifactInfo{
311+
Repository: "project_1/hello-world",
312+
Reference: "v1",
313+
ProjectName: "project_1",
314+
}))
315+
return req
316+
}(),
281317
challenge: `Bearer realm="https://harbor.core:8443/service/token",service="harbor-registry",scope="repository:project_1/hello-world:pull"`,
282318
},
283319
}
284320
for _, c := range cases {
285-
acs := accessList(c.request)
286-
assert.Equal(t, c.challenge, getChallenge(c.request, acs))
321+
t.Run(c.name, func(t *testing.T) {
322+
acs := accessList(c.request)
323+
assert.Equal(t, c.challenge, getChallenge(c.request, acs))
324+
})
287325
}
288-
289326
}
290327

291328
func TestMatch(t *testing.T) {

0 commit comments

Comments
 (0)