// 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 metadata import ( "context" "fmt" "net/http" apiauth "github.com/harness/gitness/app/api/auth" "github.com/harness/gitness/app/api/request" "github.com/harness/gitness/app/auth" "github.com/harness/gitness/audit" "github.com/harness/gitness/registry/app/api/openapi/contracts/artifact" "github.com/harness/gitness/registry/types" types2 "github.com/harness/gitness/types" gitnessenum "github.com/harness/gitness/types/enum" "github.com/rs/zerolog/log" ) func (c *APIController) ModifyRegistry( ctx context.Context, r artifact.ModifyRegistryRequestObject, ) (artifact.ModifyRegistryResponseObject, error) { regInfo, err := c.GetRegistryRequestBaseInfo(ctx, "", string(r.RegistryRef)) if err != nil { return artifact.ModifyRegistry400JSONResponse{ BadRequestJSONResponse: artifact.BadRequestJSONResponse( *GetErrorResponse(http.StatusBadRequest, err.Error()), ), }, err } space, err := c.SpaceStore.FindByRef(ctx, regInfo.ParentRef) if err != nil { return artifact.ModifyRegistry400JSONResponse{ BadRequestJSONResponse: artifact.BadRequestJSONResponse( *GetErrorResponse(http.StatusBadRequest, err.Error()), ), }, err } session, _ := request.AuthSessionFrom(ctx) permissionChecks := GetPermissionChecks(space, regInfo.RegistryIdentifier, gitnessenum.PermissionRegistryEdit) if err = apiauth.CheckRegistry( ctx, c.Authorizer, session, permissionChecks..., ); err != nil { return artifact.ModifyRegistry403JSONResponse{ UnauthorizedJSONResponse: artifact.UnauthorizedJSONResponse( *GetErrorResponse(http.StatusForbidden, err.Error()), ), }, err } repoEntity, err := c.RegistryRepository.GetByParentIDAndName(ctx, regInfo.parentID, regInfo.RegistryIdentifier) if err != nil { return throwModifyRegistry500Error(err), err } if string(repoEntity.Type) == string(artifact.RegistryTypeVIRTUAL) { return c.updateVirtualRegistry(ctx, r, repoEntity, err, regInfo, session) } upstreamproxyEntity, err := c.UpstreamProxyStore.GetByRegistryIdentifier( ctx, regInfo.parentID, regInfo.RegistryIdentifier, ) if len(upstreamproxyEntity.RepoKey) == 0 { return artifact.ModifyRegistry404JSONResponse{ NotFoundJSONResponse: artifact.NotFoundJSONResponse( *GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"), ), }, nil } if err != nil { return throwModifyRegistry500Error(err), err } registry, upstreamproxy, err := c.UpdateUpstreamProxyEntity( ctx, artifact.RegistryRequest(*r.Body), regInfo.parentID, regInfo.rootIdentifierID, upstreamproxyEntity, ) registry.ID = repoEntity.ID upstreamproxy.ID = upstreamproxyEntity.ID upstreamproxy.RegistryID = repoEntity.ID if err != nil { return throwModifyRegistry500Error(err), err } err = c.tx.WithTx( ctx, func(ctx context.Context) error { err = c.updateRegistryWithAudit(ctx, repoEntity, registry, session.Principal, regInfo.ParentRef) if err != nil { return fmt.Errorf("failed to update registry: %w", err) } err = c.updateUpstreamProxyWithAudit( ctx, upstreamproxy, session.Principal, regInfo.ParentRef, registry.Name, ) if err != nil { return fmt.Errorf("failed to update upstream proxy: %w", err) } return nil }, ) if err != nil { return throwModifyRegistry500Error(err), err } modifiedRepoEntity, err := c.UpstreamProxyStore.Get(ctx, upstreamproxyEntity.RegistryID) if err != nil { return throwModifyRegistry500Error(err), err } return artifact.ModifyRegistry200JSONResponse{ RegistryResponseJSONResponse: *CreateUpstreamProxyResponseJSONResponse(modifiedRepoEntity), }, nil } func (c *APIController) updateVirtualRegistry( ctx context.Context, r artifact.ModifyRegistryRequestObject, repoEntity *types.Registry, err error, regInfo *RegistryRequestBaseInfo, session *auth.Session, ) (artifact.ModifyRegistryResponseObject, error) { if len(repoEntity.Name) == 0 { return artifact.ModifyRegistry404JSONResponse{ NotFoundJSONResponse: artifact.NotFoundJSONResponse( *GetErrorResponse(http.StatusNotFound, "registry doesn't exist with this key"), ), }, nil } if err != nil { return throwModifyRegistry500Error(err), err } registry, err := UpdateRepoEntity( artifact.RegistryRequest(*r.Body), repoEntity.ParentID, repoEntity.RootParentID, repoEntity, ) if err != nil { return artifact.ModifyRegistry400JSONResponse{ BadRequestJSONResponse: artifact.BadRequestJSONResponse( *GetErrorResponse(http.StatusInternalServerError, err.Error()), ), }, nil } err = c.setUpstreamProxyIDs(ctx, registry, artifact.RegistryRequest(*r.Body), regInfo.parentID) if err != nil { return throwModifyRegistry500Error(err), nil } err = c.updateRegistryWithAudit(ctx, repoEntity, registry, session.Principal, regInfo.ParentRef) if err != nil { return throwModifyRegistry500Error(err), nil } err = c.updateCleanupPolicy(ctx, r.Body, registry.ID) if err != nil { return throwModifyRegistry500Error(err), nil } modifiedRepoEntity, err := c.RegistryRepository.Get(ctx, registry.ID) if err != nil { return throwModifyRegistry500Error(err), nil } cleanupPolicies, err := c.CleanupPolicyStore.GetByRegistryID(ctx, repoEntity.ID) if err != nil { return throwModifyRegistry500Error(err), nil } return artifact.ModifyRegistry200JSONResponse{ RegistryResponseJSONResponse: *CreateVirtualRepositoryResponse( modifiedRepoEntity, c.getUpstreamProxyKeys(ctx, modifiedRepoEntity.UpstreamProxies), cleanupPolicies, c.URLProvider.RegistryURL(ctx, regInfo.RootIdentifier, regInfo.RegistryIdentifier), ), }, nil } func (c *APIController) updateUpstreamProxyWithAudit( ctx context.Context, upstreamProxy *types.UpstreamProxyConfig, principal types2.Principal, parentRef string, registryName string, ) error { existingUpstreamProxy, err := c.UpstreamProxyStore.Get(ctx, upstreamProxy.RegistryID) if err != nil { log.Ctx(ctx).Warn().Msgf( "failed to fig upstream proxy config for: %d", upstreamProxy.RegistryID, ) } err = c.UpstreamProxyStore.Update(ctx, upstreamProxy) if err != nil { return err } if existingUpstreamProxy != nil { auditErr := c.AuditService.Log( ctx, principal, audit.NewResource(audit.ResourceTypeRegistryUpstreamProxy, registryName), audit.ActionUpdated, parentRef, audit.WithOldObject( audit.RegistryUpstreamProxyConfigObject{ ID: existingUpstreamProxy.ID, RegistryID: existingUpstreamProxy.RegistryID, Source: existingUpstreamProxy.Source, URL: existingUpstreamProxy.RepoURL, AuthType: existingUpstreamProxy.RepoAuthType, CreatedAt: existingUpstreamProxy.CreatedAt, UpdatedAt: existingUpstreamProxy.UpdatedAt, CreatedBy: existingUpstreamProxy.CreatedBy, UpdatedBy: existingUpstreamProxy.UpdatedBy, }, ), audit.WithNewObject( audit.RegistryUpstreamProxyConfigObject{ ID: upstreamProxy.ID, RegistryID: upstreamProxy.RegistryID, Source: upstreamProxy.Source, URL: upstreamProxy.URL, AuthType: upstreamProxy.AuthType, CreatedAt: upstreamProxy.CreatedAt, UpdatedAt: upstreamProxy.UpdatedAt, CreatedBy: upstreamProxy.CreatedBy, UpdatedBy: upstreamProxy.UpdatedBy, }, ), ) if auditErr != nil { log.Ctx(ctx).Warn().Msgf( "failed to insert audit log for update upstream proxy "+ "config operation: %s", auditErr, ) } } return err } func (c *APIController) updateRegistryWithAudit( ctx context.Context, oldRegistry *types.Registry, newRegistry *types.Registry, principal types2.Principal, parentRef string, ) error { err := c.RegistryRepository.Update(ctx, newRegistry) if err != nil { return err } auditErr := c.AuditService.Log( ctx, principal, audit.NewResource(audit.ResourceTypeRegistry, newRegistry.Name), audit.ActionUpdated, parentRef, audit.WithOldObject(oldRegistry), audit.WithNewObject(newRegistry), ) if auditErr != nil { log.Ctx(ctx).Warn().Msgf("failed to insert audit log for update registry operation: %s", auditErr) } return err } func throwModifyRegistry500Error(err error) artifact.ModifyRegistry500JSONResponse { return artifact.ModifyRegistry500JSONResponse{ InternalServerErrorJSONResponse: artifact.InternalServerErrorJSONResponse( *GetErrorResponse(http.StatusInternalServerError, err.Error()), ), } } func (c *APIController) updateCleanupPolicy( ctx context.Context, config *artifact.ModifyRegistryJSONRequestBody, registryID int64, ) error { existingCleanupPolicies, err := c.CleanupPolicyStore.GetIDsByRegistryID(ctx, registryID) if err != nil { return err } currentCleanupPolicyEntities := CreateCleanupPolicyEntity(config, registryID) err = c.CleanupPolicyStore.ModifyCleanupPolicies(ctx, currentCleanupPolicyEntities, existingCleanupPolicies) return err } func UpdateRepoEntity( dto artifact.RegistryRequest, parentID int64, rootParentID int64, existingRepo *types.Registry, ) (*types.Registry, error) { allowedPattern, blockedPattern, description, labels := getRepoEntityFields(dto) e := ValidatePackageTypeChange(string(existingRepo.PackageType), string(dto.PackageType)) if e != nil { return nil, e } e = ValidateRepoTypeChange(string(existingRepo.Type), string(dto.Config.Type)) if e != nil { return nil, e } e = ValidateIdentifierChange(existingRepo.Name, dto.Identifier) if e != nil { return nil, e } entity := &types.Registry{ Name: dto.Identifier, ID: existingRepo.ID, ParentID: parentID, RootParentID: rootParentID, Description: description, AllowedPattern: allowedPattern, BlockedPattern: blockedPattern, PackageType: existingRepo.PackageType, Type: existingRepo.Type, Labels: labels, CreatedAt: existingRepo.CreatedAt, } return entity, nil } //nolint:gocognit,cyclop func (c *APIController) UpdateUpstreamProxyEntity( ctx context.Context, dto artifact.RegistryRequest, parentID int64, rootParentID int64, u *types.UpstreamProxy, ) (*types.Registry, *types.UpstreamProxyConfig, error) { allowedPattern := []string{} if dto.AllowedPattern != nil { allowedPattern = *dto.AllowedPattern } blockedPattern := []string{} if dto.BlockedPattern != nil { blockedPattern = *dto.BlockedPattern } e := ValidatePackageTypeChange(string(u.PackageType), string(dto.PackageType)) if e != nil { return nil, nil, e } e = ValidateIdentifierChange(u.RepoKey, dto.Identifier) if e != nil { return nil, nil, e } repoEntity := &types.Registry{ ID: u.RegistryID, Name: dto.Identifier, ParentID: parentID, RootParentID: rootParentID, AllowedPattern: allowedPattern, BlockedPattern: blockedPattern, PackageType: dto.PackageType, Type: artifact.RegistryTypeUPSTREAM, CreatedAt: u.CreatedAt, } config, _ := dto.Config.AsUpstreamConfig() CleanURLPath(config.Url) upstreamProxyConfigEntity := &types.UpstreamProxyConfig{ URL: *config.Url, AuthType: string(config.AuthType), RegistryID: u.RegistryID, CreatedAt: u.CreatedAt, } if config.Source != nil && len(string(*config.Source)) > 0 { err := ValidateUpstreamSource(string(*config.Source)) if err != nil { return nil, nil, err } upstreamProxyConfigEntity.Source = string(*config.Source) } if string(artifact.UpstreamConfigSourceDockerhub) == string(*config.Source) { upstreamProxyConfigEntity.URL = "" } if u.ID != -1 { upstreamProxyConfigEntity.ID = u.ID } switch { case config.AuthType == artifact.AuthTypeUserPassword: res, err := config.Auth.AsUserPassword() if err != nil { return nil, nil, err } upstreamProxyConfigEntity.UserName = res.UserName if res.SecretIdentifier == nil { return nil, nil, fmt.Errorf("failed to create upstream proxy: secret_identifier missing") } if res.SecretSpacePath != nil && len(*res.SecretSpacePath) > 0 { upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretSpaceID(ctx, res.SecretSpacePath) if err != nil { return nil, nil, err } } else if res.SecretSpaceId != nil { upstreamProxyConfigEntity.SecretSpaceID = *res.SecretSpaceId } upstreamProxyConfigEntity.SecretIdentifier = *res.SecretIdentifier case config.AuthType == artifact.AuthTypeAccessKeySecretKey: res, err := config.Auth.AsAccessKeySecretKey() if err != nil { return nil, nil, err } switch { case res.AccessKey != nil && len(*res.AccessKey) > 0: upstreamProxyConfigEntity.UserName = *res.AccessKey case res.AccessKeySecretIdentifier == nil: return nil, nil, fmt.Errorf("failed to create upstream proxy: access_key_secret_identifier missing") default: if res.AccessKeySecretSpacePath != nil && len(*res.AccessKeySecretSpacePath) > 0 { upstreamProxyConfigEntity.UserNameSecretSpaceID, err = c.getSecretSpaceID(ctx, res.AccessKeySecretSpacePath) if err != nil { return nil, nil, err } } else if res.AccessKeySecretSpaceId != nil { upstreamProxyConfigEntity.UserNameSecretSpaceID = *res.AccessKeySecretSpaceId } upstreamProxyConfigEntity.UserNameSecretIdentifier = *res.AccessKeySecretIdentifier } if res.SecretKeySpacePath != nil && len(*res.SecretKeySpacePath) > 0 { upstreamProxyConfigEntity.SecretSpaceID, err = c.getSecretSpaceID(ctx, res.SecretKeySpacePath) if err != nil { return nil, nil, err } } else if res.SecretKeySpaceId != nil { upstreamProxyConfigEntity.SecretSpaceID = *res.SecretKeySpaceId } upstreamProxyConfigEntity.SecretIdentifier = res.SecretKeyIdentifier default: upstreamProxyConfigEntity.UserName = "" upstreamProxyConfigEntity.SecretIdentifier = "" upstreamProxyConfigEntity.SecretSpaceID = 0 } return repoEntity, upstreamProxyConfigEntity, nil }