1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-25 08:19:40 +02:00

feat(extensions): remove rbac extension (#4157)

* feat(extensions): remove rbac extension client code

* feat(extensions): remove server rbac code

* remove extensions code

* fix(notifications): remove error

* feat(extensions): remove authorizations service

* feat(rbac): deprecate fields

* fix(portainer): revert change

* fix(bouncer): remove rbac authorization check

* feat(sidebar): remove roles link

* fix(portainer): remove portainer module
This commit is contained in:
Chaim Lev-Ari 2020-08-11 08:41:37 +03:00 committed by GitHub
parent 8629738e34
commit 9d18d47194
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 98 additions and 3487 deletions

View file

@ -13,7 +13,6 @@ import (
"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/internal/authorization"
)
type authenticatePayload struct {
@ -79,11 +78,6 @@ func (handler *Handler) authenticateLDAP(w http.ResponseWriter, user *portainer.
log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error())
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
return handler.writeToken(w, user)
}
@ -103,9 +97,8 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
}
user := &portainer.User{
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(),
Username: username,
Role: portainer.StandardUserRole,
}
err = handler.DataStore.User().CreateUser(user)
@ -118,11 +111,6 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
log.Printf("Warning: unable to automatically add user into teams: %s\n", err.Error())
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
return handler.writeToken(w, user)
}

View file

