diff --git a/api/bolt/init.go b/api/bolt/init.go
index b67c39df0..7ce23f138 100644
--- a/api/bolt/init.go
+++ b/api/bolt/init.go
@@ -40,18 +40,11 @@ func (store *Store) Init() error {
portainer.LDAPGroupSearchSettings{},
},
},
- OAuthSettings: portainer.OAuthSettings{},
- AllowBindMountsForRegularUsers: true,
- AllowPrivilegedModeForRegularUsers: true,
- AllowVolumeBrowserForRegularUsers: false,
- AllowHostNamespaceForRegularUsers: true,
- AllowDeviceMappingForRegularUsers: true,
- AllowStackManagementForRegularUsers: true,
- AllowContainerCapabilitiesForRegularUsers: true,
- EnableHostManagementFeatures: false,
- EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
- TemplatesURL: portainer.DefaultTemplatesURL,
- UserSessionTimeout: portainer.DefaultUserSessionTimeout,
+ OAuthSettings: portainer.OAuthSettings{},
+
+ EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
+ TemplatesURL: portainer.DefaultTemplatesURL,
+ UserSessionTimeout: portainer.DefaultUserSessionTimeout,
}
err = store.SettingsService.UpdateSettings(defaultSettings)
diff --git a/api/bolt/migrator/migrate_dbversion25.go b/api/bolt/migrator/migrate_dbversion25.go
new file mode 100644
index 000000000..98fbb083a
--- /dev/null
+++ b/api/bolt/migrator/migrate_dbversion25.go
@@ -0,0 +1,51 @@
+package migrator
+
+import (
+ portainer "github.com/portainer/portainer/api"
+)
+
+func (m *Migrator) updateEndpointSettingsToDB25() error {
+ settings, err := m.settingsService.Settings()
+ if err != nil {
+ return err
+ }
+
+ endpoints, err := m.endpointService.Endpoints()
+ if err != nil {
+ return err
+ }
+
+ for i := range endpoints {
+ endpoint := endpoints[i]
+
+ securitySettings := portainer.EndpointSecuritySettings{}
+
+ if endpoint.Type == portainer.EdgeAgentOnDockerEnvironment ||
+ endpoint.Type == portainer.AgentOnDockerEnvironment ||
+ endpoint.Type == portainer.DockerEnvironment {
+
+ securitySettings = portainer.EndpointSecuritySettings{
+ AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
+ AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
+ AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
+ AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
+ AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
+ AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
+ }
+
+ if endpoint.Type == portainer.AgentOnDockerEnvironment || endpoint.Type == portainer.EdgeAgentOnDockerEnvironment {
+ securitySettings.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers
+ securitySettings.EnableHostManagementFeatures = settings.EnableHostManagementFeatures
+ }
+ }
+
+ endpoint.SecuritySettings = securitySettings
+
+ err = m.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/api/bolt/migrator/migrator.go b/api/bolt/migrator/migrator.go
index 8217dc302..468fe0aa7 100644
--- a/api/bolt/migrator/migrator.go
+++ b/api/bolt/migrator/migrator.go
@@ -342,5 +342,13 @@ func (m *Migrator) Migrate() error {
}
}
+ // Portainer 2.1.0
+ if m.currentDBVersion < 26 {
+ err := m.updateEndpointSettingsToDB25()
+ if err != nil {
+ return err
+ }
+ }
+
return m.versionService.StoreDBVersion(portainer.DBVersion)
}
diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go
index 229b0c0a5..d8ddf0cdd 100644
--- a/api/cmd/portainer/main.go
+++ b/api/cmd/portainer/main.go
@@ -248,6 +248,18 @@ func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore portainer.Dat
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
+
+ SecuritySettings: portainer.EndpointSecuritySettings{
+ AllowVolumeBrowserForRegularUsers: false,
+ EnableHostManagementFeatures: false,
+
+ AllowBindMountsForRegularUsers: true,
+ AllowPrivilegedModeForRegularUsers: true,
+ AllowHostNamespaceForRegularUsers: true,
+ AllowContainerCapabilitiesForRegularUsers: true,
+ AllowDeviceMappingForRegularUsers: true,
+ AllowStackManagementForRegularUsers: true,
+ },
}
if strings.HasPrefix(endpoint.URL, "tcp://") {
@@ -297,6 +309,18 @@ func createUnsecuredEndpoint(endpointURL string, dataStore portainer.DataStore,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.DockerSnapshot{},
Kubernetes: portainer.KubernetesDefault(),
+
+ SecuritySettings: portainer.EndpointSecuritySettings{
+ AllowVolumeBrowserForRegularUsers: false,
+ EnableHostManagementFeatures: false,
+
+ AllowBindMountsForRegularUsers: true,
+ AllowPrivilegedModeForRegularUsers: true,
+ AllowHostNamespaceForRegularUsers: true,
+ AllowContainerCapabilitiesForRegularUsers: true,
+ AllowDeviceMappingForRegularUsers: true,
+ AllowStackManagementForRegularUsers: true,
+ },
}
err := snapshotService.SnapshotEndpoint(endpoint)
diff --git a/api/http/handler/endpoints/endpoint_create.go b/api/http/handler/endpoints/endpoint_create.go
index 728711d87..aadbe6ca0 100644
--- a/api/http/handler/endpoints/endpoint_create.go
+++ b/api/http/handler/endpoints/endpoint_create.go
@@ -14,7 +14,7 @@ import (
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
- "github.com/portainer/portainer/api"
+ portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/portainer/api/internal/edge"
@@ -440,6 +440,18 @@ func (handler *Handler) snapshotAndPersistEndpoint(endpoint *portainer.Endpoint)
}
func (handler *Handler) saveEndpointAndUpdateAuthorizations(endpoint *portainer.Endpoint) error {
+ endpoint.SecuritySettings = portainer.EndpointSecuritySettings{
+ AllowVolumeBrowserForRegularUsers: false,
+ EnableHostManagementFeatures: false,
+
+ AllowBindMountsForRegularUsers: true,
+ AllowPrivilegedModeForRegularUsers: true,
+ AllowHostNamespaceForRegularUsers: true,
+ AllowContainerCapabilitiesForRegularUsers: true,
+ AllowDeviceMappingForRegularUsers: true,
+ AllowStackManagementForRegularUsers: true,
+ }
+
err := handler.DataStore.Endpoint().CreateEndpoint(endpoint)
if err != nil {
return err
diff --git a/api/http/handler/endpoints/endpoint_settings_update.go b/api/http/handler/endpoints/endpoint_settings_update.go
new file mode 100644
index 000000000..75d846c26
--- /dev/null
+++ b/api/http/handler/endpoints/endpoint_settings_update.go
@@ -0,0 +1,90 @@
+package endpoints
+
+import (
+ "net/http"
+
+ httperror "github.com/portainer/libhttp/error"
+ "github.com/portainer/libhttp/request"
+ "github.com/portainer/libhttp/response"
+ portainer "github.com/portainer/portainer/api"
+ "github.com/portainer/portainer/api/bolt/errors"
+)
+
+type endpointSettingsUpdatePayload struct {
+ AllowBindMountsForRegularUsers *bool `json:"allowBindMountsForRegularUsers"`
+ AllowPrivilegedModeForRegularUsers *bool `json:"allowPrivilegedModeForRegularUsers"`
+ AllowVolumeBrowserForRegularUsers *bool `json:"allowVolumeBrowserForRegularUsers"`
+ AllowHostNamespaceForRegularUsers *bool `json:"allowHostNamespaceForRegularUsers"`
+ AllowDeviceMappingForRegularUsers *bool `json:"allowDeviceMappingForRegularUsers"`
+ AllowStackManagementForRegularUsers *bool `json:"allowStackManagementForRegularUsers"`
+ AllowContainerCapabilitiesForRegularUsers *bool `json:"allowContainerCapabilitiesForRegularUsers"`
+ EnableHostManagementFeatures *bool `json:"enableHostManagementFeatures"`
+}
+
+func (payload *endpointSettingsUpdatePayload) Validate(r *http.Request) error {
+ return nil
+}
+
+// PUT request on /api/endpoints/:id/settings
+func (handler *Handler) endpointSettingsUpdate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
+ endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
+ if err != nil {
+ return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
+ }
+
+ var payload endpointSettingsUpdatePayload
+ err = request.DecodeAndValidateJSONPayload(r, &payload)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
+ }
+
+ endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
+ if err == errors.ErrObjectNotFound {
+ return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
+ } else if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
+ }
+
+ securitySettings := endpoint.SecuritySettings
+
+ if payload.AllowBindMountsForRegularUsers != nil {
+ securitySettings.AllowBindMountsForRegularUsers = *payload.AllowBindMountsForRegularUsers
+ }
+
+ if payload.AllowContainerCapabilitiesForRegularUsers != nil {
+ securitySettings.AllowContainerCapabilitiesForRegularUsers = *payload.AllowContainerCapabilitiesForRegularUsers
+ }
+
+ if payload.AllowDeviceMappingForRegularUsers != nil {
+ securitySettings.AllowDeviceMappingForRegularUsers = *payload.AllowDeviceMappingForRegularUsers
+ }
+
+ if payload.AllowHostNamespaceForRegularUsers != nil {
+ securitySettings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers
+ }
+
+ if payload.AllowPrivilegedModeForRegularUsers != nil {
+ securitySettings.AllowPrivilegedModeForRegularUsers = *payload.AllowPrivilegedModeForRegularUsers
+ }
+
+ if payload.AllowStackManagementForRegularUsers != nil {
+ securitySettings.AllowStackManagementForRegularUsers = *payload.AllowStackManagementForRegularUsers
+ }
+
+ if payload.AllowVolumeBrowserForRegularUsers != nil {
+ securitySettings.AllowVolumeBrowserForRegularUsers = *payload.AllowVolumeBrowserForRegularUsers
+ }
+
+ if payload.EnableHostManagementFeatures != nil {
+ securitySettings.EnableHostManagementFeatures = *payload.EnableHostManagementFeatures
+ }
+
+ endpoint.SecuritySettings = securitySettings
+
+ err = handler.DataStore.Endpoint().UpdateEndpoint(portainer.EndpointID(endpointID), endpoint)
+ if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Failed persisting endpoint in database", err}
+ }
+
+ return response.JSON(w, endpoint)
+}
diff --git a/api/http/handler/endpoints/handler.go b/api/http/handler/endpoints/handler.go
index 3dc8689d6..ae23f5c9a 100644
--- a/api/http/handler/endpoints/handler.go
+++ b/api/http/handler/endpoints/handler.go
@@ -39,6 +39,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
h.Handle("/endpoints",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointCreate))).Methods(http.MethodPost)
+ h.Handle("/endpoints/{id}/settings",
+ bouncer.AdminAccess(httperror.LoggerHandler(h.endpointSettingsUpdate))).Methods(http.MethodPut)
h.Handle("/endpoints/snapshot",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointSnapshots))).Methods(http.MethodPost)
h.Handle("/endpoints",
diff --git a/api/http/handler/settings/settings_public.go b/api/http/handler/settings/settings_public.go
index e94f501e0..1a9efdb77 100644
--- a/api/http/handler/settings/settings_public.go
+++ b/api/http/handler/settings/settings_public.go
@@ -10,19 +10,11 @@ import (
)
type publicSettingsResponse struct {
- LogoURL string `json:"LogoURL"`
- AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
- AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
- AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
- AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
- AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
- AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
- AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
- AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
- EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
- EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
- OAuthLoginURI string `json:"OAuthLoginURI"`
- EnableTelemetry bool `json:"EnableTelemetry"`
+ LogoURL string `json:"LogoURL"`
+ AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
+ EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
+ OAuthLoginURI string `json:"OAuthLoginURI"`
+ EnableTelemetry bool `json:"EnableTelemetry"`
}
// GET request on /api/settings/public
@@ -33,18 +25,10 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
}
publicSettings := &publicSettingsResponse{
- LogoURL: settings.LogoURL,
- AuthenticationMethod: settings.AuthenticationMethod,
- AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
- AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
- AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
- AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
- AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
- AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
- AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
- EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
- EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
- EnableTelemetry: settings.EnableTelemetry,
+ LogoURL: settings.LogoURL,
+ AuthenticationMethod: settings.AuthenticationMethod,
+ EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
+ EnableTelemetry: settings.EnableTelemetry,
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
settings.OAuthSettings.AuthorizationURI,
settings.OAuthSettings.ClientID,
diff --git a/api/http/handler/settings/settings_update.go b/api/http/handler/settings/settings_update.go
index fbdf47bcf..f89dafc3c 100644
--- a/api/http/handler/settings/settings_update.go
+++ b/api/http/handler/settings/settings_update.go
@@ -14,25 +14,17 @@ import (
)
type settingsUpdatePayload struct {
- LogoURL *string
- BlackListedLabels []portainer.Pair
- AuthenticationMethod *int
- LDAPSettings *portainer.LDAPSettings
- OAuthSettings *portainer.OAuthSettings
- AllowBindMountsForRegularUsers *bool
- AllowPrivilegedModeForRegularUsers *bool
- AllowHostNamespaceForRegularUsers *bool
- AllowVolumeBrowserForRegularUsers *bool
- AllowDeviceMappingForRegularUsers *bool
- AllowStackManagementForRegularUsers *bool
- AllowContainerCapabilitiesForRegularUsers *bool
- EnableHostManagementFeatures *bool
- SnapshotInterval *string
- TemplatesURL *string
- EdgeAgentCheckinInterval *int
- EnableEdgeComputeFeatures *bool
- UserSessionTimeout *string
- EnableTelemetry *bool
+ LogoURL *string
+ BlackListedLabels []portainer.Pair
+ AuthenticationMethod *int
+ LDAPSettings *portainer.LDAPSettings
+ OAuthSettings *portainer.OAuthSettings
+ SnapshotInterval *string
+ TemplatesURL *string
+ EdgeAgentCheckinInterval *int
+ EnableEdgeComputeFeatures *bool
+ UserSessionTimeout *string
+ EnableTelemetry *bool
}
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
@@ -107,38 +99,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.OAuthSettings.ClientSecret = clientSecret
}
- if payload.AllowBindMountsForRegularUsers != nil {
- settings.AllowBindMountsForRegularUsers = *payload.AllowBindMountsForRegularUsers
- }
-
- if payload.AllowPrivilegedModeForRegularUsers != nil {
- settings.AllowPrivilegedModeForRegularUsers = *payload.AllowPrivilegedModeForRegularUsers
- }
-
- if payload.AllowVolumeBrowserForRegularUsers != nil {
- settings.AllowVolumeBrowserForRegularUsers = *payload.AllowVolumeBrowserForRegularUsers
- }
-
- if payload.EnableHostManagementFeatures != nil {
- settings.EnableHostManagementFeatures = *payload.EnableHostManagementFeatures
- }
-
if payload.EnableEdgeComputeFeatures != nil {
settings.EnableEdgeComputeFeatures = *payload.EnableEdgeComputeFeatures
}
- if payload.AllowHostNamespaceForRegularUsers != nil {
- settings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers
- }
-
- if payload.AllowStackManagementForRegularUsers != nil {
- settings.AllowStackManagementForRegularUsers = *payload.AllowStackManagementForRegularUsers
- }
-
- if payload.AllowContainerCapabilitiesForRegularUsers != nil {
- settings.AllowContainerCapabilitiesForRegularUsers = *payload.AllowContainerCapabilitiesForRegularUsers
- }
-
if payload.SnapshotInterval != nil && *payload.SnapshotInterval != settings.SnapshotInterval {
err := handler.updateSnapshotInterval(settings, *payload.SnapshotInterval)
if err != nil {
@@ -158,10 +122,6 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
handler.JWTService.SetUserSessionDuration(userSessionDuration)
}
- if payload.AllowDeviceMappingForRegularUsers != nil {
- settings.AllowDeviceMappingForRegularUsers = *payload.AllowDeviceMappingForRegularUsers
- }
-
if payload.EnableTelemetry != nil {
settings.EnableTelemetry = *payload.EnableTelemetry
}
diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go
index 52fc2844c..e574f524e 100644
--- a/api/http/handler/stacks/create_compose_stack.go
+++ b/api/http/handler/stacks/create_compose_stack.go
@@ -339,21 +339,18 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai
// clean it. Hence the use of the mutex.
// We should contribute to libcompose to support authentication without using the config.json file.
func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig) error {
- settings, err := handler.DataStore.Settings().Settings()
- if err != nil {
- 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) &&
+ securitySettings := &config.endpoint.SecuritySettings
+
+ if (!securitySettings.AllowBindMountsForRegularUsers ||
+ !securitySettings.AllowPrivilegedModeForRegularUsers ||
+ !securitySettings.AllowHostNamespaceForRegularUsers ||
+ !securitySettings.AllowDeviceMappingForRegularUsers ||
+ !securitySettings.AllowContainerCapabilitiesForRegularUsers) &&
!isAdminOrEndpointAdmin {
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
@@ -362,7 +359,7 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig)
return err
}
- err = handler.isValidStackFile(stackContent, settings)
+ err = handler.isValidStackFile(stackContent, securitySettings)
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 f7afbdeb5..1d475b8ad 100644
--- a/api/http/handler/stacks/create_swarm_stack.go
+++ b/api/http/handler/stacks/create_swarm_stack.go
@@ -344,16 +344,13 @@ func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portaine
}
func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) error {
- settings, err := handler.DataStore.Settings().Settings()
- if err != nil {
- return err
- }
-
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
if err != nil {
return err
}
+ settings := &config.endpoint.SecuritySettings
+
if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
diff --git a/api/http/handler/stacks/stack_create.go b/api/http/handler/stacks/stack_create.go
index a9fdf2f36..24c5a96bf 100644
--- a/api/http/handler/stacks/stack_create.go
+++ b/api/http/handler/stacks/stack_create.go
@@ -46,12 +46,14 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusBadRequest, "Invalid query parameter: endpointId", err}
}
- settings, err := handler.DataStore.Settings().Settings()
- if err != nil {
- return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
+ endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
+ if err == bolterrors.ErrObjectNotFound {
+ return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
+ } else if err != nil {
+ return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
- if !settings.AllowStackManagementForRegularUsers {
+ if !endpoint.SecuritySettings.AllowStackManagementForRegularUsers {
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve user info from request context", err}
@@ -69,13 +71,6 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
}
}
- endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
- if err == bolterrors.ErrObjectNotFound {
- return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
- } else if err != nil {
- return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
- }
-
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
@@ -129,7 +124,7 @@ func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request,
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)}
}
-func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *portainer.Settings) error {
+func (handler *Handler) isValidStackFile(stackFileContent []byte, securitySettings *portainer.EndpointSecuritySettings) error {
composeConfigYAML, err := loader.ParseYAML(stackFileContent)
if err != nil {
return err
@@ -154,7 +149,7 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port
for key := range composeConfig.Services {
service := composeConfig.Services[key]
- if !settings.AllowBindMountsForRegularUsers {
+ if !securitySettings.AllowBindMountsForRegularUsers {
for _, volume := range service.Volumes {
if volume.Type == "bind" {
return errors.New("bind-mount disabled for non administrator users")
@@ -162,19 +157,19 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port
}
}
- if !settings.AllowPrivilegedModeForRegularUsers && service.Privileged == true {
+ if !securitySettings.AllowPrivilegedModeForRegularUsers && service.Privileged == true {
return errors.New("privileged mode disabled for non administrator users")
}
- if !settings.AllowHostNamespaceForRegularUsers && service.Pid == "host" {
+ if !securitySettings.AllowHostNamespaceForRegularUsers && service.Pid == "host" {
return errors.New("pid host disabled for non administrator users")
}
- if !settings.AllowDeviceMappingForRegularUsers && service.Devices != nil && len(service.Devices) > 0 {
+ if !securitySettings.AllowDeviceMappingForRegularUsers && service.Devices != nil && len(service.Devices) > 0 {
return errors.New("device mapping disabled for non administrator users")
}
- if !settings.AllowContainerCapabilitiesForRegularUsers && (len(service.CapAdd) > 0 || len(service.CapDrop) > 0) {
+ if !securitySettings.AllowContainerCapabilitiesForRegularUsers && (len(service.CapAdd) > 0 || len(service.CapDrop) > 0) {
return errors.New("container capabilities disabled for non administrator users")
}
}
diff --git a/api/http/proxy/factory/docker/containers.go b/api/http/proxy/factory/docker/containers.go
index 3f9ecf9a1..dcce9c725 100644
--- a/api/http/proxy/factory/docker/containers.go
+++ b/api/http/proxy/factory/docker/containers.go
@@ -9,7 +9,7 @@ import (
"net/http"
"github.com/docker/docker/client"
- "github.com/portainer/portainer/api"
+ portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
"github.com/portainer/portainer/api/http/security"
"github.com/portainer/portainer/api/internal/authorization"
@@ -181,7 +181,7 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
}
if !isAdminOrEndpointAdmin {
- settings, err := transport.dataStore.Settings().Settings()
+ securitySettings, err := transport.fetchEndpointSecuritySettings()
if err != nil {
return nil, err
}
@@ -197,23 +197,23 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
return nil, err
}
- if !settings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged {
+ if !securitySettings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged {
return forbiddenResponse, errors.New("forbidden to use privileged mode")
}
- if !settings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" {
+ if !securitySettings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" {
return forbiddenResponse, errors.New("forbidden to use pid host namespace")
}
- if !settings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 {
+ if !securitySettings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 {
return forbiddenResponse, errors.New("forbidden to use device mapping")
}
- if !settings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) {
+ if !securitySettings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) {
return nil, errors.New("forbidden to use container capabilities")
}
- if !settings.AllowBindMountsForRegularUsers && (len(partialContainer.HostConfig.Binds) > 0) {
+ if !securitySettings.AllowBindMountsForRegularUsers && (len(partialContainer.HostConfig.Binds) > 0) {
return forbiddenResponse, errors.New("forbidden to use bind mounts")
}
diff --git a/api/http/proxy/factory/docker/services.go b/api/http/proxy/factory/docker/services.go
index 08f01a23c..5453f8ed8 100644
--- a/api/http/proxy/factory/docker/services.go
+++ b/api/http/proxy/factory/docker/services.go
@@ -11,7 +11,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
- "github.com/portainer/portainer/api"
+ portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
"github.com/portainer/portainer/api/internal/authorization"
)
@@ -111,7 +111,7 @@ func (transport *Transport) decorateServiceCreationOperation(request *http.Reque
}
if !isAdminOrEndpointAdmin {
- settings, err := transport.dataStore.Settings().Settings()
+ securitySettings, err := transport.fetchEndpointSecuritySettings()
if err != nil {
return nil, err
}
@@ -127,7 +127,7 @@ func (transport *Transport) decorateServiceCreationOperation(request *http.Reque
return nil, err
}
- if !settings.AllowBindMountsForRegularUsers && (len(partialService.TaskTemplate.ContainerSpec.Mounts) > 0) {
+ if !securitySettings.AllowBindMountsForRegularUsers && (len(partialService.TaskTemplate.ContainerSpec.Mounts) > 0) {
for _, mount := range partialService.TaskTemplate.ContainerSpec.Mounts {
if mount.Type == "bind" {
return forbiddenResponse, errors.New("forbidden to use bind mounts")
diff --git a/api/http/proxy/factory/docker/transport.go b/api/http/proxy/factory/docker/transport.go
index 623875f53..5ac4efb4f 100644
--- a/api/http/proxy/factory/docker/transport.go
+++ b/api/http/proxy/factory/docker/transport.go
@@ -11,7 +11,7 @@ import (
"strings"
"github.com/docker/docker/client"
- "github.com/portainer/portainer/api"
+ portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/proxy/factory/responseutils"
"github.com/portainer/portainer/api/http/security"
@@ -407,12 +407,12 @@ func (transport *Transport) restrictedResourceOperation(request *http.Request, r
if tokenData.Role != portainer.AdministratorRole {
if volumeBrowseRestrictionCheck {
- settings, err := transport.dataStore.Settings().Settings()
+ securitySettings, err := transport.fetchEndpointSecuritySettings()
if err != nil {
return nil, err
}
- if !settings.AllowVolumeBrowserForRegularUsers {
+ if !securitySettings.AllowVolumeBrowserForRegularUsers {
return responseutils.WriteAccessDeniedResponse()
}
}
@@ -682,3 +682,12 @@ func (transport *Transport) isAdminOrEndpointAdmin(request *http.Request) (bool,
return tokenData.Role == portainer.AdministratorRole, nil
}
+
+func (transport *Transport) fetchEndpointSecuritySettings() (*portainer.EndpointSecuritySettings, error) {
+ endpoint, err := transport.dataStore.Endpoint().Endpoint(portainer.EndpointID(transport.endpoint.ID))
+ if err != nil {
+ return nil, err
+ }
+
+ return &endpoint.SecuritySettings, nil
+}
diff --git a/api/portainer.go b/api/portainer.go
index 00e1c32de..d03a55b41 100644
--- a/api/portainer.go
+++ b/api/portainer.go
@@ -209,6 +209,7 @@ type (
EdgeCheckinInterval int `json:"EdgeCheckinInterval"`
Kubernetes KubernetesData `json:"Kubernetes"`
ComposeSyntaxMaxVersion string `json:"ComposeSyntaxMaxVersion"`
+ SecuritySettings EndpointSecuritySettings
// Deprecated fields
// Deprecated in DBVersion == 4
@@ -272,6 +273,18 @@ type (
// Deprecated
EndpointSyncJob struct{}
+ // EndpointSecuritySettings represents settings for an endpoint
+ EndpointSecuritySettings struct {
+ AllowBindMountsForRegularUsers bool `json:"allowBindMountsForRegularUsers"`
+ AllowPrivilegedModeForRegularUsers bool `json:"allowPrivilegedModeForRegularUsers"`
+ AllowVolumeBrowserForRegularUsers bool `json:"allowVolumeBrowserForRegularUsers"`
+ AllowHostNamespaceForRegularUsers bool `json:"allowHostNamespaceForRegularUsers"`
+ AllowDeviceMappingForRegularUsers bool `json:"allowDeviceMappingForRegularUsers"`
+ AllowStackManagementForRegularUsers bool `json:"allowStackManagementForRegularUsers"`
+ AllowContainerCapabilitiesForRegularUsers bool `json:"allowContainerCapabilitiesForRegularUsers"`
+ EnableHostManagementFeatures bool `json:"enableHostManagementFeatures"`
+ }
+
// EndpointType represents the type of an endpoint
EndpointType int
@@ -516,29 +529,31 @@ type (
// Settings represents the application settings
Settings struct {
- LogoURL string `json:"LogoURL"`
- BlackListedLabels []Pair `json:"BlackListedLabels"`
- AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
- LDAPSettings LDAPSettings `json:"LDAPSettings"`
- OAuthSettings OAuthSettings `json:"OAuthSettings"`
- AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
- AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
- AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
- AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
- AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
- AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
- AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
- SnapshotInterval string `json:"SnapshotInterval"`
- TemplatesURL string `json:"TemplatesURL"`
- EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
- EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
- EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
- UserSessionTimeout string `json:"UserSessionTimeout"`
- EnableTelemetry bool `json:"EnableTelemetry"`
+ LogoURL string `json:"LogoURL"`
+ BlackListedLabels []Pair `json:"BlackListedLabels"`
+ AuthenticationMethod AuthenticationMethod `json:"AuthenticationMethod"`
+ LDAPSettings LDAPSettings `json:"LDAPSettings"`
+ OAuthSettings OAuthSettings `json:"OAuthSettings"`
+ SnapshotInterval string `json:"SnapshotInterval"`
+ TemplatesURL string `json:"TemplatesURL"`
+ EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
+ EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
+ UserSessionTimeout string `json:"UserSessionTimeout"`
+ EnableTelemetry bool `json:"EnableTelemetry"`
// Deprecated fields
DisplayDonationHeader bool
DisplayExternalContributors bool
+
+ // Deprecated fields v26
+ EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
+ AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
+ AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
+ AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
+ AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
+ AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
+ AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
+ AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
}
// SnapshotJob represents a scheduled job that can create endpoint snapshots
@@ -1127,7 +1142,7 @@ const (
// APIVersion is the version number of the Portainer API
APIVersion = "2.1.0"
// DBVersion is the version number of the Portainer database
- DBVersion = 25
+ DBVersion = 26
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
ComposeSyntaxMaxVersion = "3.9"
// AssetsServerURL represents the URL of the Portainer asset server
diff --git a/app/assets/css/app.css b/app/assets/css/app.css
index f813dbb2c..4ce4aab3c 100644
--- a/app/assets/css/app.css
+++ b/app/assets/css/app.css
@@ -625,57 +625,6 @@ ul.sidebar .sidebar-list .sidebar-sublist a.active {
margin-left: 21px;
}
-/* switch box */
-:root {
- --switch-size: 24px;
-}
-
-.switch input {
- display: none;
-}
-
-.switch i,
-.bootbox-form .checkbox i {
- display: inline-block;
- vertical-align: middle;
- cursor: pointer;
- padding-right: var(--switch-size);
- transition: all ease 0.2s;
- -webkit-transition: all ease 0.2s;
- -moz-transition: all ease 0.2s;
- -o-transition: all ease 0.2s;
- border-radius: var(--switch-size);
- box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.5);
-}
-
-.switch i:before,
-.bootbox-form .checkbox i:before {
- display: block;
- content: '';
- width: var(--switch-size);
- height: var(--switch-size);
- border-radius: var(--switch-size);
- background: white;
- box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.5);
-}
-
-.switch :checked + i,
-.bootbox-form .checkbox :checked ~ i {
- padding-right: 0;
- padding-left: var(--switch-size);
- -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
- -moz-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
- box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
-}
-/* !switch box */
-
-/* small switch box */
-.switch.small {
- --switch-size: 12px;
-}
-
-/* !small switch box */
-
.boxselector_wrapper {
display: flex;
flex-flow: row wrap;
diff --git a/app/docker/__module.js b/app/docker/__module.js
index 33495ab47..516134d7d 100644
--- a/app/docker/__module.js
+++ b/app/docker/__module.js
@@ -581,6 +581,16 @@ angular.module('portainer.docker', ['portainer.app']).config([
},
};
+ const dockerFeaturesConfiguration = {
+ name: 'docker.featuresConfiguration',
+ url: '/feat-config',
+ views: {
+ 'content@': {
+ component: 'dockerFeaturesConfigurationView',
+ },
+ },
+ };
+
$stateRegistryProvider.register(configs);
$stateRegistryProvider.register(config);
$stateRegistryProvider.register(configCreation);
@@ -630,5 +640,6 @@ angular.module('portainer.docker', ['portainer.app']).config([
$stateRegistryProvider.register(volume);
$stateRegistryProvider.register(volumeBrowse);
$stateRegistryProvider.register(volumeCreation);
+ $stateRegistryProvider.register(dockerFeaturesConfiguration);
},
]);
diff --git a/app/docker/components/dockerSidebarContent/dockerSidebarContent.html b/app/docker/components/dockerSidebarContent/dockerSidebarContent.html
index 5af425761..484451142 100644
--- a/app/docker/components/dockerSidebarContent/dockerSidebarContent.html
+++ b/app/docker/components/dockerSidebarContent/dockerSidebarContent.html
@@ -37,7 +37,15 @@
diff --git a/app/docker/views/containers/create/createContainerController.js b/app/docker/views/containers/create/createContainerController.js
index a59371819..ce5f65ecf 100644
--- a/app/docker/views/containers/create/createContainerController.js
+++ b/app/docker/views/containers/create/createContainerController.js
@@ -27,9 +27,9 @@ angular.module('portainer.docker').controller('CreateContainerController', [
'ModalService',
'RegistryService',
'SystemService',
- 'SettingsService',
'PluginService',
'HttpRequestHelper',
+ 'endpoint',
function (
$q,
$scope,
@@ -53,9 +53,9 @@ angular.module('portainer.docker').controller('CreateContainerController', [
ModalService,
RegistryService,
SystemService,
- SettingsService,
PluginService,
- HttpRequestHelper
+ HttpRequestHelper,
+ endpoint
) {
$scope.create = create;
@@ -709,14 +709,8 @@ angular.module('portainer.docker').controller('CreateContainerController', [
Notifications.error('Failure', err, 'Unable to retrieve engine details');
});
- SettingsService.publicSettings()
- .then(function success(data) {
- $scope.allowBindMounts = $scope.isAdminOrEndpointAdmin || data.AllowBindMountsForRegularUsers;
- $scope.allowPrivilegedMode = data.AllowPrivilegedModeForRegularUsers;
- })
- .catch(function error(err) {
- Notifications.error('Failure', err, 'Unable to retrieve application settings');
- });
+ $scope.allowBindMounts = $scope.isAdminOrEndpointAdmin || endpoint.SecuritySettings.allowBindMountsForRegularUsers;
+ $scope.allowPrivilegedMode = endpoint.SecuritySettings.allowPrivilegedModeForRegularUsers;
PluginService.loggingPlugins(apiVersion < 1.25).then(function success(loggingDrivers) {
$scope.availableLoggingDrivers = loggingDrivers;
@@ -933,15 +927,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
}
async function shouldShowDevices() {
- const { allowDeviceMappingForRegularUsers } = $scope.applicationState.application;
-
- return allowDeviceMappingForRegularUsers || Authentication.isAdmin();
+ return endpoint.SecuritySettings.allowDeviceMappingForRegularUsers || Authentication.isAdmin();
}
async function checkIfContainerCapabilitiesEnabled() {
- const { allowContainerCapabilitiesForRegularUsers } = $scope.applicationState.application;
-
- return allowContainerCapabilitiesForRegularUsers || Authentication.isAdmin();
+ return endpoint.SecuritySettings.allowContainerCapabilitiesForRegularUsers || Authentication.isAdmin();
}
initView();
diff --git a/app/docker/views/containers/edit/containerController.js b/app/docker/views/containers/edit/containerController.js
index 1b0cf5a81..95affc028 100644
--- a/app/docker/views/containers/edit/containerController.js
+++ b/app/docker/views/containers/edit/containerController.js
@@ -22,6 +22,7 @@ angular.module('portainer.docker').controller('ContainerController', [
'HttpRequestHelper',
'Authentication',
'StateManager',
+ 'endpoint',
function (
$q,
$scope,
@@ -41,7 +42,8 @@ angular.module('portainer.docker').controller('ContainerController', [
ImageService,
HttpRequestHelper,
Authentication,
- StateManager
+ StateManager,
+ endpoint
) {
$scope.activityTime = 0;
$scope.portBindings = [];
@@ -97,14 +99,13 @@ angular.module('portainer.docker').controller('ContainerController', [
const inSwarm = $scope.container.Config.Labels['com.docker.swarm.service.id'];
const autoRemove = $scope.container.HostConfig.AutoRemove;
const admin = Authentication.isAdmin();
- const appState = StateManager.getState();
const {
allowContainerCapabilitiesForRegularUsers,
allowHostNamespaceForRegularUsers,
allowDeviceMappingForRegularUsers,
allowBindMountsForRegularUsers,
allowPrivilegedModeForRegularUsers,
- } = appState.application;
+ } = endpoint.SecuritySettings;
const settingRestrictsRegularUsers =
!allowContainerCapabilitiesForRegularUsers ||
diff --git a/app/docker/views/dashboard/dashboardController.js b/app/docker/views/dashboard/dashboardController.js
index dfcac9981..0e09b30d1 100644
--- a/app/docker/views/dashboard/dashboardController.js
+++ b/app/docker/views/dashboard/dashboardController.js
@@ -17,6 +17,7 @@ angular.module('portainer.docker').controller('DashboardController', [
'EndpointProvider',
'StateManager',
'TagService',
+ 'endpoint',
function (
$scope,
$q,
@@ -32,7 +33,8 @@ angular.module('portainer.docker').controller('DashboardController', [
Notifications,
EndpointProvider,
StateManager,
- TagService
+ TagService,
+ endpoint
) {
$scope.dismissInformationPanel = function (id) {
StateManager.dismissInformationPanel(id);
@@ -89,9 +91,8 @@ angular.module('portainer.docker').controller('DashboardController', [
async function shouldShowStacks() {
const isAdmin = Authentication.isAdmin();
- const { allowStackManagementForRegularUsers } = $scope.applicationState.application;
- return isAdmin || allowStackManagementForRegularUsers;
+ return isAdmin || endpoint.SecuritySettings.allowStackManagementForRegularUsers;
}
initView();
diff --git a/app/docker/views/docker-features-configuration/docker-features-configuration.controller.js b/app/docker/views/docker-features-configuration/docker-features-configuration.controller.js
new file mode 100644
index 000000000..bedec4867
--- /dev/null
+++ b/app/docker/views/docker-features-configuration/docker-features-configuration.controller.js
@@ -0,0 +1,94 @@
+export default class DockerFeaturesConfigurationController {
+ /* @ngInject */
+ constructor($async, EndpointService, Notifications, StateManager) {
+ this.$async = $async;
+ this.EndpointService = EndpointService;
+ this.Notifications = Notifications;
+ this.StateManager = StateManager;
+
+ this.formValues = {
+ enableHostManagementFeatures: false,
+ allowVolumeBrowserForRegularUsers: false,
+ disableBindMountsForRegularUsers: false,
+ disablePrivilegedModeForRegularUsers: false,
+ disableHostNamespaceForRegularUsers: false,
+ disableStackManagementForRegularUsers: false,
+ disableDeviceMappingForRegularUsers: false,
+ disableContainerCapabilitiesForRegularUsers: false,
+ };
+
+ this.isAgent = false;
+
+ this.state = {
+ actionInProgress: false,
+ };
+
+ this.save = this.save.bind(this);
+ }
+
+ isContainerEditDisabled() {
+ const {
+ disableBindMountsForRegularUsers,
+ disableHostNamespaceForRegularUsers,
+ disablePrivilegedModeForRegularUsers,
+ disableDeviceMappingForRegularUsers,
+ disableContainerCapabilitiesForRegularUsers,
+ } = this.formValues;
+ return (
+ disableBindMountsForRegularUsers ||
+ disableHostNamespaceForRegularUsers ||
+ disablePrivilegedModeForRegularUsers ||
+ disableDeviceMappingForRegularUsers ||
+ disableContainerCapabilitiesForRegularUsers
+ );
+ }
+
+ async save() {
+ return this.$async(async () => {
+ try {
+ this.state.actionInProgress = true;
+ const securitySettings = {
+ enableHostManagementFeatures: this.formValues.enableHostManagementFeatures,
+ allowBindMountsForRegularUsers: !this.formValues.disableBindMountsForRegularUsers,
+ allowPrivilegedModeForRegularUsers: !this.formValues.disablePrivilegedModeForRegularUsers,
+ allowVolumeBrowserForRegularUsers: this.formValues.allowVolumeBrowserForRegularUsers,
+ allowHostNamespaceForRegularUsers: !this.formValues.disableHostNamespaceForRegularUsers,
+ allowDeviceMappingForRegularUsers: !this.formValues.disableDeviceMappingForRegularUsers,
+ allowStackManagementForRegularUsers: !this.formValues.disableStackManagementForRegularUsers,
+ allowContainerCapabilitiesForRegularUsers: !this.formValues.disableContainerCapabilitiesForRegularUsers,
+ };
+
+ await this.EndpointService.updateSecuritySettings(this.endpoint.Id, securitySettings);
+
+ this.endpoint.SecuritySettings = securitySettings;
+ this.Notifications.success('Saved settings successfully');
+ } catch (e) {
+ this.Notifications.error('Failure', e, 'Failed saving settings');
+ }
+ this.state.actionInProgress = false;
+ });
+ }
+
+ checkAgent() {
+ const applicationState = this.StateManager.getState();
+ return applicationState.endpoint.mode.agentProxy;
+ }
+
+ $onInit() {
+ const securitySettings = this.endpoint.SecuritySettings;
+
+ const isAgent = this.checkAgent();
+ this.isAgent = isAgent;
+
+ this.formValues = {
+ enableHostManagementFeatures: isAgent && securitySettings.enableHostManagementFeatures,
+ allowVolumeBrowserForRegularUsers: isAgent && securitySettings.allowVolumeBrowserForRegularUsers,
+ disableBindMountsForRegularUsers: !securitySettings.allowBindMountsForRegularUsers,
+ disablePrivilegedModeForRegularUsers: !securitySettings.allowPrivilegedModeForRegularUsers,
+ disableHostNamespaceForRegularUsers: !securitySettings.allowHostNamespaceForRegularUsers,
+ disableDeviceMappingForRegularUsers: !securitySettings.allowDeviceMappingForRegularUsers,
+ disableStackManagementForRegularUsers: !securitySettings.allowStackManagementForRegularUsers,
+ disableContainerCapabilitiesForRegularUsers: !securitySettings.allowContainerCapabilitiesForRegularUsers,
+ };
+ }
+}
diff --git a/app/docker/views/docker-features-configuration/docker-features-configuration.html b/app/docker/views/docker-features-configuration/docker-features-configuration.html
new file mode 100644
index 000000000..0c5f033de
--- /dev/null
+++ b/app/docker/views/docker-features-configuration/docker-features-configuration.html
@@ -0,0 +1,137 @@
+
+
+ Docker configuration
+
+
+
diff --git a/app/docker/views/docker-features-configuration/index.js b/app/docker/views/docker-features-configuration/index.js
new file mode 100644
index 000000000..5aec2152e
--- /dev/null
+++ b/app/docker/views/docker-features-configuration/index.js
@@ -0,0 +1,11 @@
+import angular from 'angular';
+
+import controller from './docker-features-configuration.controller';
+
+angular.module('portainer.docker').component('dockerFeaturesConfigurationView', {
+ templateUrl: './docker-features-configuration.html',
+ controller,
+ bindings: {
+ endpoint: '<',
+ },
+});
diff --git a/app/docker/views/host/host-view-controller.js b/app/docker/views/host/host-view-controller.js
index ebb5a6975..b7433ed0f 100644
--- a/app/docker/views/host/host-view-controller.js
+++ b/app/docker/views/host/host-view-controller.js
@@ -29,7 +29,7 @@ angular.module('portainer.docker').controller('HostViewController', [
ctrl.state.isAdmin = Authentication.isAdmin();
var agentApiVersion = applicationState.endpoint.agentApiVersion;
ctrl.state.agentApiVersion = agentApiVersion;
- ctrl.state.enableHostManagementFeatures = applicationState.application.enableHostManagementFeatures;
+ ctrl.state.enableHostManagementFeatures = ctrl.endpoint.SecuritySettings.enableHostManagementFeatures;
$q.all({
version: SystemService.version(),
diff --git a/app/docker/views/host/host-view.js b/app/docker/views/host/host-view.js
index eec3d03e5..db3904bb0 100644
--- a/app/docker/views/host/host-view.js
+++ b/app/docker/views/host/host-view.js
@@ -1,4 +1,7 @@
angular.module('portainer.docker').component('hostView', {
templateUrl: './host-view.html',
controller: 'HostViewController',
+ bindings: {
+ endpoint: '<',
+ },
});
diff --git a/app/docker/views/nodes/node-details/node-details-view-controller.js b/app/docker/views/nodes/node-details/node-details-view-controller.js
index 020c3b586..bed849412 100644
--- a/app/docker/views/nodes/node-details/node-details-view-controller.js
+++ b/app/docker/views/nodes/node-details/node-details-view-controller.js
@@ -20,7 +20,7 @@ angular.module('portainer.docker').controller('NodeDetailsViewController', [
var applicationState = StateManager.getState();
ctrl.state.isAgent = applicationState.endpoint.mode.agentProxy;
ctrl.state.isAdmin = Authentication.isAdmin();
- ctrl.state.enableHostManagementFeatures = applicationState.application.enableHostManagementFeatures;
+ ctrl.state.enableHostManagementFeatures = ctrl.endpoint.SecuritySettings.enableHostManagementFeatures;
var fetchJobs = ctrl.state.isAdmin && ctrl.state.isAgent;
diff --git a/app/docker/views/nodes/node-details/node-details-view.js b/app/docker/views/nodes/node-details/node-details-view.js
index 929b4f4ad..be55e2a80 100644
--- a/app/docker/views/nodes/node-details/node-details-view.js
+++ b/app/docker/views/nodes/node-details/node-details-view.js
@@ -1,4 +1,7 @@
angular.module('portainer.docker').component('nodeDetailsView', {
templateUrl: './node-details-view.html',
controller: 'NodeDetailsViewController',
+ bindings: {
+ endpoint: '<',
+ },
});
diff --git a/app/docker/views/services/create/createServiceController.js b/app/docker/views/services/create/createServiceController.js
index a59d74207..d61cfdfff 100644
--- a/app/docker/views/services/create/createServiceController.js
+++ b/app/docker/views/services/create/createServiceController.js
@@ -30,9 +30,9 @@ angular.module('portainer.docker').controller('CreateServiceController', [
'RegistryService',
'HttpRequestHelper',
'NodeService',
- 'SettingsService',
'WebhookService',
'EndpointProvider',
+ 'endpoint',
function (
$q,
$scope,
@@ -56,9 +56,9 @@ angular.module('portainer.docker').controller('CreateServiceController', [
RegistryService,
HttpRequestHelper,
NodeService,
- SettingsService,
WebhookService,
- EndpointProvider
+ EndpointProvider,
+ endpoint
) {
$scope.formValues = {
Name: '',
@@ -593,10 +593,9 @@ angular.module('portainer.docker').controller('CreateServiceController', [
async function checkIfAllowedBindMounts() {
const isAdmin = Authentication.isAdmin();
- const settings = await SettingsService.publicSettings();
- const { AllowBindMountsForRegularUsers } = settings;
+ const { allowBindMountsForRegularUsers } = endpoint.SecuritySettings;
- return isAdmin || AllowBindMountsForRegularUsers;
+ return isAdmin || allowBindMountsForRegularUsers;
}
},
]);
diff --git a/app/docker/views/services/edit/serviceController.js b/app/docker/views/services/edit/serviceController.js
index b73938d74..6a44c4d00 100644
--- a/app/docker/views/services/edit/serviceController.js
+++ b/app/docker/views/services/edit/serviceController.js
@@ -34,7 +34,6 @@ angular.module('portainer.docker').controller('ServiceController', [
'SecretService',
'ImageService',
'SecretHelper',
- 'Service',
'ServiceHelper',
'LabelHelper',
'TaskService',
@@ -45,7 +44,6 @@ angular.module('portainer.docker').controller('ServiceController', [
'ModalService',
'PluginService',
'Authentication',
- 'SettingsService',
'VolumeService',
'ImageHelper',
'WebhookService',
@@ -53,6 +51,7 @@ angular.module('portainer.docker').controller('ServiceController', [
'clipboard',
'WebhookHelper',
'NetworkService',
+ 'endpoint',
function (
$q,
$scope,
@@ -67,7 +66,6 @@ angular.module('portainer.docker').controller('ServiceController', [
SecretService,
ImageService,
SecretHelper,
- Service,
ServiceHelper,
LabelHelper,
TaskService,
@@ -78,14 +76,14 @@ angular.module('portainer.docker').controller('ServiceController', [
ModalService,
PluginService,
Authentication,
- SettingsService,
VolumeService,
ImageHelper,
WebhookService,
EndpointProvider,
clipboard,
WebhookHelper,
- NetworkService
+ NetworkService,
+ endpoint
) {
$scope.state = {
updateInProgress: false,
@@ -666,7 +664,6 @@ angular.module('portainer.docker').controller('ServiceController', [
availableImages: ImageService.images(),
availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25),
availableNetworks: NetworkService.networks(true, true, apiVersion >= 1.25),
- settings: SettingsService.publicSettings(),
webhooks: WebhookService.webhooks(service.Id, EndpointProvider.endpointID()),
});
})
@@ -677,7 +674,7 @@ angular.module('portainer.docker').controller('ServiceController', [
$scope.availableImages = ImageService.getUniqueTagListFromImages(data.availableImages);
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
$scope.availableVolumes = data.volumes;
- $scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
+ $scope.allowBindMounts = endpoint.SecuritySettings.allowBindMountsForRegularUsers;
$scope.isAdmin = Authentication.isAdmin();
$scope.availableNetworks = data.availableNetworks;
$scope.swarmNetworks = _.filter($scope.availableNetworks, (network) => network.Scope === 'swarm');
diff --git a/app/docker/views/volumes/volumesController.js b/app/docker/views/volumes/volumesController.js
index 8496a9e22..2044adf9d 100644
--- a/app/docker/views/volumes/volumesController.js
+++ b/app/docker/views/volumes/volumesController.js
@@ -10,7 +10,8 @@ angular.module('portainer.docker').controller('VolumesController', [
'EndpointProvider',
'Authentication',
'ModalService',
- function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, EndpointProvider, Authentication, ModalService) {
+ 'endpoint',
+ function ($q, $scope, $state, VolumeService, ServiceService, VolumeHelper, Notifications, HttpRequestHelper, EndpointProvider, Authentication, ModalService, endpoint) {
$scope.removeAction = function (selectedItems) {
ModalService.confirmDeletion('Do you want to remove the selected volume(s)?', (confirmed) => {
if (confirmed) {
@@ -75,8 +76,7 @@ angular.module('portainer.docker').controller('VolumesController', [
function initView() {
getVolumes();
- $scope.showBrowseAction =
- $scope.applicationState.endpoint.mode.agentProxy && (Authentication.isAdmin() || $scope.applicationState.application.enableVolumeBrowserForNonAdminUsers);
+ $scope.showBrowseAction = $scope.applicationState.endpoint.mode.agentProxy && (Authentication.isAdmin() || endpoint.SecuritySettings.allowVolumeBrowserForRegularUsers);
}
initView();
diff --git a/app/portainer/components/forms/por-switch-field/por-switch-field.css b/app/portainer/components/forms/por-switch-field/por-switch-field.css
new file mode 100644
index 000000000..5fa6465b9
--- /dev/null
+++ b/app/portainer/components/forms/por-switch-field/por-switch-field.css
@@ -0,0 +1,52 @@
+/* switch box */
+
+.switch {
+ --switch-size: 24px;
+}
+
+.switch.small {
+ --switch-size: 12px;
+}
+
+.switch input {
+ display: none;
+}
+
+.switch i,
+.bootbox-form .checkbox i {
+ display: inline-block;
+ vertical-align: middle;
+ cursor: pointer;
+ padding-right: var(--switch-size);
+ transition: all ease 0.2s;
+ -webkit-transition: all ease 0.2s;
+ -moz-transition: all ease 0.2s;
+ -o-transition: all ease 0.2s;
+ border-radius: var(--switch-size);
+ box-shadow: inset 0 0 1px 1px rgba(0, 0, 0, 0.5);
+}
+
+.switch i:before,
+.bootbox-form .checkbox i:before {
+ display: block;
+ content: '';
+ width: var(--switch-size);
+ height: var(--switch-size);
+ border-radius: var(--switch-size);
+ background: white;
+ box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.5);
+}
+
+.switch :checked + i,
+.bootbox-form .checkbox :checked ~ i {
+ padding-right: 0;
+ padding-left: var(--switch-size);
+ -webkit-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
+ -moz-box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
+ box-shadow: inset 0 0 1px rgba(0, 0, 0, 0.5), inset 0 0 40px #337ab7;
+}
+
+.switch :disabled + i {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
diff --git a/app/portainer/components/forms/por-switch-field/por-switch-field.html b/app/portainer/components/forms/por-switch-field/por-switch-field.html
new file mode 100644
index 000000000..d20ae2776
--- /dev/null
+++ b/app/portainer/components/forms/por-switch-field/por-switch-field.html
@@ -0,0 +1,14 @@
+
diff --git a/app/portainer/components/forms/por-switch-field/por-switch-field.js b/app/portainer/components/forms/por-switch-field/por-switch-field.js
new file mode 100644
index 000000000..800604209
--- /dev/null
+++ b/app/portainer/components/forms/por-switch-field/por-switch-field.js
@@ -0,0 +1,18 @@
+import angular from 'angular';
+
+import './por-switch-field.css';
+
+export const porSwitchField = {
+ templateUrl: './por-switch-field.html',
+ bindings: {
+ tooltip: '@',
+ ngModel: '=',
+ label: '@',
+ name: '@',
+ labelClass: '@',
+ disabled: '<',
+ onChange: '<',
+ },
+};
+
+angular.module('portainer.app').component('porSwitchField', porSwitchField);
diff --git a/app/portainer/components/forms/por-switch/por-switch.html b/app/portainer/components/forms/por-switch/por-switch.html
new file mode 100644
index 000000000..92e8ab1fe
--- /dev/null
+++ b/app/portainer/components/forms/por-switch/por-switch.html
@@ -0,0 +1,3 @@
+
diff --git a/app/portainer/components/forms/por-switch/por-switch.js b/app/portainer/components/forms/por-switch/por-switch.js
new file mode 100644
index 000000000..5382b437f
--- /dev/null
+++ b/app/portainer/components/forms/por-switch/por-switch.js
@@ -0,0 +1,15 @@
+import angular from 'angular';
+
+const porSwitch = {
+ templateUrl: './por-switch.html',
+ bindings: {
+ ngModel: '=',
+ id: '@',
+ className: '@',
+ name: '@',
+ disabled: '<',
+ onChange: '<',
+ },
+};
+
+angular.module('portainer.app').component('porSwitch', porSwitch);
diff --git a/app/portainer/models/settings.js b/app/portainer/models/settings.js
index 40f0003f4..a15b6ba04 100644
--- a/app/portainer/models/settings.js
+++ b/app/portainer/models/settings.js
@@ -4,16 +4,8 @@ export function SettingsViewModel(data) {
this.AuthenticationMethod = data.AuthenticationMethod;
this.LDAPSettings = data.LDAPSettings;
this.OAuthSettings = new OAuthSettingsViewModel(data.OAuthSettings);
- this.AllowBindMountsForRegularUsers = data.AllowBindMountsForRegularUsers;
- this.AllowPrivilegedModeForRegularUsers = data.AllowPrivilegedModeForRegularUsers;
- this.AllowVolumeBrowserForRegularUsers = data.AllowVolumeBrowserForRegularUsers;
- this.AllowHostNamespaceForRegularUsers = data.AllowHostNamespaceForRegularUsers;
- this.AllowDeviceMappingForRegularUsers = data.AllowDeviceMappingForRegularUsers;
- this.AllowStackManagementForRegularUsers = data.AllowStackManagementForRegularUsers;
- this.AllowContainerCapabilitiesForRegularUsers = data.AllowContainerCapabilitiesForRegularUsers;
this.SnapshotInterval = data.SnapshotInterval;
this.TemplatesURL = data.TemplatesURL;
- this.EnableHostManagementFeatures = data.EnableHostManagementFeatures;
this.EdgeAgentCheckinInterval = data.EdgeAgentCheckinInterval;
this.EnableEdgeComputeFeatures = data.EnableEdgeComputeFeatures;
this.UserSessionTimeout = data.UserSessionTimeout;
@@ -21,15 +13,7 @@ export function SettingsViewModel(data) {
}
export function PublicSettingsViewModel(settings) {
- this.AllowBindMountsForRegularUsers = settings.AllowBindMountsForRegularUsers;
- this.AllowPrivilegedModeForRegularUsers = settings.AllowPrivilegedModeForRegularUsers;
- this.AllowVolumeBrowserForRegularUsers = settings.AllowVolumeBrowserForRegularUsers;
- this.AllowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
- this.AllowStackManagementForRegularUsers = settings.AllowStackManagementForRegularUsers;
- this.AllowContainerCapabilitiesForRegularUsers = settings.AllowContainerCapabilitiesForRegularUsers;
- this.AllowHostNamespaceForRegularUsers = settings.AllowHostNamespaceForRegularUsers;
this.AuthenticationMethod = settings.AuthenticationMethod;
- this.EnableHostManagementFeatures = settings.EnableHostManagementFeatures;
this.EnableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
this.LogoURL = settings.LogoURL;
this.OAuthLoginURI = settings.OAuthLoginURI;
diff --git a/app/portainer/rest/endpoint.js b/app/portainer/rest/endpoint.js
index 65a6b82d4..71842f9a1 100644
--- a/app/portainer/rest/endpoint.js
+++ b/app/portainer/rest/endpoint.js
@@ -21,6 +21,7 @@ angular.module('portainer.app').factory('Endpoints', [
snapshots: { method: 'POST', params: { action: 'snapshot' } },
snapshot: { method: 'POST', params: { id: '@id', action: 'snapshot' } },
status: { method: 'GET', params: { id: '@id', action: 'status' } },
+ updateSecuritySettings: { method: 'PUT', params: { id: '@id', action: 'settings' } },
}
);
},
diff --git a/app/portainer/services/api/endpointService.js b/app/portainer/services/api/endpointService.js
index 43f55201d..a2cc0fef8 100644
--- a/app/portainer/services/api/endpointService.js
+++ b/app/portainer/services/api/endpointService.js
@@ -6,7 +6,9 @@ angular.module('portainer.app').factory('EndpointService', [
'FileUploadService',
function EndpointServiceFactory($q, Endpoints, FileUploadService) {
'use strict';
- var service = {};
+ var service = {
+ updateSecuritySettings,
+ };
service.endpoint = function (endpointID) {
return Endpoints.get({ id: endpointID }).$promise;
@@ -146,5 +148,9 @@ angular.module('portainer.app').factory('EndpointService', [
};
return service;
+
+ function updateSecuritySettings(id, securitySettings) {
+ return Endpoints.updateSecuritySettings({ id }, securitySettings).$promise;
+ }
},
]);
diff --git a/app/portainer/services/stateManager.js b/app/portainer/services/stateManager.js
index c98185fa9..401ae1422 100644
--- a/app/portainer/services/stateManager.js
+++ b/app/portainer/services/stateManager.js
@@ -72,51 +72,11 @@ angular.module('portainer.app').factory('StateManager', [
LocalStorage.storeApplicationState(state.application);
};
- manager.updateEnableHostManagementFeatures = function (enableHostManagementFeatures) {
- state.application.enableHostManagementFeatures = enableHostManagementFeatures;
- LocalStorage.storeApplicationState(state.application);
- };
-
- manager.updateEnableVolumeBrowserForNonAdminUsers = function (enableVolumeBrowserForNonAdminUsers) {
- state.application.enableVolumeBrowserForNonAdminUsers = enableVolumeBrowserForNonAdminUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
manager.updateEnableEdgeComputeFeatures = function updateEnableEdgeComputeFeatures(enableEdgeComputeFeatures) {
state.application.enableEdgeComputeFeatures = enableEdgeComputeFeatures;
LocalStorage.storeApplicationState(state.application);
};
- manager.updateAllowHostNamespaceForRegularUsers = function (allowHostNamespaceForRegularUsers) {
- state.application.allowHostNamespaceForRegularUsers = allowHostNamespaceForRegularUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
- manager.updateAllowDeviceMappingForRegularUsers = function updateAllowDeviceMappingForRegularUsers(allowDeviceMappingForRegularUsers) {
- state.application.allowDeviceMappingForRegularUsers = allowDeviceMappingForRegularUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
- manager.updateAllowStackManagementForRegularUsers = function updateAllowStackManagementForRegularUsers(allowStackManagementForRegularUsers) {
- state.application.allowStackManagementForRegularUsers = allowStackManagementForRegularUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
- manager.updateAllowContainerCapabilitiesForRegularUsers = function updateAllowContainerCapabilitiesForRegularUsers(allowContainerCapabilitiesForRegularUsers) {
- state.application.allowContainerCapabilitiesForRegularUsers = allowContainerCapabilitiesForRegularUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
- manager.updateAllowBindMountsForRegularUsers = function updateAllowBindMountsForRegularUsers(allowBindMountsForRegularUsers) {
- state.application.allowBindMountsForRegularUsers = allowBindMountsForRegularUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
- manager.updateAllowPrivilegedModeForRegularUsers = function (AllowPrivilegedModeForRegularUsers) {
- state.application.allowPrivilegedModeForRegularUsers = AllowPrivilegedModeForRegularUsers;
- LocalStorage.storeApplicationState(state.application);
- };
-
manager.updateEnableTelemetry = function updateEnableTelemetry(enableTelemetry) {
state.application.enableTelemetry = enableTelemetry;
$analytics.setOptOut(!enableTelemetry);
@@ -128,15 +88,7 @@ angular.module('portainer.app').factory('StateManager', [
state.application.enableTelemetry = settings.EnableTelemetry;
state.application.logo = settings.LogoURL;
state.application.snapshotInterval = settings.SnapshotInterval;
- state.application.enableHostManagementFeatures = settings.EnableHostManagementFeatures;
- state.application.enableVolumeBrowserForNonAdminUsers = settings.AllowVolumeBrowserForRegularUsers;
state.application.enableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
- state.application.allowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
- state.application.allowStackManagementForRegularUsers = settings.AllowStackManagementForRegularUsers;
- state.application.allowContainerCapabilitiesForRegularUsers = settings.AllowContainerCapabilitiesForRegularUsers;
- state.application.allowBindMountsForRegularUsers = settings.AllowBindMountsForRegularUsers;
- state.application.allowPrivilegedModeForRegularUsers = settings.AllowPrivilegedModeForRegularUsers;
- state.application.allowHostNamespaceForRegularUsers = settings.AllowHostNamespaceForRegularUsers;
state.application.validity = moment().unix();
}
diff --git a/app/portainer/views/settings/settings.html b/app/portainer/views/settings/settings.html
index 01008afb3..39644cb58 100644
--- a/app/portainer/views/settings/settings.html
+++ b/app/portainer/views/settings/settings.html
@@ -76,100 +76,7 @@
-
- Host and Filesystem
-
-
-
-
-
-
- Docker Endpoint Security Options
-
-
-
-
-
-
-
-
-
- Note: The recreate/duplicate/edit feature is currently disabled (for non-admin users) by one or more security settings.
-
-
-
Edge Compute
diff --git a/app/portainer/views/settings/settingsController.js b/app/portainer/views/settings/settingsController.js
index 5bd78d9f0..86b352ced 100644
--- a/app/portainer/views/settings/settingsController.js
+++ b/app/portainer/views/settings/settingsController.js
@@ -25,33 +25,12 @@ angular.module('portainer.app').controller('SettingsController', [
$scope.formValues = {
customLogo: false,
- restrictBindMounts: false,
- restrictPrivilegedMode: false,
labelName: '',
labelValue: '',
- enableHostManagementFeatures: false,
- enableVolumeBrowser: false,
enableEdgeComputeFeatures: false,
- restrictHostNamespaceForRegularUsers: false,
- allowDeviceMappingForRegularUsers: false,
- allowStackManagementForRegularUsers: false,
- disableContainerCapabilitiesForRegularUsers: false,
enableTelemetry: false,
};
- $scope.isContainerEditDisabled = function isContainerEditDisabled() {
- const {
- restrictBindMounts,
- restrictHostNamespaceForRegularUsers,
- restrictPrivilegedMode,
- disableDeviceMappingForRegularUsers,
- disableContainerCapabilitiesForRegularUsers,
- } = this.formValues;
- return (
- restrictBindMounts || restrictHostNamespaceForRegularUsers || restrictPrivilegedMode || disableDeviceMappingForRegularUsers || disableContainerCapabilitiesForRegularUsers
- );
- };
-
$scope.removeFilteredContainerLabel = function (index) {
var settings = $scope.settings;
settings.BlackListedLabels.splice(index, 1);
@@ -77,15 +56,7 @@ angular.module('portainer.app').controller('SettingsController', [
settings.LogoURL = '';
}
- settings.AllowBindMountsForRegularUsers = !$scope.formValues.restrictBindMounts;
- settings.AllowPrivilegedModeForRegularUsers = !$scope.formValues.restrictPrivilegedMode;
- settings.AllowVolumeBrowserForRegularUsers = $scope.formValues.enableVolumeBrowser;
- settings.EnableHostManagementFeatures = $scope.formValues.enableHostManagementFeatures;
settings.EnableEdgeComputeFeatures = $scope.formValues.enableEdgeComputeFeatures;
- settings.AllowHostNamespaceForRegularUsers = !$scope.formValues.restrictHostNamespaceForRegularUsers;
- settings.AllowDeviceMappingForRegularUsers = !$scope.formValues.disableDeviceMappingForRegularUsers;
- settings.AllowStackManagementForRegularUsers = !$scope.formValues.disableStackManagementForRegularUsers;
- settings.AllowContainerCapabilitiesForRegularUsers = !$scope.formValues.disableContainerCapabilitiesForRegularUsers;
settings.EnableTelemetry = $scope.formValues.enableTelemetry;
$scope.state.actionInProgress = true;
@@ -98,15 +69,7 @@ angular.module('portainer.app').controller('SettingsController', [
Notifications.success('Settings updated');
StateManager.updateLogo(settings.LogoURL);
StateManager.updateSnapshotInterval(settings.SnapshotInterval);
- StateManager.updateEnableHostManagementFeatures(settings.EnableHostManagementFeatures);
- StateManager.updateEnableVolumeBrowserForNonAdminUsers(settings.AllowVolumeBrowserForRegularUsers);
- StateManager.updateAllowHostNamespaceForRegularUsers(settings.AllowHostNamespaceForRegularUsers);
StateManager.updateEnableEdgeComputeFeatures(settings.EnableEdgeComputeFeatures);
- StateManager.updateAllowDeviceMappingForRegularUsers(settings.AllowDeviceMappingForRegularUsers);
- StateManager.updateAllowStackManagementForRegularUsers(settings.AllowStackManagementForRegularUsers);
- StateManager.updateAllowContainerCapabilitiesForRegularUsers(settings.AllowContainerCapabilitiesForRegularUsers);
- StateManager.updateAllowPrivilegedModeForRegularUsers(settings.AllowPrivilegedModeForRegularUsers);
- StateManager.updateAllowBindMountsForRegularUsers(settings.AllowBindMountsForRegularUsers);
StateManager.updateEnableTelemetry(settings.EnableTelemetry);
$state.reload();
})
@@ -127,15 +90,7 @@ angular.module('portainer.app').controller('SettingsController', [
if (settings.LogoURL !== '') {
$scope.formValues.customLogo = true;
}
- $scope.formValues.restrictBindMounts = !settings.AllowBindMountsForRegularUsers;
- $scope.formValues.restrictPrivilegedMode = !settings.AllowPrivilegedModeForRegularUsers;
- $scope.formValues.enableVolumeBrowser = settings.AllowVolumeBrowserForRegularUsers;
- $scope.formValues.enableHostManagementFeatures = settings.EnableHostManagementFeatures;
$scope.formValues.enableEdgeComputeFeatures = settings.EnableEdgeComputeFeatures;
- $scope.formValues.restrictHostNamespaceForRegularUsers = !settings.AllowHostNamespaceForRegularUsers;
- $scope.formValues.disableDeviceMappingForRegularUsers = !settings.AllowDeviceMappingForRegularUsers;
- $scope.formValues.disableStackManagementForRegularUsers = !settings.AllowStackManagementForRegularUsers;
- $scope.formValues.disableContainerCapabilitiesForRegularUsers = !settings.AllowContainerCapabilitiesForRegularUsers;
$scope.formValues.enableTelemetry = settings.EnableTelemetry;
})
.catch(function error(err) {
diff --git a/app/portainer/views/sidebar/sidebarController.js b/app/portainer/views/sidebar/sidebarController.js
index 24b357fec..6e1d8ecef 100644
--- a/app/portainer/views/sidebar/sidebarController.js
+++ b/app/portainer/views/sidebar/sidebarController.js
@@ -46,9 +46,17 @@ angular.module('portainer.app').controller('SidebarController', [
async function shouldShowStacks() {
const isAdmin = Authentication.isAdmin();
- const { allowStackManagementForRegularUsers } = $scope.applicationState.application;
- return isAdmin || allowStackManagementForRegularUsers;
+ if (isAdmin) {
+ return true;
+ }
+
+ const endpoint = EndpointProvider.currentEndpoint();
+ if (!endpoint || !endpoint.SecuritySettings) {
+ return false;
+ }
+
+ return endpoint.SecuritySettings.allowStackManagementForRegularUsers;
}
$transitions.onEnter({}, async () => {
diff --git a/app/portainer/views/stacks/stacksController.js b/app/portainer/views/stacks/stacksController.js
index c80bb3496..32506459c 100644
--- a/app/portainer/views/stacks/stacksController.js
+++ b/app/portainer/views/stacks/stacksController.js
@@ -1,7 +1,7 @@
angular.module('portainer.app').controller('StacksController', StacksController);
/* @ngInject */
-function StacksController($scope, $state, Notifications, StackService, ModalService, EndpointProvider, Authentication, StateManager) {
+function StacksController($scope, $state, Notifications, StackService, ModalService, EndpointProvider, Authentication, endpoint) {
$scope.removeAction = function (selectedItems) {
ModalService.confirmDeletion('Do you want to remove the selected stack(s)? Associated services will be removed as well.', function onConfirm(confirmed) {
if (!confirmed) {
@@ -55,8 +55,7 @@ function StacksController($scope, $state, Notifications, StackService, ModalServ
}
async function loadCreateEnabled() {
- const appState = StateManager.getState().application;
- return appState.allowStackManagementForRegularUsers || Authentication.isAdmin();
+ return endpoint.SecuritySettings.allowStackManagementForRegularUsers || Authentication.isAdmin();
}
async function initView() {
diff --git a/app/portainer/views/templates/templatesController.js b/app/portainer/views/templates/templatesController.js
index 04f9d224a..1b27ae9af 100644
--- a/app/portainer/views/templates/templatesController.js
+++ b/app/portainer/views/templates/templatesController.js
@@ -16,8 +16,8 @@ angular.module('portainer.app').controller('TemplatesController', [
'ResourceControlService',
'Authentication',
'FormValidator',
- 'SettingsService',
'StackService',
+ 'endpoint',
function (
$scope,
$q,
@@ -33,8 +33,8 @@ angular.module('portainer.app').controller('TemplatesController', [
ResourceControlService,
Authentication,
FormValidator,
- SettingsService,
- StackService
+ StackService,
+ endpoint
) {
$scope.state = {
selectedTemplate: null,
@@ -263,7 +263,6 @@ angular.module('portainer.app').controller('TemplatesController', [
false,
endpointMode.provider === 'DOCKER_SWARM_MODE' && apiVersion >= 1.25
),
- settings: SettingsService.publicSettings(),
})
.then(function success(data) {
var templates = data.templates;
@@ -271,8 +270,7 @@ angular.module('portainer.app').controller('TemplatesController', [
$scope.availableVolumes = _.orderBy(data.volumes.Volumes, [(volume) => volume.Name.toLowerCase()], ['asc']);
var networks = data.networks;
$scope.availableNetworks = networks;
- var settings = data.settings;
- $scope.allowBindMounts = settings.AllowBindMountsForRegularUsers;
+ $scope.allowBindMounts = endpoint.SecuritySettings.allowBindMountsForRegularUsers;
})
.catch(function error(err) {
$scope.templates = [];