Compare commits

...

6 Commits

11 changed files with 406 additions and 37 deletions

View File

@@ -1,8 +1,11 @@
package migrator
import (
"fmt"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/internal/stackutils"
)
@@ -38,3 +41,38 @@ func (m *Migrator) updateStackResourceControlToDB27() error {
return nil
}
func (m *Migrator) updateRegistriesToDB27() error {
registries, err := m.registryService.Registries()
if err != nil {
return err
}
endpoints, err := m.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
for _, registry := range registries {
userIDs := []portainer.UserID{}
for id := range registry.UserAccessPolicies {
userIDs = append(userIDs, portainer.UserID(id))
}
teamIDs := []portainer.TeamID{}
for id := range registry.TeamAccessPolicies {
teamIDs = append(teamIDs, portainer.TeamID(id))
}
resourceControl := authorization.NewRestrictedResourceControl(
fmt.Sprintf("%d-%d", int(registry.ID), int(endpoint.ID)),
portainer.RegistryResourceControl,
userIDs,
teamIDs,
)
m.resourceControlService.CreateResourceControl(resourceControl)
}
}
return nil
}

View File

@@ -1,6 +1,7 @@
package registries
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
@@ -8,6 +9,7 @@ import (
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
func hideFields(registry *portainer.Registry) {
@@ -47,3 +49,68 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
bouncer.AdminAccess(httperror.LoggerHandler(h.proxyRequestsToGitlabAPIWithoutRegistry)))
return h
}
func (handler *Handler) userIsAdmin(userID portainer.UserID) (bool, error) {
user, err := handler.DataStore.User().User(userID)
if err != nil {
return false, err
}
isAdmin := user.Role == portainer.AdministratorRole
return isAdmin, nil
}
func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) {
isAdmin := user.Role == portainer.AdministratorRole
return isAdmin, nil
}
func (handler *Handler) userCanCreateRegistry(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) {
user, err := handler.DataStore.User().User(securityContext.UserID)
if err != nil {
return false, err
}
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
}
func (handler *Handler) userCanAccessRegistry(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) {
user, err := handler.DataStore.User().User(securityContext.UserID)
if err != nil {
return false, err
}
userTeamIDs := make([]portainer.TeamID, 0)
for _, membership := range securityContext.UserMemberships {
userTeamIDs = append(userTeamIDs, membership.TeamID)
}
if resourceControl != nil && authorization.UserCanAccessResource(securityContext.UserID, userTeamIDs, resourceControl) {
return true, nil
}
return handler.userIsAdminOrEndpointAdmin(user, endpointID)
}
func (handler *Handler) computeRegistryResourceControlID(registryID portainer.RegistryID, endpointID portainer.EndpointID) string {
return fmt.Sprintf("%d-%d", int(registryID), int(endpointID))
}
func (handler *Handler) filterRegistries(registries []portainer.Registry, resourceControls []portainer.ResourceControl, endpointID portainer.EndpointID) []portainer.Registry {
if endpointID == 0 {
return registries
}
filteredRegistries := make([]portainer.Registry, 0, len(registries))
for _, registry := range registries {
for _, resourceControl := range resourceControls {
if resourceControl.ResourceID == handler.computeRegistryResourceControlID(registry.ID, endpointID) {
filteredRegistries = append(filteredRegistries, registry)
}
}
}
return filteredRegistries
}

View File