@ -11,7 +11,6 @@ import (
"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/internal/authorization"
)
type oauthPayload struct {
@ -76,9 +75,8 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
if user == nil {
user = &portainer.User{
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(),
Username: username,
Role: portainer.StandardUserRole,
}
err = handler.DataStore.User().CreateUser(user)
@ -99,10 +97,6 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
}
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
return handler.writeToken(w, user)

View file

@ -9,7 +9,6 @@ import (
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
// Handler is the HTTP handler used to handle authentication operations.
@ -21,7 +20,6 @@ type Handler struct {
LDAPService portainer.LDAPService
OAuthService portainer.OAuthService
ProxyManager *proxy.Manager
AuthorizationService *authorization.Service
KubernetesTokenCacheManager *kubernetes.TokenCacheManager
}

View file

@ -39,10 +39,8 @@ func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
}
updateAuthorizations := false
for _, endpoint := range endpoints {
if endpoint.GroupID == portainer.EndpointGroupID(endpointGroupID) {
updateAuthorizations = true
endpoint.GroupID = portainer.EndpointGroupID(1)
err = handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
@ -56,13 +54,6 @@ func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Reque
}
}
if updateAuthorizations {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
for _, tagID := range endpointGroup.TagIDs {
tag, err := handler.DataStore.Tag().Tag(tagID)
if err != nil {

View file

@ -92,15 +92,12 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
}
}
updateAuthorizations := false
if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpointGroup.UserAccessPolicies) {
endpointGroup.UserAccessPolicies = payload.UserAccessPolicies
updateAuthorizations = true
}
if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpointGroup.TeamAccessPolicies) {
endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies
updateAuthorizations = true
}
err = handler.DataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, endpointGroup)
@ -108,13 +105,6 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint group changes inside the database", err}
}
if updateAuthorizations {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
if tagsChanged {
endpoints, err := handler.DataStore.Endpoint().Endpoints()
if err != nil {

View file

@ -7,14 +7,12 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
// Handler is the HTTP handler used to handle endpoint group operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
AuthorizationService *authorization.Service
DataStore portainer.DataStore
}
// NewHandler creates a handler to manage endpoint group operations.

View file

@ -24,7 +24,7 @@ func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.R
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -26,7 +26,7 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -26,7 +26,7 @@ func (handler *Handler) proxyRequestsToKubernetesAPI(w http.ResponseWriter, r *h
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -27,7 +27,7 @@ func (handler *Handler) proxyRequestsToStoridgeAPI(w http.ResponseWriter, r *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -445,15 +445,6 @@ func (handler *Handler) saveEndpointAndUpdateAuthorizations(endpoint *portainer.
return err
}
group, err := handler.DataStore.EndpointGroup().EndpointGroup(endpoint.GroupID)
if err != nil {
return err
}
if len(group.UserAccessPolicies) > 0 || len(group.TeamAccessPolicies) > 0 {
return handler.AuthorizationService.UpdateUsersAuthorizations()
}
for _, tagID := range endpoint.TagIDs {
tag, err := handler.DataStore.Tag().Tag(tagID)
if err != nil {

View file

@ -40,13 +40,6 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *
handler.ProxyManager.DeleteEndpointProxy(endpoint)
if len(endpoint.UserAccessPolicies) > 0 || len(endpoint.TeamAccessPolicies) > 0 {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
err = handler.DataStore.EndpointRelation().DeleteEndpointRelation(endpoint.ID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove endpoint relation from the database", err}

View file

@ -24,7 +24,7 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -126,15 +126,12 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
endpoint.Kubernetes = *payload.Kubernetes
}
updateAuthorizations := false
if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpoint.UserAccessPolicies) {
endpoint.UserAccessPolicies = payload.UserAccessPolicies
updateAuthorizations = true
}
if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpoint.TeamAccessPolicies) {
endpoint.TeamAccessPolicies = payload.TeamAccessPolicies
updateAuthorizations = true
}
if payload.Status != nil {
@ -226,13 +223,6 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint changes inside the database", err}
}
if updateAuthorizations {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
if (endpoint.Type == portainer.EdgeAgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnKubernetesEnvironment) && (groupIDChanged || tagsChanged) {
relation, err := handler.DataStore.EndpointRelation().EndpointRelation(endpoint.ID)
if err != nil {

View file

@ -5,7 +5,6 @@ import (
portainer "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"
"net/http"
@ -24,7 +23,6 @@ type Handler struct {
*mux.Router
requestBouncer *security.RequestBouncer
DataStore portainer.DataStore
AuthorizationService *authorization.Service
FileService portainer.FileService
ProxyManager *proxy.Manager
ReverseTunnelService portainer.ReverseTunnelService

View file

@ -1,117 +0,0 @@
package extensions
import (
portainer "github.com/portainer/portainer/api"
)
func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) {
tmp := policies[key]
tmp.RoleID = 4
policies[key] = tmp
}
func updateTeamAccessPolicyToReadOnlyRole(policies portainer.TeamAccessPolicies, key portainer.TeamID) {
tmp := policies[key]
tmp.RoleID = 4
policies[key] = tmp
}
func (handler *Handler) upgradeRBACData() error {
endpointGroups, err := handler.DataStore.EndpointGroup().EndpointGroups()
if err != nil {
return err
}
for _, endpointGroup := range endpointGroups {
for key := range endpointGroup.UserAccessPolicies {
updateUserAccessPolicyToReadOnlyRole(endpointGroup.UserAccessPolicies, key)
}
for key := range endpointGroup.TeamAccessPolicies {
updateTeamAccessPolicyToReadOnlyRole(endpointGroup.TeamAccessPolicies, key)
}
err := handler.DataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
if err != nil {
return err
}
}
endpoints, err := handler.DataStore.Endpoint().Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
for key := range endpoint.UserAccessPolicies {
updateUserAccessPolicyToReadOnlyRole(endpoint.UserAccessPolicies, key)
}
for key := range endpoint.TeamAccessPolicies {
updateTeamAccessPolicyToReadOnlyRole(endpoint.TeamAccessPolicies, key)
}
err := handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
return handler.AuthorizationService.UpdateUsersAuthorizations()
}
func updateUserAccessPolicyToNoRole(policies portainer.UserAccessPolicies, key portainer.UserID) {
tmp := policies[key]
tmp.RoleID = 0
policies[key] = tmp
}
func updateTeamAccessPolicyToNoRole(policies portainer.TeamAccessPolicies, key portainer.TeamID) {
tmp := policies[key]
tmp.RoleID = 0
policies[key] = tmp
}
func (handler *Handler) downgradeRBACData() error {
endpointGroups, err := handler.DataStore.EndpointGroup().EndpointGroups()
if err != nil {
return err
}
for _, endpointGroup := range endpointGroups {
for key := range endpointGroup.UserAccessPolicies {
updateUserAccessPolicyToNoRole(endpointGroup.UserAccessPolicies, key)
}
for key := range endpointGroup.TeamAccessPolicies {
updateTeamAccessPolicyToNoRole(endpointGroup.TeamAccessPolicies, key)
}
err := handler.DataStore.EndpointGroup().UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
if err != nil {
return err
}
}
endpoints, err := handler.DataStore.Endpoint().Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
for key := range endpoint.UserAccessPolicies {
updateUserAccessPolicyToNoRole(endpoint.UserAccessPolicies, key)
}
for key := range endpoint.TeamAccessPolicies {
updateTeamAccessPolicyToNoRole(endpoint.TeamAccessPolicies, key)
}
err := handler.DataStore.Endpoint().UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
}
return handler.AuthorizationService.UpdateUsersAuthorizations()
}

View file

@ -1,87 +0,0 @@
package extensions
import (
"errors"
"net/http"
"strconv"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
type extensionCreatePayload struct {
License string
}
func (payload *extensionCreatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.License) {
return errors.New("Invalid license")
}
return nil
}
func (handler *Handler) extensionCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload extensionCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
extensionIdentifier, err := strconv.Atoi(string(payload.License[0]))
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid license format", err}
}
extensionID := portainer.ExtensionID(extensionIdentifier)
extensions, err := handler.DataStore.Extension().Extensions()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions status from the database", err}
}
for _, existingExtension := range extensions {
if existingExtension.ID == extensionID && existingExtension.Enabled {
return &httperror.HandlerError{http.StatusConflict, "Unable to enable extension", errors.New("This extension is already enabled")}
}
}
extension := &portainer.Extension{
ID: extensionID,
}
extensionDefinitions, err := handler.ExtensionManager.FetchExtensionDefinitions()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extension definitions", err}
}
for _, def := range extensionDefinitions {
if def.ID == extension.ID {
extension.Version = def.Version
break
}
}
err = handler.ExtensionManager.EnableExtension(extension, payload.License)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to enable extension", err}
}
extension.Enabled = true
if extension.ID == portainer.RBACExtension {
err = handler.upgradeRBACData()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err}
}
}
err = handler.DataStore.Extension().Persist(extension)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err}
}
return response.Empty(w)
}

View file

@ -1,46 +0,0 @@
package extensions
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
)
// DELETE request on /api/extensions/:id
func (handler *Handler) extensionDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
extensionIdentifier, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid extension identifier route variable", err}
}
extensionID := portainer.ExtensionID(extensionIdentifier)
extension, err := handler.DataStore.Extension().Extension(extensionID)
if err == errors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a extension with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err}
}
err = handler.ExtensionManager.DisableExtension(extension)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete extension", err}
}
if extensionID == portainer.RBACExtension {
err = handler.downgradeRBACData()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err}
}
}
err = handler.DataStore.Extension().DeleteExtension(extensionID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete the extension from the database", err}
}
return response.Empty(w)
}

View file

