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
This commit is contained in:
Chaim Lev-Ari
2021-01-17 22:31:23 +02:00
committed by GitHub
parent 9f92e0aee3
commit 06db4e0ad4
12 changed files with 122 additions and 95 deletions

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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))
}
}
}

View File

@@ -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 := &registryAccessContext{
isAdmin: true,
userID: tokenData.ID,

View File

@@ -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{}

View File

@@ -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{}

View File

@@ -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,
}
}

View File

@@ -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{

View File

@@ -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