diff --git a/api/http/handler/extensions/handler.go b/api/http/handler/extensions/handler.go
index b7ebbd06b..bba32f389 100644
--- a/api/http/handler/extensions/handler.go
+++ b/api/http/handler/extensions/handler.go
@@ -23,7 +23,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
}
h.Handle("/extensions",
- bouncer.AdministratorAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
+ bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
h.Handle("/extensions",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost)
h.Handle("/extensions/{id}",
diff --git a/api/http/handler/registries/handler.go b/api/http/handler/registries/handler.go
index a8f3ae24c..26edebd15 100644
--- a/api/http/handler/registries/handler.go
+++ b/api/http/handler/registries/handler.go
@@ -18,6 +18,7 @@ func hideFields(registry *portainer.Registry) {
// Handler is the HTTP handler used to handle registry operations.
type Handler struct {
*mux.Router
+ requestBouncer *security.RequestBouncer
RegistryService portainer.RegistryService
ExtensionService portainer.ExtensionService
FileService portainer.FileService
@@ -27,7 +28,8 @@ type Handler struct {
// NewHandler creates a handler to manage registry operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
- Router: mux.NewRouter(),
+ Router: mux.NewRouter(),
+ requestBouncer: bouncer,
}
h.Handle("/registries",
@@ -35,7 +37,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
h.Handle("/registries",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.registryList))).Methods(http.MethodGet)
h.Handle("/registries/{id}",
- bouncer.AdministratorAccess(httperror.LoggerHandler(h.registryInspect))).Methods(http.MethodGet)
+ bouncer.RestrictedAccess(httperror.LoggerHandler(h.registryInspect))).Methods(http.MethodGet)
h.Handle("/registries/{id}",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.registryUpdate))).Methods(http.MethodPut)
h.Handle("/registries/{id}/access",
@@ -45,7 +47,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
h.Handle("/registries/{id}",
bouncer.AdministratorAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
h.PathPrefix("/registries/{id}/v2").Handler(
- bouncer.AdministratorAccess(httperror.LoggerHandler(h.proxyRequestsToRegistryAPI)))
+ bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToRegistryAPI)))
return h
}
diff --git a/api/http/handler/registries/proxy.go b/api/http/handler/registries/proxy.go
index b83bcc549..844dc7e79 100644
--- a/api/http/handler/registries/proxy.go
+++ b/api/http/handler/registries/proxy.go
@@ -24,6 +24,11 @@ func (handler *Handler) proxyRequestsToRegistryAPI(w http.ResponseWriter, r *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
}
+ err = handler.requestBouncer.RegistryAccess(r, registry)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access registry", portainer.ErrEndpointAccessDenied}
+ }
+
extension, err := handler.ExtensionService.Extension(portainer.RegistryManagementExtension)
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Registry management extension is not enabled", err}
diff --git a/api/http/handler/registries/registry_inspect.go b/api/http/handler/registries/registry_inspect.go
index 96cdf84ac..1dd9a3ffb 100644
--- a/api/http/handler/registries/registry_inspect.go
+++ b/api/http/handler/registries/registry_inspect.go
@@ -23,6 +23,11 @@ func (handler *Handler) registryInspect(w http.ResponseWriter, r *http.Request)
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
}
+ err = handler.requestBouncer.RegistryAccess(r, registry)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access registry", portainer.ErrEndpointAccessDenied}
+ }
+
hideFields(registry)
return response.JSON(w, registry)
}
diff --git a/api/http/security/authorization.go b/api/http/security/authorization.go
index 6ffa3a0e4..4a0bac587 100644
--- a/api/http/security/authorization.go
+++ b/api/http/security/authorization.go
@@ -153,10 +153,10 @@ func authorizedEndpointAccess(endpoint *portainer.Endpoint, endpointGroup *porta
return true
}
-// AuthorizedEndpointGroupAccess ensure that the user can access the specified endpoint group.
+// authorizedEndpointGroupAccess ensure that the user can access the specified endpoint group.
// It will check if the user is part of the authorized users or part of a team that is
// listed in the authorized teams.
-func AuthorizedEndpointGroupAccess(endpointGroup *portainer.EndpointGroup, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
+func authorizedEndpointGroupAccess(endpointGroup *portainer.EndpointGroup, userID portainer.UserID, memberships []portainer.TeamMembership) bool {
return authorizedAccess(userID, memberships, endpointGroup.AuthorizedUsers, endpointGroup.AuthorizedTeams)
}
diff --git a/api/http/security/bouncer.go b/api/http/security/bouncer.go
index de0c75523..fa88612a8 100644
--- a/api/http/security/bouncer.go
+++ b/api/http/security/bouncer.go
@@ -111,6 +111,31 @@ func (bouncer *RequestBouncer) EndpointAccess(r *http.Request, endpoint *portain
return nil
}
+// RegistryAccess retrieves the JWT token from the request context and verifies
+// that the user can access the specified registry.
+// An error is returned when access is denied.
+func (bouncer *RequestBouncer) RegistryAccess(r *http.Request, registry *portainer.Registry) error {
+ tokenData, err := RetrieveTokenData(r)
+ if err != nil {
+ return err
+ }
+
+ if tokenData.Role == portainer.AdministratorRole {
+ return nil
+ }
+
+ memberships, err := bouncer.teamMembershipService.TeamMembershipsByUserID(tokenData.ID)
+ if err != nil {
+ return err
+ }
+
+ if !AuthorizedRegistryAccess(registry, tokenData.ID, memberships) {
+ return portainer.ErrEndpointAccessDenied
+ }
+
+ return nil
+}
+
// mwSecureHeaders provides secure headers middleware for handlers.
func mwSecureHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
diff --git a/api/http/security/filter.go b/api/http/security/filter.go
index 878a66689..4d44043b3 100644
--- a/api/http/security/filter.go
+++ b/api/http/security/filter.go
@@ -124,7 +124,7 @@ func FilterEndpointGroups(endpointGroups []portainer.EndpointGroup, context *Res
filteredEndpointGroups = make([]portainer.EndpointGroup, 0)
for _, group := range endpointGroups {
- if AuthorizedEndpointGroupAccess(&group, context.UserID, context.UserMemberships) {
+ if authorizedEndpointGroupAccess(&group, context.UserID, context.UserMemberships) {
filteredEndpointGroups = append(filteredEndpointGroups, group)
}
}
diff --git a/app/extensions/registry-management/views/repositories/registryRepositories.html b/app/extensions/registry-management/views/repositories/registryRepositories.html
index 6ca3664c7..5e3210ca7 100644
--- a/app/extensions/registry-management/views/repositories/registryRepositories.html
+++ b/app/extensions/registry-management/views/repositories/registryRepositories.html
@@ -5,7 +5,7 @@