@ -1,55 +0,0 @@
package extensions
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/client"
)
// GET request on /api/extensions/:id
func (handler *Handler) extensionInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
extensionIdentifier, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid extension identifier route variable", err}
}
extensionID := portainer.ExtensionID(extensionIdentifier)
definitions, err := handler.ExtensionManager.FetchExtensionDefinitions()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions informations", err}
}
localExtension, err := handler.DataStore.Extension().Extension(extensionID)
if err != nil && err != errors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extension information from the database", err}
}
var extension portainer.Extension
var extensionDefinition portainer.Extension
for _, definition := range definitions {
if definition.ID == extensionID {
extensionDefinition = definition
break
}
}
if localExtension == nil {
extension = extensionDefinition
} else {
extension = *localExtension
}
mergeExtensionAndDefinition(&extension, &extensionDefinition)
description, _ := client.Get(extension.DescriptionURL, 5)
extension.Description = string(description)
return response.JSON(w, extension)
}

View file

@ -1,30 +0,0 @@
package extensions
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
)
// GET request on /api/extensions?store=<store>
func (handler *Handler) extensionList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
fetchManifestInformation, _ := request.RetrieveBooleanQueryParameter(r, "store", true)
extensions, err := handler.DataStore.Extension().Extensions()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions from the database", err}
}
if fetchManifestInformation {
definitions, err := handler.ExtensionManager.FetchExtensionDefinitions()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve extensions informations", err}
}
extensions = mergeExtensionsAndDefinitions(extensions, definitions)
}
return response.JSON(w, extensions)
}

View file

@ -1,58 +0,0 @@
package extensions
import (
"errors"
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
)
type extensionUpdatePayload struct {
Version string
}
func (payload *extensionUpdatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Version) {
return errors.New("Invalid extension version")
}
return nil
}
func (handler *Handler) extensionUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
extensionIdentifier, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid extension identifier route variable", err}
}
extensionID := portainer.ExtensionID(extensionIdentifier)
var payload extensionUpdatePayload
err = request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
extension, err := handler.DataStore.Extension().Extension(extensionID)
if err == bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a extension with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err}
}
err = handler.ExtensionManager.UpdateExtension(extension, payload.Version)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update extension", err}
}
err = handler.DataStore.Extension().Persist(extension)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err}
}
return response.Empty(w)
}

View file

@ -1,76 +0,0 @@
package extensions
import (
"errors"
"net/http"
"strconv"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
type extensionUploadPayload struct {
License string
ExtensionArchive []byte
ArchiveFileName string
}
func (payload *extensionUploadPayload) Validate(r *http.Request) error {
license, err := request.RetrieveMultiPartFormValue(r, "License", false)
if err != nil {
return errors.New("Invalid license")
}
payload.License = license
fileData, fileName, err := request.RetrieveMultiPartFormFile(r, "file")
if err != nil {
return errors.New("Invalid extension archive file. Ensure that the file is uploaded correctly")
}
payload.ExtensionArchive = fileData
payload.ArchiveFileName = fileName
return nil
}
func (handler *Handler) extensionUpload(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
payload := &extensionUploadPayload{}
err := payload.Validate(r)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
extensionIdentifier, err := strconv.Atoi(string(payload.License[0]))
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid license format", err}
}
extensionID := portainer.ExtensionID(extensionIdentifier)
extension := &portainer.Extension{
ID: extensionID,
}
_ = handler.ExtensionManager.DisableExtension(extension)
err = handler.ExtensionManager.InstallExtension(extension, payload.License, payload.ArchiveFileName, payload.ExtensionArchive)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to install extension", err}
}
extension.Enabled = true
if extension.ID == portainer.RBACExtension {
err = handler.upgradeRBACData()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err}
}
}
err = handler.DataStore.Extension().Persist(extension)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err}
}
return response.Empty(w)
}

View file

@ -1,84 +0,0 @@
package extensions
import (
"net/http"
"github.com/coreos/go-semver/semver"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
// Handler is the HTTP handler used to handle extension operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
ExtensionManager portainer.ExtensionManager
AuthorizationService *authorization.Service
}
// NewHandler creates a handler to manage extension operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/extensions",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
h.Handle("/extensions",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost)
h.Handle("/extensions/upload",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionUpload))).Methods(http.MethodPost)
h.Handle("/extensions/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionInspect))).Methods(http.MethodGet)
h.Handle("/extensions/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionDelete))).Methods(http.MethodDelete)
h.Handle("/extensions/{id}/update",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionUpdate))).Methods(http.MethodPost)
return h
}
func mergeExtensionsAndDefinitions(extensions, definitions []portainer.Extension) []portainer.Extension {
for _, definition := range definitions {
foundInDB := false
for idx, extension := range extensions {
if extension.ID == definition.ID {
foundInDB = true
mergeExtensionAndDefinition(&extensions[idx], &definition)
break
}
}
if !foundInDB {
extensions = append(extensions, definition)
}
}
return extensions
}
func mergeExtensionAndDefinition(extension, definition *portainer.Extension) {
extension.Name = definition.Name
extension.ShortDescription = definition.ShortDescription
extension.Deal = definition.Deal
extension.Available = definition.Available
extension.DescriptionURL = definition.DescriptionURL
extension.Images = definition.Images
extension.Logo = definition.Logo
extension.Price = definition.Price
extension.PriceDescription = definition.PriceDescription
extension.ShopURL = definition.ShopURL
definitionVersion := semver.New(definition.Version)
extensionVersion := semver.New(extension.Version)
if extensionVersion.LessThan(*definitionVersion) {
extension.UpdateAvailable = true
}
extension.Version = definition.Version
}