@@ -10,6 +10,8 @@ import (
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
)
type registryConfigurePayload struct {
@@ -98,12 +100,6 @@ func (handler *Handler) registryConfigure(w http.ResponseWriter, r *http.Request
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
}
payload := &registryConfigurePayload{}
err = payload.Validate(r)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
@@ -111,6 +107,47 @@ func (handler *Handler) registryConfigure(w http.ResponseWriter, r *http.Request
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
}
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the registry inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the registry inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the registry", err}
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
access, err := handler.userCanAccessRegistry(securityContext, endpoint.ID, resourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate registry access", err}
}
if !access {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", httperrors.ErrResourceAccessDenied}
}
payload := &registryConfigurePayload{}
err = payload.Validate(r)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
registry.ManagementConfiguration = &portainer.RegistryManagementConfiguration{
Type: registry.Type,
}

View File

@@ -9,6 +9,9 @@ import (
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
type registryCreatePayload struct {
@@ -64,6 +67,18 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
registry := &portainer.Registry{
Type: portainer.RegistryType(payload.Type),
Name: payload.Name,
@@ -76,11 +91,54 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
Gitlab: payload.Gitlab,
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
}
canCreate, err := handler.userCanCreateRegistry(securityContext, portainer.EndpointID(endpointID))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate Registry creation", err}
}
if !canCreate {
errMsg := "Registry creation is disabled for non-admin users"
return &httperror.HandlerError{http.StatusForbidden, errMsg, errors.New(errMsg)}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
tokenData, err := security.RetrieveTokenData(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user details from authentication token", err}
}
err = handler.DataStore.Registry().CreateRegistry(registry)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the registry inside the database", err}
}
var resourceControl *portainer.ResourceControl
isAdmin, err := handler.userIsAdmin(tokenData.ID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
}
if isAdmin {
resourceControl = authorization.NewAdministratorsOnlyResourceControl(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
} else {
resourceControl = authorization.NewPrivateResourceControl(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl, tokenData.ID)
}
err = handler.DataStore.ResourceControl().CreateResourceControl(resourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist resource control inside the database", err}
}
hideFields(registry)
return response.JSON(w, registry)
}

View File

@@ -8,6 +8,9 @@ import (
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
)
// @id RegistryDelete
@@ -28,13 +31,48 @@ func (handler *Handler) registryDelete(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
}
_, err = handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
if err == errors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
}
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the registry inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the registry inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the registry", err}
}
access, err := handler.userCanAccessRegistry(securityContext, endpoint.ID, resourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate registry access", err}
}
if !access {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", httperrors.ErrResourceAccessDenied}
}
err = handler.DataStore.Registry().DeleteRegistry(portainer.RegistryID(registryID))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the registry from the database", err}

View File

@@ -10,6 +10,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api/http/security"
)
// @id RegistryInspect
@@ -44,6 +45,45 @@ func (handler *Handler) registryInspect(w http.ResponseWriter, r *http.Request)
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access registry", errors.ErrEndpointAccessDenied}
}
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the registry", err}
}
access, err := handler.userCanAccessRegistry(securityContext, endpoint.ID, resourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate registry access", err}
}
if !access {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", errors.ErrResourceAccessDenied}
}
if resourceControl != nil {
registry.ResourceControl = resourceControl
}
hideFields(registry)
return response.JSON(w, registry)
}

View File

@@ -4,8 +4,12 @@ import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
// @id RegistryList
@@ -21,21 +25,54 @@ import (
// @failure 500 "Server error"
// @router /registries [get]
func (handler *Handler) registryList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
registries, err := handler.DataStore.Registry().Registries()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
}
resourceControls, err := handler.DataStore.ResourceControl().ResourceControls()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve resource controls from the database", err}
}
registries = handler.filterRegistries(registries, resourceControls, endpoint.ID)
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
filteredRegistries := security.FilterRegistries(registries, securityContext)
registries = authorization.DecorateRegistries(registries, resourceControls)
for idx := range filteredRegistries {
hideFields(&filteredRegistries[idx])
if !securityContext.IsAdmin {
user, err := handler.DataStore.User().User(securityContext.UserID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user information from the database", err}
}
userTeamIDs := make([]portainer.TeamID, 0)
for _, membership := range securityContext.UserMemberships {
userTeamIDs = append(userTeamIDs, membership.TeamID)
}
registries = authorization.FilterAuthorizedRegistries(registries, user, userTeamIDs)
}
return response.JSON(w, filteredRegistries)
for idx := range registries {
hideFields(&registries[idx])
}
return response.JSON(w, registries)
}

View File

@@ -9,6 +9,8 @@ import (
"github.com/portainer/libhttp/response"
portainer "github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
)
type registryUpdatePayload struct {
@@ -52,12 +54,6 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
}
var payload registryUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
registry, err := handler.DataStore.Registry().Registry(portainer.RegistryID(registryID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
@@ -65,6 +61,47 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
}
endpointID, err := request.RetrieveNumericQueryParameter(r, "endpointId", false)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the registry inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the registry inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
resourceControl, err := handler.DataStore.ResourceControl().ResourceControlByResourceIDAndType(handler.computeRegistryResourceControlID(registry.ID, endpoint.ID), portainer.RegistryResourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the registry", err}
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
access, err := handler.userCanAccessRegistry(securityContext, endpoint.ID, resourceControl)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify user authorizations to validate registry access", err}
}
if !access {
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", httperrors.ErrResourceAccessDenied}
}
var payload registryUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
if payload.Name != nil {
registry.Name = *payload.Name
}
@@ -102,14 +139,6 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
}
}
if payload.UserAccessPolicies != nil {
registry.UserAccessPolicies = payload.UserAccessPolicies
}
if payload.TeamAccessPolicies != nil {
registry.TeamAccessPolicies = payload.TeamAccessPolicies
}
err = handler.DataStore.Registry().UpdateRegistry(registry.ID, registry)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist registry changes inside the database", err}

