feat(settings): add setting to disable device mapping for regular users (#4017)
* feat(settings): introduce device mapping service * feat(containers): hide devices field when setting is on * feat(containers): prevent passing of devices when not allowed * feat(stacks): prevent non admin from device mapping * feat(stacks): disallow swarm stack creation for user * refactor(settings): replace disableDeviceMapping with allow * fix(stacks): remove check for disable device mappings from swarm * feat(settings): rename field to disable * feat(settings): supply default value for disableDeviceMapping * feat(container): check for endpoint admin
This commit is contained in:
@@ -21,6 +21,7 @@ type publicSettingsResponse struct {
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
DisableStackManagementForRegularUsers bool `json:"DisableStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
}
|
||||
|
||||
// GET request on /api/settings/public
|
||||
@@ -45,6 +46,7 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
|
||||
settings.OAuthSettings.ClientID,
|
||||
settings.OAuthSettings.RedirectURI,
|
||||
settings.OAuthSettings.Scopes),
|
||||
AllowDeviceMappingForRegularUsers: settings.AllowDeviceMappingForRegularUsers,
|
||||
DisableStackManagementForRegularUsers: settings.DisableStackManagementForRegularUsers,
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ type settingsUpdatePayload struct {
|
||||
EnableEdgeComputeFeatures *bool
|
||||
DisableStackManagementForRegularUsers *bool
|
||||
AllowHostNamespaceForRegularUsers *bool
|
||||
AllowDeviceMappingForRegularUsers *bool
|
||||
}
|
||||
|
||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||
@@ -135,6 +136,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
|
||||
}
|
||||
|
||||
if payload.AllowDeviceMappingForRegularUsers != nil {
|
||||
settings.AllowDeviceMappingForRegularUsers = *payload.AllowDeviceMappingForRegularUsers
|
||||
}
|
||||
|
||||
tlsError := handler.updateTLS(settings)
|
||||
if tlsError != nil {
|
||||
return tlsError
|
||||
|
||||
@@ -335,7 +335,11 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
if (!settings.AllowBindMountsForRegularUsers || !settings.AllowPrivilegedModeForRegularUsers || !settings.AllowHostNamespaceForRegularUsers) && !isAdminOrEndpointAdmin {
|
||||
if (!settings.AllowBindMountsForRegularUsers ||
|
||||
!settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers) && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
|
||||
@@ -341,6 +341,7 @@ func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) err
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
|
||||
@@ -160,6 +160,10 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port
|
||||
if !settings.AllowHostNamespaceForRegularUsers && service.Pid == "host" {
|
||||
return errors.New("pid host disabled for non administrator users")
|
||||
}
|
||||
|
||||
if !settings.AllowDeviceMappingForRegularUsers && service.Devices != nil && len(service.Devices) > 0 {
|
||||
return errors.New("device mapping disabled for non administrator users")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -156,8 +156,9 @@ func containerHasBlackListedLabel(containerLabels map[string]interface{}, labelB
|
||||
func (transport *Transport) decorateContainerCreationOperation(request *http.Request, resourceIdentifierAttribute string, resourceType portainer.ResourceControlType) (*http.Response, error) {
|
||||
type PartialContainer struct {
|
||||
HostConfig struct {
|
||||
Privileged bool `json:"Privileged"`
|
||||
PidMode string `json:"PidMode"`
|
||||
Privileged bool `json:"Privileged"`
|
||||
PidMode string `json:"PidMode"`
|
||||
Devices []interface{} `json:"Devices"`
|
||||
} `json:"HostConfig"`
|
||||
}
|
||||
|
||||
@@ -186,14 +187,18 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
endpointResourceAccess = true
|
||||
}
|
||||
|
||||
if (rbacExtension != nil && !endpointResourceAccess && tokenData.Role != portainer.AdministratorRole) || (rbacExtension == nil && tokenData.Role != portainer.AdministratorRole) {
|
||||
isAdmin := (rbacExtension != nil && endpointResourceAccess) || tokenData.Role == portainer.AdministratorRole
|
||||
|
||||
if !isAdmin {
|
||||
settings, err := transport.settingsService.Settings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !settings.AllowPrivilegedModeForRegularUsers || !settings.AllowHostNamespaceForRegularUsers {
|
||||
if !settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers {
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -213,6 +218,10 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
return forbiddenResponse, errors.New("forbidden to use pid host namespace")
|
||||
}
|
||||
|
||||
if len(partialContainer.HostConfig.Devices) > 0 {
|
||||
return nil, errors.New("forbidden to use device mapping")
|
||||
}
|
||||
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user