View file

@ -15,7 +15,6 @@ import (
"github.com/portainer/portainer/api/http/handler/endpointgroups"
"github.com/portainer/portainer/api/http/handler/endpointproxy"
"github.com/portainer/portainer/api/http/handler/endpoints"
"github.com/portainer/portainer/api/http/handler/extensions"
"github.com/portainer/portainer/api/http/handler/file"
"github.com/portainer/portainer/api/http/handler/motd"
"github.com/portainer/portainer/api/http/handler/registries"
@ -50,7 +49,6 @@ type Handler struct {
EndpointProxyHandler *endpointproxy.Handler
FileHandler *file.Handler
MOTDHandler *motd.Handler
ExtensionHandler *extensions.Handler
RegistryHandler *registries.Handler
ResourceControlHandler *resourcecontrols.Handler
RoleHandler *roles.Handler
@ -104,8 +102,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
default:
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
}
case strings.HasPrefix(r.URL.Path, "/api/extensions"):
http.StripPrefix("/api", h.ExtensionHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/motd"):
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/registries"):

View file

@ -3,8 +3,6 @@ package settings
import (
"net/http"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/api"
@ -19,12 +17,11 @@ func hideFields(settings *portainer.Settings) {
// Handler is the HTTP handler used to handle settings operations.
type Handler struct {
*mux.Router
AuthorizationService *authorization.Service
DataStore portainer.DataStore
FileService portainer.FileService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
SnapshotService portainer.SnapshotService
DataStore portainer.DataStore
FileService portainer.FileService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
SnapshotService portainer.SnapshotService
}
// NewHandler creates a handler to manage settings operations.

View file

@ -10,7 +10,6 @@ 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/filesystem"
)
@ -116,10 +115,8 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.AllowPrivilegedModeForRegularUsers = *payload.AllowPrivilegedModeForRegularUsers
}
updateAuthorizations := false
if payload.AllowVolumeBrowserForRegularUsers != nil {
settings.AllowVolumeBrowserForRegularUsers = *payload.AllowVolumeBrowserForRegularUsers
updateAuthorizations = true
}
if payload.EnableHostManagementFeatures != nil {
@ -179,37 +176,9 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err}
}
if updateAuthorizations {
err := handler.updateVolumeBrowserSetting(settings)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update RBAC authorizations", err}
}
}
return response.JSON(w, settings)
}
func (handler *Handler) updateVolumeBrowserSetting(settings *portainer.Settings) error {
err := handler.AuthorizationService.UpdateVolumeBrowsingAuthorizations(settings.AllowVolumeBrowserForRegularUsers)
if err != nil {
return err
}
extension, err := handler.DataStore.Extension().Extension(portainer.RBACExtension)
if err != nil && err != bolterrors.ErrObjectNotFound {
return err
}
if extension != nil {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return err
}
}
return nil
}
func (handler *Handler) updateSnapshotInterval(settings *portainer.Settings, snapshotInterval string) error {
settings.SnapshotInterval = snapshotInterval

View file

@ -8,7 +8,6 @@ import (
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
"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"
)
@ -81,22 +80,8 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR
func (handler *Handler) userIsAdminOrEndpointAdmin(user *portainer.User, endpointID portainer.EndpointID) (bool, error) {
isAdmin := user.Role == portainer.AdministratorRole
if isAdmin {
return true, nil
}
rbacExtension, err := handler.DataStore.Extension().Extension(portainer.RBACExtension)
if err != nil && err != bolterrors.ErrObjectNotFound {
return false, errors.New("Unable to verify if RBAC extension is loaded")
}
if rbacExtension == nil {
return false, nil
}
_, endpointResourceAccess := user.EndpointAuthorizations[portainer.EndpointID(endpointID)][portainer.EndpointResourcesAccess]
return endpointResourceAccess, nil
return isAdmin, nil
}
func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) {

View file

@ -76,7 +76,7 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -65,7 +65,7 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
@ -114,30 +114,8 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
user, err := handler.DataStore.User().User(securityContext.UserID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
}
rbacExtension, err := handler.DataStore.Extension().Extension(portainer.RBACExtension)
if err != nil && err != bolterrors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to verify if RBAC extension is loaded", err}
}
endpointResourceAccess := false
_, ok := user.EndpointAuthorizations[portainer.EndpointID(endpointID)][portainer.EndpointResourcesAccess]
if ok {
endpointResourceAccess = true
}
if rbacExtension != nil {
if !securityContext.IsAdmin && !endpointResourceAccess {
return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", httperrors.ErrUnauthorized}
}
} else {
if !securityContext.IsAdmin {
return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", httperrors.ErrUnauthorized}
}
if !securityContext.IsAdmin {
return &httperror.HandlerError{http.StatusUnauthorized, "Permission denied to delete the stack", httperrors.ErrUnauthorized}
}
stack, err := handler.DataStore.Stack().StackByName(stackName)
@ -155,7 +133,7 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -38,7 +38,7 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -33,7 +33,7 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -7,7 +7,6 @@ import (
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
@ -44,14 +43,6 @@ func (handler *Handler) stackList(w http.ResponseWriter, r *http.Request) *httpe
stacks = authorization.DecorateStacks(stacks, resourceControls)
if !securityContext.IsAdmin {
rbacExtensionEnabled := true
_, err := handler.DataStore.Extension().Extension(portainer.RBACExtension)
if err == errors.ErrObjectNotFound {
rbacExtensionEnabled = false
} else if err != nil && err != errors.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to check if RBAC extension is enabled", err}
}
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}
@ -62,7 +53,7 @@ func (handler *Handler) stackList(w http.ResponseWriter, r *http.Request) *httpe
userTeamIDs = append(userTeamIDs, membership.TeamID)
}
stacks = authorization.FilterAuthorizedStacks(stacks, user, userTeamIDs, rbacExtensionEnabled)
stacks = authorization.FilterAuthorizedStacks(stacks, user, userTeamIDs)
}
return response.JSON(w, stacks)

