From 06db4e0ad4bbf3cd08584d7825bcbe545189f207 Mon Sep 17 00:00:00 2001 From: Chaim Lev-Ari Date: Sun, 17 Jan 2021 22:31:23 +0200 Subject: [PATCH] fix(auth): skip security checks with --no-auth flag (#4513) * fix(stacks): skip security checks if no-auth * fix(containers): skip security check when auth is disabled * fix(volumes): show browse if auth is disabled --- .../handler/stacks/create_compose_stack.go | 46 +++++----- api/http/handler/stacks/create_swarm_stack.go | 37 ++++---- api/http/handler/stacks/handler.go | 9 +- api/http/proxy/factory/docker.go | 1 + api/http/proxy/factory/docker/containers.go | 88 ++++++++++--------- api/http/proxy/factory/docker/transport.go | 4 +- api/http/proxy/factory/docker_unix.go | 1 + api/http/proxy/factory/docker_windows.go | 1 + api/http/proxy/factory/factory.go | 5 +- api/http/proxy/manager.go | 6 +- api/http/server.go | 3 +- app/docker/views/volumes/volumesController.js | 16 ++-- 12 files changed, 122 insertions(+), 95 deletions(-) diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index fdadf9c5f..d41de5ca8 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -302,9 +302,12 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai } filteredRegistries := security.FilterRegistries(registries, securityContext) - user, err := handler.UserService.User(securityContext.UserID) - if err != nil { - return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err} + var user *portainer.User + if !handler.authDisabled { + user, err = handler.UserService.User(securityContext.UserID) + if err != nil { + return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err} + } } config := &composeStackDeploymentConfig{ @@ -330,28 +333,29 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig) return err } - isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID) - if err != nil { - return err - } - - if (!settings.AllowBindMountsForRegularUsers || - !settings.AllowPrivilegedModeForRegularUsers || - !settings.AllowHostNamespaceForRegularUsers || - !settings.AllowDeviceMappingForRegularUsers || - !settings.AllowContainerCapabilitiesForRegularUsers) && - !isAdminOrEndpointAdmin { - - composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint) - - stackContent, err := handler.FileService.GetFileContent(composeFilePath) + if !handler.authDisabled { + isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID) if err != nil { return err } - err = handler.isValidStackFile(stackContent, settings) - if err != nil { - return err + if (!settings.AllowBindMountsForRegularUsers || + !settings.AllowPrivilegedModeForRegularUsers || + !settings.AllowHostNamespaceForRegularUsers || + !settings.AllowDeviceMappingForRegularUsers || + !settings.AllowContainerCapabilitiesForRegularUsers) && !isAdminOrEndpointAdmin { + + composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint) + + stackContent, err := handler.FileService.GetFileContent(composeFilePath) + if err != nil { + return err + } + + err = handler.isValidStackFile(stackContent, settings) + if err != nil { + return err + } } } diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index 504437372..fce77ed78 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -311,9 +311,12 @@ func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portaine } filteredRegistries := security.FilterRegistries(registries, securityContext) - user, err := handler.UserService.User(securityContext.UserID) - if err != nil { - return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err} + var user *portainer.User + if !handler.authDisabled { + user, err = handler.UserService.User(securityContext.UserID) + if err != nil { + return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err} + } } config := &swarmStackDeploymentConfig{ @@ -335,23 +338,25 @@ func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) err return err } - isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID) - if err != nil { - return err - } - - if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin { - - composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint) - - stackContent, err := handler.FileService.GetFileContent(composeFilePath) + if !handler.authDisabled { + isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID) if err != nil { return err } - err = handler.isValidStackFile(stackContent, settings) - if err != nil { - return err + if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin { + + composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint) + + stackContent, err := handler.FileService.GetFileContent(composeFilePath) + if err != nil { + return err + } + + err = handler.isValidStackFile(stackContent, settings) + if err != nil { + return err + } } } diff --git a/api/http/handler/stacks/handler.go b/api/http/handler/stacks/handler.go index 253acb7e4..36faac97d 100644 --- a/api/http/handler/stacks/handler.go +++ b/api/http/handler/stacks/handler.go @@ -16,6 +16,8 @@ type Handler struct { stackCreationMutex *sync.Mutex stackDeletionMutex *sync.Mutex requestBouncer *security.RequestBouncer + authDisabled bool + *mux.Router FileService portainer.FileService GitService portainer.GitService @@ -32,9 +34,10 @@ type Handler struct { } // NewHandler creates a handler to manage stack operations. -func NewHandler(bouncer *security.RequestBouncer) *Handler { +func NewHandler(bouncer *security.RequestBouncer, authDisabled bool) *Handler { h := &Handler{ Router: mux.NewRouter(), + authDisabled: authDisabled, stackCreationMutex: &sync.Mutex{}, stackDeletionMutex: &sync.Mutex{}, requestBouncer: bouncer, @@ -57,7 +60,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler { } func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) { - if securityContext.IsAdmin { + if securityContext.IsAdmin || handler.authDisabled { return true, nil } @@ -90,7 +93,7 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR } func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) { - if securityContext.IsAdmin { + if securityContext.IsAdmin || handler.authDisabled { return true, nil } diff --git a/api/http/proxy/factory/docker.go b/api/http/proxy/factory/docker.go index acf0731bd..2f0a94902 100644 --- a/api/http/proxy/factory/docker.go +++ b/api/http/proxy/factory/docker.go @@ -68,6 +68,7 @@ func (factory *ProxyFactory) newDockerHTTPProxy(endpoint *portainer.Endpoint) (h ExtensionService: factory.extensionService, SignatureService: factory.signatureService, DockerClientFactory: factory.dockerClientFactory, + AuthDisabled: factory.authDisabled, } dockerTransport, err := docker.NewTransport(transportParameters, httpTransport) diff --git a/api/http/proxy/factory/docker/containers.go b/api/http/proxy/factory/docker/containers.go index b4463afa1..99dfd46cc 100644 --- a/api/http/proxy/factory/docker/containers.go +++ b/api/http/proxy/factory/docker/containers.go @@ -173,63 +173,65 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req return nil, err } - user, err := transport.userService.User(tokenData.ID) - if err != nil { - return nil, err - } - - rbacExtension, err := transport.extensionService.Extension(portainer.RBACExtension) - if err != nil && err != portainer.ErrObjectNotFound { - return nil, err - } - - endpointResourceAccess := false - _, ok := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess] - if ok { - endpointResourceAccess = true - } - - isAdmin := (rbacExtension != nil && endpointResourceAccess) || tokenData.Role == portainer.AdministratorRole - - if !isAdmin { - settings, err := transport.settingsService.Settings() + if !transport.authDisabled { + user, err := transport.userService.User(tokenData.ID) if err != nil { return nil, err } - if !settings.AllowPrivilegedModeForRegularUsers || - !settings.AllowHostNamespaceForRegularUsers || - !settings.AllowDeviceMappingForRegularUsers || - !settings.AllowContainerCapabilitiesForRegularUsers { + rbacExtension, err := transport.extensionService.Extension(portainer.RBACExtension) + if err != nil && err != portainer.ErrObjectNotFound { + return nil, err + } - body, err := ioutil.ReadAll(request.Body) + endpointResourceAccess := false + _, ok := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess] + if ok { + endpointResourceAccess = true + } + + isAdmin := (rbacExtension != nil && endpointResourceAccess) || tokenData.Role == portainer.AdministratorRole + + if !isAdmin { + settings, err := transport.settingsService.Settings() if err != nil { return nil, err } - partialContainer := &PartialContainer{} - err = json.Unmarshal(body, partialContainer) - if err != nil { - return nil, err - } + if !settings.AllowPrivilegedModeForRegularUsers || + !settings.AllowHostNamespaceForRegularUsers || + !settings.AllowDeviceMappingForRegularUsers || + !settings.AllowContainerCapabilitiesForRegularUsers { - if !settings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged { - return forbiddenResponse, errors.New("forbidden to use privileged mode") - } + body, err := ioutil.ReadAll(request.Body) + if err != nil { + return nil, err + } - if !settings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" { - return forbiddenResponse, errors.New("forbidden to use pid host namespace") - } + partialContainer := &PartialContainer{} + err = json.Unmarshal(body, partialContainer) + if err != nil { + return nil, err + } - if !settings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 { - return nil, errors.New("forbidden to use device mapping") - } + if !settings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged { + return forbiddenResponse, errors.New("forbidden to use privileged mode") + } - if !settings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) { - return nil, errors.New("forbidden to use container capabilities") - } + if !settings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" { + return forbiddenResponse, errors.New("forbidden to use pid host namespace") + } - request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + if !settings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 { + return nil, errors.New("forbidden to use device mapping") + } + + if !settings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) { + return nil, errors.New("forbidden to use container capabilities") + } + + request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + } } } diff --git a/api/http/proxy/factory/docker/transport.go b/api/http/proxy/factory/docker/transport.go index 2138d0135..5013abfb5 100644 --- a/api/http/proxy/factory/docker/transport.go +++ b/api/http/proxy/factory/docker/transport.go @@ -38,6 +38,7 @@ type ( extensionService portainer.ExtensionService dockerClient *client.Client dockerClientFactory *docker.ClientFactory + authDisabled bool } // TransportParameters is used to create a new Transport @@ -54,6 +55,7 @@ type ( ReverseTunnelService portainer.ReverseTunnelService ExtensionService portainer.ExtensionService DockerClientFactory *docker.ClientFactory + AuthDisabled bool } restrictedDockerOperationContext struct { @@ -94,6 +96,7 @@ func NewTransport(parameters *TransportParameters, httpTransport *http.Transport dockerClientFactory: parameters.DockerClientFactory, HTTPTransport: httpTransport, dockerClient: dockerClient, + authDisabled: parameters.AuthDisabled, } return transport, nil @@ -656,7 +659,6 @@ func (transport *Transport) createRegistryAccessContext(request *http.Request) ( return nil, err } - accessContext := ®istryAccessContext{ isAdmin: true, userID: tokenData.ID, diff --git a/api/http/proxy/factory/docker_unix.go b/api/http/proxy/factory/docker_unix.go index 214b50747..40b68f286 100644 --- a/api/http/proxy/factory/docker_unix.go +++ b/api/http/proxy/factory/docker_unix.go @@ -24,6 +24,7 @@ func (factory ProxyFactory) newOSBasedLocalProxy(path string, endpoint *portaine ExtensionService: factory.extensionService, SignatureService: factory.signatureService, DockerClientFactory: factory.dockerClientFactory, + AuthDisabled: factory.authDisabled, } proxy := &dockerLocalProxy{} diff --git a/api/http/proxy/factory/docker_windows.go b/api/http/proxy/factory/docker_windows.go index 50ba768f7..7171210ac 100644 --- a/api/http/proxy/factory/docker_windows.go +++ b/api/http/proxy/factory/docker_windows.go @@ -25,6 +25,7 @@ func (factory ProxyFactory) newOSBasedLocalProxy(path string, endpoint *portaine ExtensionService: factory.extensionService, SignatureService: factory.signatureService, DockerClientFactory: factory.dockerClientFactory, + AuthDisabled: factory.authDisabled, } proxy := &dockerLocalProxy{} diff --git a/api/http/proxy/factory/factory.go b/api/http/proxy/factory/factory.go index 207977f21..7f447d92f 100644 --- a/api/http/proxy/factory/factory.go +++ b/api/http/proxy/factory/factory.go @@ -6,7 +6,7 @@ import ( "net/http/httputil" "net/url" - "github.com/portainer/portainer/api" + portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/docker" ) @@ -32,6 +32,7 @@ type ( reverseTunnelService portainer.ReverseTunnelService extensionService portainer.ExtensionService dockerClientFactory *docker.ClientFactory + authDisabled bool } // ProxyFactoryParameters is used to create a new ProxyFactory @@ -47,6 +48,7 @@ type ( ReverseTunnelService portainer.ReverseTunnelService ExtensionService portainer.ExtensionService DockerClientFactory *docker.ClientFactory + AuthDisabled bool } ) @@ -64,6 +66,7 @@ func NewProxyFactory(parameters *ProxyFactoryParameters) *ProxyFactory { reverseTunnelService: parameters.ReverseTunnelService, extensionService: parameters.ExtensionService, dockerClientFactory: parameters.DockerClientFactory, + authDisabled: parameters.AuthDisabled, } } diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index fa27f6399..d2bab1dd9 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -4,8 +4,8 @@ import ( "net/http" "strconv" - "github.com/orcaman/concurrent-map" - "github.com/portainer/portainer/api" + cmap "github.com/orcaman/concurrent-map" + portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/docker" "github.com/portainer/portainer/api/http/proxy/factory" ) @@ -34,6 +34,7 @@ type ( ReverseTunnelService portainer.ReverseTunnelService ExtensionService portainer.ExtensionService DockerClientFactory *docker.ClientFactory + AuthDisabled bool } ) @@ -51,6 +52,7 @@ func NewManager(parameters *ManagerParams) *Manager { ReverseTunnelService: parameters.ReverseTunnelService, ExtensionService: parameters.ExtensionService, DockerClientFactory: parameters.DockerClientFactory, + AuthDisabled: parameters.AuthDisabled, } return &Manager{ diff --git a/api/http/server.go b/api/http/server.go index 1029bbfd2..6da333279 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -104,6 +104,7 @@ func (server *Server) Start() error { ReverseTunnelService: server.ReverseTunnelService, ExtensionService: server.ExtensionService, DockerClientFactory: server.DockerClientFactory, + AuthDisabled: server.AuthDisabled, } proxyManager := proxy.NewManager(proxyManagerParameters) @@ -247,7 +248,7 @@ func (server *Server) Start() error { settingsHandler.ExtensionService = server.ExtensionService settingsHandler.AuthorizationService = authorizationService - var stackHandler = stacks.NewHandler(requestBouncer) + var stackHandler = stacks.NewHandler(requestBouncer, server.AuthDisabled) stackHandler.FileService = server.FileService stackHandler.StackService = server.StackService stackHandler.EndpointService = server.EndpointService diff --git a/app/docker/views/volumes/volumesController.js b/app/docker/views/volumes/volumesController.js index b990df43f..5f8237b54 100644 --- a/app/docker/views/volumes/volumesController.js +++ b/app/docker/views/volumes/volumesController.js @@ -73,14 +73,16 @@ angular.module('portainer.docker').controller('VolumesController', [ $scope.showBrowseAction = $scope.applicationState.endpoint.mode.agentProxy; - ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) { - if (!extensionEnabled) { - var isAdmin = Authentication.isAdmin(); - if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) { - $scope.showBrowseAction = false; + if ($scope.applicationState.application.authentication) { + ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) { + if (!extensionEnabled) { + var isAdmin = Authentication.isAdmin(); + if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) { + $scope.showBrowseAction = false; + } } - } - }); + }); + } } initView();