1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

feat(settings): add new settings to disable volume browser (#3239)

* feat(settings): add new settings to disable volume browser

* feat(api): update setting to be compliant with RBAC

* refactor(api): update method comment

* fix(api): remove volume browsing authorizations by default

* feat(settings): rewrite volume management setting description

* feat(settings): rewrite volume management setting tooltip

* Update app/portainer/views/settings/settings.html

Co-Authored-By: William <william.conquest@portainer.io>
This commit is contained in:
Anthony Lapenna 2019-10-08 13:17:58 +13:00 committed by GitHub
parent ef4c138e03
commit 9aa52a6975
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 239 additions and 48 deletions

View file

@ -56,6 +56,52 @@ func DefaultPortainerAuthorizations() Authorizations {
}
}
// UpdateVolumeBrowsingAuthorizations will update all the volume browsing authorizations for each role (except endpoint administrator)
// based on the specified removeAuthorizations parameter. If removeAuthorizations is set to true, all
// the authorizations will be dropped for the each role. If removeAuthorizations is set to false, the authorizations
// will be reset based for each role.
func (service AuthorizationService) UpdateVolumeBrowsingAuthorizations(remove bool) error {
roles, err := service.roleService.Roles()
if err != nil {
return err
}
for _, role := range roles {
// all roles except endpoint administrator
if role.ID != RoleID(1) {
updateRoleVolumeBrowsingAuthorizations(&role, remove)
err := service.roleService.UpdateRole(role.ID, &role)
if err != nil {
return err
}
}
}
return nil
}
func updateRoleVolumeBrowsingAuthorizations(role *Role, removeAuthorizations bool) {
if !removeAuthorizations {
delete(role.Authorizations, OperationDockerAgentBrowseDelete)
delete(role.Authorizations, OperationDockerAgentBrowseGet)
delete(role.Authorizations, OperationDockerAgentBrowseList)
delete(role.Authorizations, OperationDockerAgentBrowsePut)
delete(role.Authorizations, OperationDockerAgentBrowseRename)
return
}
role.Authorizations[OperationDockerAgentBrowseGet] = true
role.Authorizations[OperationDockerAgentBrowseList] = true
// Standard-user
if role.ID == RoleID(3) {
role.Authorizations[OperationDockerAgentBrowseDelete] = true
role.Authorizations[OperationDockerAgentBrowsePut] = true
role.Authorizations[OperationDockerAgentBrowseRename] = true
}
}
// RemoveTeamAccessPolicies will remove all existing access policies associated to the specified team
func (service *AuthorizationService) RemoveTeamAccessPolicies(teamID TeamID) error {
endpoints, err := service.endpointService.Endpoints()

View file

@ -218,8 +218,6 @@ func (store *Store) Init() error {
portainer.OperationDockerAgentPing: true,
portainer.OperationDockerAgentList: true,
portainer.OperationDockerAgentHostInfo: true,
portainer.OperationDockerAgentBrowseGet: true,
portainer.OperationDockerAgentBrowseList: true,
portainer.OperationPortainerStackList: true,
portainer.OperationPortainerStackInspect: true,
portainer.OperationPortainerStackFile: true,
@ -342,11 +340,6 @@ func (store *Store) Init() error {
portainer.OperationDockerAgentPing: true,
portainer.OperationDockerAgentList: true,
portainer.OperationDockerAgentHostInfo: true,
portainer.OperationDockerAgentBrowseDelete: true,
portainer.OperationDockerAgentBrowseGet: true,
portainer.OperationDockerAgentBrowseList: true,
portainer.OperationDockerAgentBrowsePut: true,
portainer.OperationDockerAgentBrowseRename: true,
portainer.OperationDockerAgentUndefined: true,
portainer.OperationPortainerResourceControlCreate: true,
portainer.OperationPortainerResourceControlUpdate: true,
@ -413,8 +406,6 @@ func (store *Store) Init() error {
portainer.OperationDockerAgentPing: true,
portainer.OperationDockerAgentList: true,
portainer.OperationDockerAgentHostInfo: true,
portainer.OperationDockerAgentBrowseGet: true,
portainer.OperationDockerAgentBrowseList: true,
portainer.OperationPortainerStackList: true,
portainer.OperationPortainerStackInspect: true,
portainer.OperationPortainerStackFile: true,

View file

@ -15,3 +15,14 @@ func (m *Migrator) updateUsersToDBVersion20() error {
authorizationService := portainer.NewAuthorizationService(authorizationServiceParameters)
return authorizationService.UpdateUsersAuthorizations()
}
func (m *Migrator) updateSettingsToDBVersion20() error {
legacySettings, err := m.settingsService.Settings()
if err != nil {
return err
}
legacySettings.AllowVolumeBrowserForRegularUsers = false
return m.settingsService.UpdateSettings(legacySettings)
}

View file

@ -271,6 +271,11 @@ func (m *Migrator) Migrate() error {
if err != nil {
return err
}
err = m.updateSettingsToDBVersion20()
if err != nil {
return err
}
}
return m.versionService.StoreDBVersion(portainer.DBVersion)

View file

@ -66,18 +66,24 @@ func (service *Service) Roles() ([]portainer.Role, error) {
}
// CreateRole creates a new Role.
func (service *Service) CreateRole(set *portainer.Role) error {
func (service *Service) CreateRole(role *portainer.Role) error {
return service.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
id, _ := bucket.NextSequence()
set.ID = portainer.RoleID(id)
role.ID = portainer.RoleID(id)
data, err := internal.MarshalObject(set)
data, err := internal.MarshalObject(role)
if err != nil {
return err
}
return bucket.Put(internal.Itob(int(set.ID)), data)
return bucket.Put(internal.Itob(int(role.ID)), data)
})
}
// UpdateRole updates a role.
func (service *Service) UpdateRole(ID portainer.RoleID, role *portainer.Role) error {
identifier := internal.Itob(int(ID))
return internal.UpdateObject(service.db, BucketName, identifier, role)
}

View file

@ -259,18 +259,15 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
LogoURL: *flags.Logo,
AuthenticationMethod: portainer.AuthenticationInternal,
LDAPSettings: portainer.LDAPSettings{
AutoCreateUsers: true,
TLSConfig: portainer.TLSConfiguration{},
SearchSettings: []portainer.LDAPSearchSettings{
portainer.LDAPSearchSettings{},
},
GroupSearchSettings: []portainer.LDAPGroupSearchSettings{
portainer.LDAPGroupSearchSettings{},
},
AutoCreateUsers: true,
TLSConfig: portainer.TLSConfiguration{},
SearchSettings: []portainer.LDAPSearchSettings{},
GroupSearchSettings: []portainer.LDAPGroupSearchSettings{},
},
OAuthSettings: portainer.OAuthSettings{},
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
SnapshotInterval: *flags.SnapshotInterval,
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,

View file

@ -35,11 +35,6 @@ func (handler *Handler) upgradeRBACData() error {
if err != nil {
return err
}
//err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpointGroup.UserAccessPolicies, &endpointGroup.TeamAccessPolicies)
//if err != nil {
// return err
//}
}
endpoints, err := handler.EndpointService.Endpoints()
@ -60,14 +55,7 @@ func (handler *Handler) upgradeRBACData() error {
if err != nil {
return err
}
//err = handler.AuthorizationService.UpdateUserAuthorizationsFromPolicies(&endpoint.UserAccessPolicies, &endpoint.TeamAccessPolicies)
//if err != nil {
// return err
//}
}
return handler.AuthorizationService.UpdateUsersAuthorizations()
//return nil
}

View file

@ -17,11 +17,14 @@ func hideFields(settings *portainer.Settings) {
// Handler is the HTTP handler used to handle settings operations.
type Handler struct {
*mux.Router
SettingsService portainer.SettingsService
LDAPService portainer.LDAPService
FileService portainer.FileService
JobScheduler portainer.JobScheduler
ScheduleService portainer.ScheduleService
SettingsService portainer.SettingsService
LDAPService portainer.LDAPService
FileService portainer.FileService
JobScheduler portainer.JobScheduler
ScheduleService portainer.ScheduleService
RoleService portainer.RoleService
ExtensionService portainer.ExtensionService
AuthorizationService *portainer.AuthorizationService
}
// NewHandler creates a handler to manage settings operations.

View file

@ -14,6 +14,7 @@ type publicSettingsResponse struct {
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
ExternalTemplates bool `json:"ExternalTemplates"`
OAuthLoginURI string `json:"OAuthLoginURI"`
@ -31,6 +32,7 @@ func (handler *Handler) settingsPublic(w http.ResponseWriter, r *http.Request) *
AuthenticationMethod: settings.AuthenticationMethod,
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
ExternalTemplates: false,
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",

View file

@ -19,6 +19,7 @@ type settingsUpdatePayload struct {
OAuthSettings *portainer.OAuthSettings
AllowBindMountsForRegularUsers *bool
AllowPrivilegedModeForRegularUsers *bool
AllowVolumeBrowserForRegularUsers *bool
EnableHostManagementFeatures *bool
SnapshotInterval *string
TemplatesURL *string
@ -93,6 +94,12 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
settings.AllowPrivilegedModeForRegularUsers = *payload.AllowPrivilegedModeForRegularUsers
}
updateAuthorizations := false
if payload.AllowVolumeBrowserForRegularUsers != nil {
settings.AllowVolumeBrowserForRegularUsers = *payload.AllowVolumeBrowserForRegularUsers
updateAuthorizations = true
}
if payload.EnableHostManagementFeatures != nil {
settings.EnableHostManagementFeatures = *payload.EnableHostManagementFeatures
}
@ -118,9 +125,37 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist settings changes inside the database", err}
}
if updateAuthorizations {
err := handler.updateVolumeBrowserSetting(settings)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update RBAC authorizations", err}
}
}
return response.JSON(w, settings)
}
func (handler *Handler) updateVolumeBrowserSetting(settings *portainer.Settings) error {
err := handler.AuthorizationService.UpdateVolumeBrowsingAuthorizations(settings.AllowVolumeBrowserForRegularUsers)
if err != nil {
return err
}
extension, err := handler.ExtensionService.Extension(portainer.RBACExtension)
if err != nil && err != portainer.ErrObjectNotFound {
return err
}
if extension != nil {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return err
}
}
return nil
}
func (handler *Handler) updateSnapshotInterval(settings *portainer.Settings, snapshotInterval string) error {
settings.SnapshotInterval = snapshotInterval

View file

@ -26,6 +26,7 @@ type (
SettingsService portainer.SettingsService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
ExtensionService portainer.ExtensionService
endpointIdentifier portainer.EndpointID
endpointType portainer.EndpointType
}
@ -129,7 +130,8 @@ func (p *proxyTransport) proxyAgentRequest(r *http.Request) (*http.Response, err
if !found || len(volumeIDParameter) < 1 {
return p.administratorOperation(r)
}
return p.restrictedOperation(r, volumeIDParameter[0])
return p.restrictedVolumeBrowserOperation(r, volumeIDParameter[0])
}
return p.executeDockerRequest(r)
@ -386,6 +388,62 @@ func (p *proxyTransport) restrictedOperation(request *http.Request, resourceID s
return p.executeDockerRequest(request)
}
// restrictedVolumeBrowserOperation is similar to restrictedOperation but adds an extra check on a specific setting
func (p *proxyTransport) restrictedVolumeBrowserOperation(request *http.Request, resourceID string) (*http.Response, error) {
var err error
tokenData, err := security.RetrieveTokenData(request)
if err != nil {
return nil, err
}
if tokenData.Role != portainer.AdministratorRole {
settings, err := p.SettingsService.Settings()
if err != nil {
return nil, err
}
_, err = p.ExtensionService.Extension(portainer.RBACExtension)
if err == portainer.ErrObjectNotFound && !settings.AllowVolumeBrowserForRegularUsers {
return writeAccessDeniedResponse()
} else if err != nil && err != portainer.ErrObjectNotFound {
return nil, err
}
user, err := p.UserService.User(tokenData.ID)
if err != nil {
return nil, err
}
endpointResourceAccess := false
_, ok := user.EndpointAuthorizations[p.endpointIdentifier][portainer.EndpointResourcesAccess]
if ok {
endpointResourceAccess = true
}
teamMemberships, err := p.TeamMembershipService.TeamMembershipsByUserID(tokenData.ID)
if err != nil {
return nil, err
}
userTeamIDs := make([]portainer.TeamID, 0)
for _, membership := range teamMemberships {
userTeamIDs = append(userTeamIDs, membership.TeamID)
}
resourceControls, err := p.ResourceControlService.ResourceControls()
if err != nil {
return nil, err
}
resourceControl := getResourceControlByResourceID(resourceID, resourceControls)
if !endpointResourceAccess && (resourceControl == nil || !canUserAccessResource(tokenData.ID, userTeamIDs, resourceControl)) {
return writeAccessDeniedResponse()
}
}
return p.executeDockerRequest(request)
}
// rewriteOperationWithLabelFiltering will create a new operation context with data that will be used
// to decorate the original request's response as well as retrieve all the black listed labels
// to filter the resources.

View file

@ -23,6 +23,7 @@ type proxyFactory struct {
DockerHubService portainer.DockerHubService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
ExtensionService portainer.ExtensionService
}
func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
@ -77,6 +78,7 @@ func (factory *proxyFactory) createDockerReverseProxy(u *url.URL, endpoint *port
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
ReverseTunnelService: factory.ReverseTunnelService,
ExtensionService: factory.ExtensionService,
dockerTransport: &http.Transport{},
endpointIdentifier: endpoint.ID,
endpointType: endpoint.Type,

View file

@ -18,6 +18,7 @@ func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endp
SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
ExtensionService: factory.ExtensionService,
dockerTransport: newSocketTransport(path),
ReverseTunnelService: factory.ReverseTunnelService,
endpointIdentifier: endpoint.ID,

View file

@ -22,6 +22,7 @@ func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endp
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
ReverseTunnelService: factory.ReverseTunnelService,
ExtensionService: factory.ExtensionService,
dockerTransport: newNamedPipeTransport(path),
endpointIdentifier: endpoint.ID,
endpointType: endpoint.Type,

View file

@ -38,6 +38,7 @@ type (
DockerHubService portainer.DockerHubService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
ExtensionService portainer.ExtensionService
}
)
@ -56,6 +57,7 @@ func NewManager(parameters *ManagerParams) *Manager {
DockerHubService: parameters.DockerHubService,
SignatureService: parameters.SignatureService,
ReverseTunnelService: parameters.ReverseTunnelService,
ExtensionService: parameters.ExtensionService,
},
reverseTunnelService: parameters.ReverseTunnelService,
}