View file

@ -53,7 +53,7 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -72,7 +72,7 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -4,7 +4,6 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"net/http"
@ -14,8 +13,7 @@ import (
// Handler is the HTTP handler used to handle team membership operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
AuthorizationService *authorization.Service
DataStore portainer.DataStore
}
// NewHandler creates a handler to manage team membership operations.

View file

@ -72,10 +72,5 @@ func (handler *Handler) teamMembershipCreate(w http.ResponseWriter, r *http.Requ
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist team memberships inside the database", err}
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
return response.JSON(w, membership)
}

View file

@ -40,10 +40,5 @@ func (handler *Handler) teamMembershipDelete(w http.ResponseWriter, r *http.Requ
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the team membership from the database", err}
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
return response.Empty(w)
}

View file

@ -1,7 +1,6 @@
package teams
import (
"github.com/portainer/portainer/api/internal/authorization"
"net/http"
"github.com/gorilla/mux"
@ -13,8 +12,7 @@ import (
// Handler is the HTTP handler used to handle team operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
AuthorizationService *authorization.Service
DataStore portainer.DataStore
}
// NewHandler creates a handler to manage team operations.

View file

@ -34,10 +34,5 @@ func (handler *Handler) teamDelete(w http.ResponseWriter, r *http.Request) *http
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete associated team memberships from the database", err}
}
err = handler.AuthorizationService.RemoveTeamAccessPolicies(portainer.TeamID(teamID))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clean-up team access policies", err}
}
return response.Empty(w)
}

View file

@ -9,7 +9,6 @@ import (
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/internal/authorization"
)
type adminInitPayload struct {
@ -45,9 +44,8 @@ func (handler *Handler) adminInit(w http.ResponseWriter, r *http.Request) *httpe
}
user := &portainer.User{
Username: payload.Username,
Role: portainer.AdministratorRole,
PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(),
Username: payload.Username,
Role: portainer.AdministratorRole,
}
user.Password, err = handler.CryptoService.Hash(payload.Password)

View file

@ -6,7 +6,6 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"net/http"
@ -28,9 +27,8 @@ func hideFields(user *portainer.User) {
// Handler is the HTTP handler used to handle user operations.
type Handler struct {
*mux.Router
DataStore portainer.DataStore
CryptoService portainer.CryptoService
AuthorizationService *authorization.Service
DataStore portainer.DataStore
CryptoService portainer.CryptoService
}
// NewHandler creates a handler to manage user operations.

View file

@ -12,7 +12,6 @@ import (
bolterrors "github.com/portainer/portainer/api/bolt/errors"
httperrors "github.com/portainer/portainer/api/http/errors"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
)
type userCreatePayload struct {
@ -62,9 +61,8 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http
}
user = &portainer.User{
Username: payload.Username,
Role: portainer.UserRole(payload.Role),
PortainerAuthorizations: authorization.DefaultPortainerAuthorizations(),
Username: payload.Username,
Role: portainer.UserRole(payload.Role),
}
settings, err := handler.DataStore.Settings().Settings()

View file

@ -81,10 +81,5 @@ func (handler *Handler) deleteUser(w http.ResponseWriter, user *portainer.User)
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove user memberships from the database", err}
}
err = handler.AuthorizationService.RemoveUserAccessPolicies(user.ID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clean-up user access policies", err}
}
return response.Empty(w)
}

View file

@ -40,7 +40,7 @@ func (handler *Handler) websocketAttach(w http.ResponseWriter, r *http.Request)
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -47,7 +47,7 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -56,7 +56,7 @@ func (handler *Handler) websocketPodExec(w http.ResponseWriter, r *http.Request)
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}

View file

@ -12,7 +12,6 @@ import (
"github.com/docker/docker/client"
"github.com/portainer/portainer/api"
bolterrors "github.com/portainer/portainer/api/bolt/errors"
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
"github.com/portainer/portainer/api/http/security"
@ -402,16 +401,6 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r
}
if tokenData.Role != portainer.AdministratorRole {
rbacExtension, err := transport.dataStore.Extension().Extension(portainer.RBACExtension)
if err != nil && err != bolterrors.ErrObjectNotFound {
return nil, err
}
user, err := transport.dataStore.User().User(tokenData.ID)
if err != nil {
return nil, err
}
if volumeBrowseRestrictionCheck {
settings, err := transport.dataStore.Settings().Settings()
if err != nil {
@ -419,28 +408,10 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r
}
if !settings.AllowVolumeBrowserForRegularUsers {
if rbacExtension == nil {
return responseutils.WriteAccessDeniedResponse()
}
// Return access denied for all roles except endpoint-administrator
_, userCanBrowse := user.EndpointAuthorizations[transport.endpoint.ID][portainer.OperationDockerAgentBrowseList]
if !userCanBrowse {
return responseutils.WriteAccessDeniedResponse()
}
return responseutils.WriteAccessDeniedResponse()
}
}
endpointResourceAccess := false
_, ok := user.EndpointAuthorizations[transport.endpoint.ID][portainer.EndpointResourcesAccess]
if ok {
endpointResourceAccess = true
}
if rbacExtension != nil && endpointResourceAccess {
return transport.executeDockerRequest(request)
}
teamMemberships, err := transport.dataStore.TeamMembership().TeamMembershipsByUserID(tokenData.ID)
if err != nil {
return nil, err
@ -713,25 +684,5 @@ func (transport *Transport) isAdminOrEndpointAdmin(request *http.Request) (bool,
return false, err
}
if tokenData.Role == portainer.AdministratorRole {
return true, nil
}
user, err := transport.dataStore.User().User(tokenData.ID)
if err != nil {
return false, err
}
rbacExtension, err := transport.dataStore.Extension().Extension(portainer.RBACExtension)
if err != nil && err != bolterrors.ErrObjectNotFound {
return false, err
}
if rbacExtension == nil {
return false, nil
}
_, endpointResourceAccess := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess]
return endpointResourceAccess, nil
return tokenData.Role == portainer.AdministratorRole, nil
}

