feat: [AH-783]: Added middleware for original URL (#3221)

* [AH-783]: Updated method names
* [AH-783]: Lint fixed
* [AH-783]: Added middleware for original URL
BT-10437
Arvind Choudhary 2025-01-03 23:00:20 +00:00 committed by Harness
parent dec251e385
commit 0ac9a3aac1
9 changed files with 105 additions and 19 deletions

View File

@ -17,7 +17,6 @@ package oci
import (
"context"
"net/http"
"net/url"
"strings"
usercontroller "github.com/harness/gitness/app/api/controller/user"
@ -27,11 +26,13 @@ import (
urlprovider "github.com/harness/gitness/app/url"
"github.com/harness/gitness/registry/app/api/controller/metadata"
"github.com/harness/gitness/registry/app/api/openapi/contracts/artifact"
"github.com/harness/gitness/registry/app/common"
"github.com/harness/gitness/registry/app/dist_temp/dcontext"
"github.com/harness/gitness/registry/app/dist_temp/errcode"
"github.com/harness/gitness/registry/app/pkg"
"github.com/harness/gitness/registry/app/pkg/commons"
"github.com/harness/gitness/registry/app/pkg/docker"
"github.com/harness/gitness/registry/request"
v2 "github.com/distribution/distribution/v3/registry/api/v2"
"github.com/opencontainers/go-digest"
@ -110,16 +111,6 @@ func getRouteType(url string) routeType {
return Invalid
}
func GetQueryParamMap(queryParams url.Values) map[string]string {
queryMap := make(map[string]string)
for key, values := range queryParams {
if len(values) > 0 {
queryMap[key] = values[0]
}
}
return queryMap
}
// ExtractPathVars extracts registry, image, reference, digest and tag from the path
// Path format: /v2/:rootSpace/:registry/:image/manifests/:reference (for ex:
// /v2/myRootSpace/reg1/alpine/blobs/sha256:a258b2a6b59a7aa244d8ceab095c7f8df726f27075a69fca7ad8490f3f63148a).
@ -178,11 +169,24 @@ func handleErrors(ctx context.Context, errors errcode.Errors, w http.ResponseWri
}
}
func getPathRoot(ctx context.Context) string {
originalURL := request.OriginalURLFrom(ctx)
pathRoot := ""
if originalURL != "" {
originalURL = strings.Trim(originalURL, "/")
segments := strings.Split(originalURL, "/")
if len(segments) > 1 {
pathRoot = segments[1]
}
}
return pathRoot
}
func (h *Handler) GetRegistryInfo(r *http.Request, remoteSupport bool) (pkg.RegistryInfo, error) {
ctx := r.Context()
queryParams := r.URL.Query()
path := r.URL.Path
paramMap := GetQueryParamMap(queryParams)
paramMap := common.ExtractFirstQueryParams(queryParams)
rootIdentifier, registryIdentifier, image, ref, dgst, tag := ExtractPathVars(path, paramMap)
if err := metadata.ValidateIdentifier(rootIdentifier); err != nil {
return pkg.RegistryInfo{}, err
@ -207,9 +211,12 @@ func (h *Handler) GetRegistryInfo(r *http.Request, remoteSupport bool) (pkg.Regi
return pkg.RegistryInfo{}, errcode.ErrCodeParentNotFound
}
pathRoot := getPathRoot(r.Context())
info := &pkg.RegistryInfo{
ArtifactInfo: &pkg.ArtifactInfo{
BaseInfo: &pkg.BaseInfo{
PathRoot: pathRoot,
RootIdentifier: rootIdentifier,
RootParentID: rootSpace.ID,
ParentID: registry.ParentID,

View File

@ -101,7 +101,7 @@ func getScope(r *http.Request) string {
var scope string
path := r.URL.Path
if path != "/v2/" && path != "/v2/token" {
paramMap := oci.GetQueryParamMap(r.URL.Query())
paramMap := common.ExtractFirstQueryParams(r.URL.Query())
rootIdentifier, registryIdentifier, _, _, _, _ := oci.ExtractPathVars(path, paramMap)
var access []registryauth.Access
access = registryauth.AppendAccess(access, r.Method, rootIdentifier+"/"+registryIdentifier)

View File

@ -0,0 +1,29 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package middleware
import (
"net/http"
"github.com/harness/gitness/registry/request"
)
// StoreOriginalURL stores the original URL in the context.
func StoreOriginalURL(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := request.WithOriginalURL(r.Context(), r.URL.Path)
next.ServeHTTP(w, r.WithContext(ctx))
})
}

View File

@ -71,7 +71,9 @@ func NewOCIHandler(handlerV2 *oci.Handler) RegistryOCIHandler {
http.MethodGet: NewHandlerBlock2(handlerV2.GetReferrers, false),
},
}
r.Route("/v2", func(r chi.Router) {
r.Use(middleware.StoreOriginalURL)
r.Use(middlewareauthn.Attempt(handlerV2.Authenticator))
r.Get("/token", func(w http.ResponseWriter, req *http.Request) {
handlerV2.GetToken(w, req)

View File

@ -70,3 +70,13 @@ func TrimURLScheme(urlStr string) string {
// Reconstruct the URL string without the scheme
return strings.TrimPrefix(u.String(), "//")
}
func ExtractFirstQueryParams(queryParams url.Values) map[string]string {
queryMap := make(map[string]string)
for key, values := range queryParams {
if len(values) > 0 {
queryMap[key] = values[0]
}
}
return queryMap
}

View File

@ -19,6 +19,7 @@ import (
)
type BaseInfo struct {
PathRoot string
ParentID int64
RootIdentifier string
RootParentID int64

View File

@ -776,7 +776,7 @@ func (r *LocalRegistry) PutManifest(
}
// Construct a canonical url for the uploaded manifest.
name, _ := reference.WithName(fmt.Sprintf("%s/%s", artInfo.RegIdentifier, artInfo.Image))
name, _ := reference.WithName(fmt.Sprintf("%s/%s/%s", artInfo.PathRoot, artInfo.RegIdentifier, artInfo.Image))
canonicalRef, err := reference.WithDigest(name, d)
if err != nil {
errs = append(errs, errcode.ErrCodeUnknown.WithDetail(err))
@ -1414,7 +1414,7 @@ func writeBlobCreatedHeaders(
headers *commons.ResponseHeaders,
info pkg.RegistryInfo,
) error {
path, err := reference.WithName(fmt.Sprintf("%s/%s/%s", info.RootIdentifier, info.RegIdentifier, info.Image))
path, err := reference.WithName(fmt.Sprintf("%s/%s/%s", info.PathRoot, info.RegIdentifier, info.Image))
if err != nil {
return err
}
@ -1458,7 +1458,7 @@ func blobUploadResponse(
return err
}
image := info.Image
path, err := reference.WithName(fmt.Sprintf("%s/%s/%s", info.RootIdentifier, repoKey, image))
path, err := reference.WithName(fmt.Sprintf("%s/%s/%s", info.PathRoot, repoKey, image))
if err != nil {
return err
}

View File

@ -213,7 +213,7 @@ func (r *RemoteRegistry) ManifestExist(
localRegistryIdentifier := ExtractRegistryIdentifierFromPath(artInfo.Path)
responseHeaders.Code = http.StatusMovedPermanently
responseHeaders.Headers = map[string]string{
"Location": defaultManifestURL(artInfo.RootIdentifier, localRegistryIdentifier, artInfo.Image,
"Location": defaultManifestURL(artInfo.PathRoot, localRegistryIdentifier, artInfo.Image,
registryInfo),
}
return responseHeaders, descriptor, manifestResult, errs
@ -304,7 +304,7 @@ func (r *RemoteRegistry) PullManifest(
localRegistryIdentifier := ExtractRegistryIdentifierFromPath(artInfo.Path)
responseHeaders.Code = http.StatusMovedPermanently
responseHeaders.Headers = map[string]string{
"Location": defaultManifestURL(artInfo.RootIdentifier, localRegistryIdentifier, artInfo.Image,
"Location": defaultManifestURL(artInfo.PathRoot, localRegistryIdentifier, artInfo.Image,
registryInfo),
}
return responseHeaders, descriptor, manifestResult, errs
@ -424,7 +424,7 @@ func (r *RemoteRegistry) fetchBlobInternal(
localRegistryIdentifier := ExtractRegistryIdentifierFromPath(info.Path)
responseHeaders.Code = http.StatusMovedPermanently
responseHeaders.Headers = map[string]string{
"Location": defaultBlobURL(info.RootIdentifier, localRegistryIdentifier, info.Image, info.Digest),
"Location": defaultBlobURL(info.PathRoot, localRegistryIdentifier, info.Image, info.Digest),
}
return responseHeaders, fr, size, readCloser, redirectURL, errs
}

View File

@ -0,0 +1,37 @@
// Copyright 2023 Harness, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package request
import (
"context"
"github.com/rs/zerolog/log"
)
type contextKey string
const OriginalURLKey contextKey = "originalURL"
func OriginalURLFrom(ctx context.Context) string {
originalURL, ok := ctx.Value(OriginalURLKey).(string)
if !ok {
log.Ctx(ctx).Warn().Msg("Original URL not found in context")
}
return originalURL
}
func WithOriginalURL(parent context.Context, originalURL string) context.Context {
return context.WithValue(parent, OriginalURLKey, originalURL)
}