View file

@ -91,6 +91,7 @@ func (server *Server) Start() error {
DockerHubService: server.DockerHubService,
SignatureService: server.SignatureService,
ReverseTunnelService: server.ReverseTunnelService,
ExtensionService: server.ExtensionService,
}
proxyManager := proxy.NewManager(proxyManagerParameters)
@ -196,6 +197,9 @@ func (server *Server) Start() error {
settingsHandler.FileService = server.FileService
settingsHandler.JobScheduler = server.JobScheduler
settingsHandler.ScheduleService = server.ScheduleService
settingsHandler.RoleService = server.RoleService
settingsHandler.ExtensionService = server.ExtensionService
settingsHandler.AuthorizationService = authorizationService
var stackHandler = stacks.NewHandler(requestBouncer)
stackHandler.FileService = server.FileService

View file

@ -106,6 +106,7 @@ type (
OAuthSettings OAuthSettings `json:"OAuthSettings"`
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
SnapshotInterval string `json:"SnapshotInterval"`
TemplatesURL string `json:"TemplatesURL"`
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
@ -637,7 +638,8 @@ type (
RoleService interface {
Role(ID RoleID) (*Role, error)
Roles() ([]Role, error)
CreateRole(set *Role) error
CreateRole(role *Role) error
UpdateRole(ID RoleID, role *Role) error
}
// TeamService represents a service for managing user data
@ -903,7 +905,7 @@ const (
// APIVersion is the version number of the Portainer API
APIVersion = "1.22.0"
// DBVersion is the version number of the Portainer database
DBVersion = 20
DBVersion = 21
// AssetsServerURL represents the URL of the Portainer asset server
AssetsServerURL = "https://portainer-io-assets.sfo2.digitaloceanspaces.com"
// MessageOfTheDayURL represents the URL where Portainer MOTD message can be retrieved