View File

@@ -1,6 +1,7 @@
package authorization
import (
"fmt"
"strconv"
portainer "github.com/portainer/portainer/api"
@@ -106,6 +107,20 @@ func NewRestrictedResourceControl(resourceIdentifier string, resourceType portai
}
}
// DecorateRegistries will iterate through a list of registries, check for an associated resource control for each
// registry and decorate the registry element if a resource control is found.
func DecorateRegistries(registries []portainer.Registry, resourceControls []portainer.ResourceControl) []portainer.Registry {
for idx, registry := range registries {
resourceControl := GetResourceControlByResourceIDAndType(fmt.Sprintf("%d", registry.ID), portainer.RegistryResourceControl, resourceControls)
if resourceControl != nil {
registries[idx].ResourceControl = resourceControl
}
}
return registries
}
// DecorateStacks will iterate through a list of stacks, check for an associated resource control for each
// stack and decorate the stack element if a resource control is found.
func DecorateStacks(stacks []portainer.Stack, resourceControls []portainer.ResourceControl) []portainer.Stack {
@@ -134,6 +149,19 @@ func DecorateCustomTemplates(templates []portainer.CustomTemplate, resourceContr
return templates
}
// FilterAuthorizedRegistries returns a list of decorated registries filtered through resource control access checks.
func FilterAuthorizedRegistries(registries []portainer.Registry, user *portainer.User, userTeamIDs []portainer.TeamID) []portainer.Registry {
authorizedRegistries := make([]portainer.Registry, 0)
for _, registry := range registries {
if registry.ResourceControl != nil && UserCanAccessResource(user.ID, userTeamIDs, registry.ResourceControl) {
authorizedRegistries = append(authorizedRegistries, registry)
}
}
return authorizedRegistries
}
// FilterAuthorizedStacks returns a list of decorated stacks filtered through resource control access checks.
func FilterAuthorizedStacks(stacks []portainer.Stack, user *portainer.User, userTeamIDs []portainer.TeamID) []portainer.Stack {
authorizedStacks := make([]portainer.Stack, 0)

View File

@@ -508,8 +508,12 @@ type (
Password string `json:"Password,omitempty" example:"registry_password"`
ManagementConfiguration *RegistryManagementConfiguration `json:"ManagementConfiguration"`
Gitlab GitlabRegistryData `json:"Gitlab"`
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
ResourceControl *ResourceControl `json:"ResourceControl"`
// Deprecated fields
// Deprecated in DBVersion == 26
UserAccessPolicies UserAccessPolicies `json:"UserAccessPolicies"`
TeamAccessPolicies TeamAccessPolicies `json:"TeamAccessPolicies"`
// Deprecated fields
// Deprecated in DBVersion == 18
@@ -546,7 +550,7 @@ type (
// List of Docker resources that will inherit this access control
SubResourceIDs []string `json:"SubResourceIds" example:"617c5f22bb9b023d6daab7cba43a57576f83492867bc767d1c59416b065e5f08"`
// Type of Docker resource. Valid values are: 1- container, 2 -service
// 3 - volume, 4 - secret, 5 - stack, 6 - config or 7 - custom template
// 3 - volume, 4 - secret, 5 - stack, 6 - config, 7 - custom template or 8 - registry
Type ResourceControlType `json:"Type" example:"1"`
UserAccesses []UserResourceAccess `json:"UserAccesses" example:""`
TeamAccesses []TeamResourceAccess `json:"TeamAccesses" example:""`
@@ -1487,6 +1491,8 @@ const (
ConfigResourceControl
// CustomTemplateResourceControl represents a resource control associated to a custom template
CustomTemplateResourceControl
// RegistryResourceControl represents a resource control associated to a registry
RegistryResourceControl
)
const (

View File

@@ -7,15 +7,6 @@
<rd-header-content>Registry management</rd-header-content>
</rd-header>
<information-panel title-text="Registry usage">
<span class="small">
<p class="text-muted">
<i class="fa fa-info-circle blue-icon" aria-hidden="true" style="margin-right: 2px;"></i>
DockerHub credentials and registries can only be used with Docker endpoints at the time.
</p>
</span>
</information-panel>
<div class="row" ng-if="dockerhub && isAdmin">
<div class="col-sm-12">
<rd-widget>