View file

@ -1,7 +1,6 @@
package factory
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
@ -16,12 +15,8 @@ import (
const azureAPIBaseURL = "https://management.azure.com"
var extensionPorts = map[portainer.ExtensionID]string{
portainer.RBACExtension: "7003",
}
type (
// ProxyFactory is a factory to create reverse proxies to Docker endpoints and extensions
// ProxyFactory is a factory to create reverse proxies
ProxyFactory struct {
dataStore portainer.DataStore
signatureService portainer.DigitalSignatureService
@ -44,25 +39,6 @@ func NewProxyFactory(dataStore portainer.DataStore, signatureService portainer.D
}
}
// BuildExtensionURL returns the URL to an extension server
func BuildExtensionURL(extensionID portainer.ExtensionID) string {
return fmt.Sprintf("http://%s:%s", portainer.ExtensionServer, extensionPorts[extensionID])
}
// NewExtensionProxy returns a new HTTP proxy to an extension server
func (factory *ProxyFactory) NewExtensionProxy(extensionID portainer.ExtensionID) (http.Handler, error) {
address := "http://" + portainer.ExtensionServer + ":" + extensionPorts[extensionID]
extensionURL, err := url.Parse(address)
if err != nil {
return nil, err
}
extensionURL.Scheme = "http"
proxy := httputil.NewSingleHostReverseProxy(extensionURL)
return proxy, nil
}
// NewLegacyExtensionProxy returns a new HTTP proxy to a legacy extension server (Storidge)
func (factory *ProxyFactory) NewLegacyExtensionProxy(extensionAPIURL string) (http.Handler, error) {
extensionURL, err := url.Parse(extensionAPIURL)

View file

@ -2,7 +2,6 @@ package proxy
import (
"net/http"
"strconv"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
@ -21,7 +20,6 @@ type (
Manager struct {
proxyFactory *factory.ProxyFactory
endpointProxies cmap.ConcurrentMap
extensionProxies cmap.ConcurrentMap
legacyExtensionProxies cmap.ConcurrentMap
}
)
@ -30,7 +28,6 @@ type (
func NewManager(dataStore portainer.DataStore, signatureService portainer.DigitalSignatureService, tunnelService portainer.ReverseTunnelService, clientFactory *docker.ClientFactory, kubernetesClientFactory *cli.ClientFactory, kubernetesTokenCacheManager *kubernetes.TokenCacheManager) *Manager {
return &Manager{
endpointProxies: cmap.New(),
extensionProxies: cmap.New(),
legacyExtensionProxies: cmap.New(),
proxyFactory: factory.NewProxyFactory(dataStore, signatureService, tunnelService, clientFactory, kubernetesClientFactory, kubernetesTokenCacheManager),
}
@ -63,38 +60,6 @@ func (manager *Manager) DeleteEndpointProxy(endpoint *portainer.Endpoint) {
manager.endpointProxies.Remove(string(endpoint.ID))
}
// CreateExtensionProxy creates a new HTTP reverse proxy for an extension and
// registers it in the extension map associated to the specified extension identifier
func (manager *Manager) CreateExtensionProxy(extensionID portainer.ExtensionID) (http.Handler, error) {
proxy, err := manager.proxyFactory.NewExtensionProxy(extensionID)
if err != nil {
return nil, err
}
manager.extensionProxies.Set(strconv.Itoa(int(extensionID)), proxy)
return proxy, nil
}
// GetExtensionProxy returns an extension proxy associated to an extension identifier
func (manager *Manager) GetExtensionProxy(extensionID portainer.ExtensionID) http.Handler {
proxy, ok := manager.extensionProxies.Get(strconv.Itoa(int(extensionID)))
if !ok {
return nil
}
return proxy.(http.Handler)
}
// GetExtensionURL retrieves the URL of an extension running locally based on the extension port table
func (manager *Manager) GetExtensionURL(extensionID portainer.ExtensionID) string {
return factory.BuildExtensionURL(extensionID)
}
// DeleteExtensionProxy deletes the extension proxy associated to an extension identifier
func (manager *Manager) DeleteExtensionProxy(extensionID portainer.ExtensionID) {
manager.extensionProxies.Remove(strconv.Itoa(int(extensionID)))
}
// CreateLegacyExtensionProxy creates a new HTTP reverse proxy for a legacy extension and adds it to the registered proxies
func (manager *Manager) CreateLegacyExtensionProxy(key, extensionAPIURL string) (http.Handler, error) {
proxy, err := manager.proxyFactory.NewLegacyExtensionProxy(extensionAPIURL)

View file

@ -14,9 +14,8 @@ import (
type (
// RequestBouncer represents an entity that manages API request accesses
RequestBouncer struct {
dataStore portainer.DataStore
jwtService portainer.JWTService
rbacExtensionClient *rbacExtensionClient
dataStore portainer.DataStore
jwtService portainer.JWTService
}
// RestrictedRequestContext is a data structure containing information
@ -30,11 +29,10 @@ type (
)
// NewRequestBouncer initializes a new RequestBouncer
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService, rbacExtensionURL string) *RequestBouncer {
func NewRequestBouncer(dataStore portainer.DataStore, jwtService portainer.JWTService) *RequestBouncer {
return &RequestBouncer{
dataStore: dataStore,
jwtService: jwtService,
rbacExtensionClient: newRBACExtensionClient(rbacExtensionURL),
dataStore: dataStore,
jwtService: jwtService,
}
}
@ -47,8 +45,7 @@ func (bouncer *RequestBouncer) PublicAccess(h http.Handler) http.Handler {
// AdminAccess defines a security check for API endpoints that require an authorization check.
// Authentication is required to access these endpoints.
// If the RBAC extension is enabled, authorizations are required to use these endpoints.
// If the RBAC extension is not enabled, the administrator role is required to use these endpoints.
// The administrator role is required to use these endpoints.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to inside the API operation for extra authorization validation
// and resource filtering.
@ -61,8 +58,6 @@ func (bouncer *RequestBouncer) AdminAccess(h http.Handler) http.Handler {
// RestrictedAccess defines a security check for restricted API endpoints.
// Authentication is required to access these endpoints.
// If the RBAC extension is enabled, authorizations are required to use these endpoints.
// If the RBAC extension is not enabled, access is granted to any authenticated user.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to inside the API operation for extra authorization validation
// and resource filtering.
@ -86,11 +81,9 @@ func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler
// AuthorizedEndpointOperation retrieves the JWT token from the request context and verifies
// that the user can access the specified endpoint.
// If the RBAC extension is enabled and the authorizationCheck flag is set,
// it will also validate that the user can execute the specified operation.
// An error is returned when access to the endpoint is denied or if the user do not have the required
// authorization to execute the operation.
func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint, authorizationCheck bool) error {
func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endpoint *portainer.Endpoint) error {
tokenData, err := RetrieveTokenData(r)
if err != nil {
return err
@ -114,13 +107,6 @@ func (bouncer *RequestBouncer) AuthorizedEndpointOperation(r *http.Request, endp
return httperrors.ErrEndpointAccessDenied
}
if authorizationCheck {
err = bouncer.checkEndpointOperationAuthorization(r, endpoint)
if err != nil {
return ErrAuthorizationRequired
}
}
return nil
}
@ -142,38 +128,6 @@ func (bouncer *RequestBouncer) AuthorizedEdgeEndpointOperation(r *http.Request,
return nil
}
func (bouncer *RequestBouncer) checkEndpointOperationAuthorization(r *http.Request, endpoint *portainer.Endpoint) error {
tokenData, err := RetrieveTokenData(r)
if err != nil {
return err
}
if tokenData.Role == portainer.AdministratorRole {
return nil
}
extension, err := bouncer.dataStore.Extension().Extension(portainer.RBACExtension)
if err == bolterrors.ErrObjectNotFound {
return nil
} else if err != nil {
return err
}
user, err := bouncer.dataStore.User().User(tokenData.ID)
if err != nil {
return err
}
apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(),
Method: r.Method,
Authorizations: user.EndpointAuthorizations[endpoint.ID],
}
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
return bouncer.rbacExtensionClient.checkAuthorization(apiOperation)
}
// 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.
@ -206,9 +160,8 @@ func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler
}
// mwCheckPortainerAuthorizations will verify that the user has the required authorization to access
// a specific API endpoint. It will leverage the RBAC extension authorization validation if the extension
// is enabled.
// If the administratorOnly flag is specified and the RBAC extension is not enabled, this will prevent non-admin
// a specific API endpoint.
// If the administratorOnly flag is specified, this will prevent non-admin
// users from accessing the endpoint.
func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler, administratorOnly bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -223,21 +176,12 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
return
}
extension, err := bouncer.dataStore.Extension().Extension(portainer.RBACExtension)
if err == bolterrors.ErrObjectNotFound {
if administratorOnly {
httperror.WriteError(w, http.StatusForbidden, "Access denied", httperrors.ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
return
} else if err != nil {
httperror.WriteError(w, http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err)
if administratorOnly {
httperror.WriteError(w, http.StatusForbidden, "Access denied", httperrors.ErrUnauthorized)
return
}
user, err := bouncer.dataStore.User().User(tokenData.ID)
_, err = bouncer.dataStore.User().User(tokenData.ID)
if err != nil && err == bolterrors.ErrObjectNotFound {
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", httperrors.ErrUnauthorized)
return
@ -246,19 +190,6 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
return
}
apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(),
Method: r.Method,
Authorizations: user.PortainerAuthorizations,
}
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
err = bouncer.rbacExtensionClient.checkAuthorization(apiOperation)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, "Access denied", ErrAuthorizationRequired)
return
}
next.ServeHTTP(w, r)
})
}

View file

@ -1,59 +0,0 @@
package security
import (
"encoding/json"
"net/http"
"time"
portainer "github.com/portainer/portainer/api"
)
const (
defaultHTTPTimeout = 5
)
type rbacExtensionClient struct {
httpClient *http.Client
extensionURL string
licenseKey string
}
func newRBACExtensionClient(extensionURL string) *rbacExtensionClient {
return &rbacExtensionClient{
extensionURL: extensionURL,
httpClient: &http.Client{
Timeout: time.Second * time.Duration(defaultHTTPTimeout),
},
}
}
func (client *rbacExtensionClient) setLicenseKey(licenseKey string) {
client.licenseKey = licenseKey
}
func (client *rbacExtensionClient) checkAuthorization(authRequest *portainer.APIOperationAuthorizationRequest) error {
encodedAuthRequest, err := json.Marshal(authRequest)
if err != nil {
return err
}
req, err := http.NewRequest("GET", client.extensionURL+"/authorized_operation", nil)
if err != nil {
return err
}
req.Header.Set("X-RBAC-AuthorizationRequest", string(encodedAuthRequest))
req.Header.Set("X-PortainerExtension-License", client.licenseKey)
resp, err := client.httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
return ErrAuthorizationRequired
}
return nil
}

View file

@ -20,7 +20,6 @@ import (
"github.com/portainer/portainer/api/http/handler/endpointgroups"
"github.com/portainer/portainer/api/http/handler/endpointproxy"
"github.com/portainer/portainer/api/http/handler/endpoints"
"github.com/portainer/portainer/api/http/handler/extensions"
"github.com/portainer/portainer/api/http/handler/file"
"github.com/portainer/portainer/api/http/handler/motd"
"github.com/portainer/portainer/api/http/handler/registries"
@ -41,7 +40,6 @@ import (
"github.com/portainer/portainer/api/http/proxy"
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
"github.com/portainer/portainer/api/kubernetes/cli"
)
@ -51,7 +49,6 @@ type Server struct {
AssetsPath string
Status *portainer.Status
ReverseTunnelService portainer.ReverseTunnelService
ExtensionManager portainer.ExtensionManager
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
@ -74,12 +71,10 @@ type Server struct {
// Start starts the HTTP server
func (server *Server) Start() error {
authorizationService := authorization.NewService(server.DataStore)
kubernetesTokenCacheManager := kubernetes.NewTokenCacheManager()
proxyManager := proxy.NewManager(server.DataStore, server.SignatureService, server.ReverseTunnelService, server.DockerClientFactory, server.KubernetesClientFactory, kubernetesTokenCacheManager)
rbacExtensionURL := proxyManager.GetExtensionURL(portainer.RBACExtension)
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService, rbacExtensionURL)
requestBouncer := security.NewRequestBouncer(server.DataStore, server.JWTService)
rateLimiter := security.NewRateLimiter(10, 1*time.Second, 1*time.Hour)
@ -89,7 +84,6 @@ func (server *Server) Start() error {
authHandler.JWTService = server.JWTService
authHandler.LDAPService = server.LDAPService
authHandler.ProxyManager = proxyManager
authHandler.AuthorizationService = authorizationService
authHandler.KubernetesTokenCacheManager = kubernetesTokenCacheManager
authHandler.OAuthService = server.OAuthService
@ -122,7 +116,6 @@ func (server *Server) Start() error {
var endpointHandler = endpoints.NewHandler(requestBouncer)
endpointHandler.DataStore = server.DataStore
endpointHandler.AuthorizationService = authorizationService
endpointHandler.FileService = server.FileService
endpointHandler.ProxyManager = proxyManager
endpointHandler.SnapshotService = server.SnapshotService
@ -136,7 +129,6 @@ func (server *Server) Start() error {
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
endpointGroupHandler.DataStore = server.DataStore
endpointGroupHandler.AuthorizationService = authorizationService
var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer)
endpointProxyHandler.DataStore = server.DataStore
@ -147,11 +139,6 @@ func (server *Server) Start() error {
var motdHandler = motd.NewHandler(requestBouncer)
var extensionHandler = extensions.NewHandler(requestBouncer)
extensionHandler.DataStore = server.DataStore
extensionHandler.ExtensionManager = server.ExtensionManager
extensionHandler.AuthorizationService = authorizationService
var registryHandler = registries.NewHandler(requestBouncer)
registryHandler.DataStore = server.DataStore
registryHandler.FileService = server.FileService
@ -161,7 +148,6 @@ func (server *Server) Start() error {
resourceControlHandler.DataStore = server.DataStore
var settingsHandler = settings.NewHandler(requestBouncer)
settingsHandler.AuthorizationService = authorizationService
settingsHandler.DataStore = server.DataStore
settingsHandler.FileService = server.FileService
settingsHandler.JWTService = server.JWTService
@ -181,11 +167,9 @@ func (server *Server) Start() error {
var teamHandler = teams.NewHandler(requestBouncer)
teamHandler.DataStore = server.DataStore
teamHandler.AuthorizationService = authorizationService
var teamMembershipHandler = teammemberships.NewHandler(requestBouncer)
teamMembershipHandler.DataStore = server.DataStore
teamMembershipHandler.AuthorizationService = authorizationService
var statusHandler = status.NewHandler(requestBouncer, server.Status)
@ -202,7 +186,6 @@ func (server *Server) Start() error {
var userHandler = users.NewHandler(requestBouncer, rateLimiter)
userHandler.DataStore = server.DataStore
userHandler.CryptoService = server.CryptoService
userHandler.AuthorizationService = authorizationService
var websocketHandler = websocket.NewHandler(requestBouncer)
websocketHandler.DataStore = server.DataStore
@ -229,7 +212,6 @@ func (server *Server) Start() error {
EndpointProxyHandler: endpointProxyHandler,
FileHandler: fileHandler,
MOTDHandler: motdHandler,
ExtensionHandler: extensionHandler,
RegistryHandler: registryHandler,
ResourceControlHandler: resourceControlHandler,
SettingsHandler: settingsHandler,