Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a813388920 | ||
|
|
39e9dca7b8 | ||
|
|
cfdd38c55e | ||
|
|
a12a0b61dc | ||
|
|
d2cdbf789e | ||
|
|
06db4e0ad4 | ||
|
|
9f92e0aee3 |
@@ -1,7 +1,9 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
@@ -11,7 +13,7 @@ import (
|
||||
"github.com/portainer/portainer/api/bolt/tunnelserver"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/dockerhub"
|
||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||
@@ -34,6 +36,7 @@ import (
|
||||
|
||||
const (
|
||||
databaseFileName = "portainer.db"
|
||||
dbBackupFileName = "portainer-1-24-backup.db"
|
||||
)
|
||||
|
||||
// Store defines the implementation of portainer.DataStore using
|
||||
@@ -284,3 +287,38 @@ func (store *Store) initServices() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *Store) Backup1_24db() error {
|
||||
version, err := store.VersionService.DBVersion()
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if version != 24 {
|
||||
return nil
|
||||
}
|
||||
|
||||
databasePath := path.Join(store.path, databaseFileName)
|
||||
dbBackupPath := path.Join(store.path, dbBackupFileName)
|
||||
|
||||
source, err := os.Open(databasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer source.Close()
|
||||
|
||||
destination, err := os.Create(dbBackupPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer destination.Close()
|
||||
|
||||
_, err = io.Copy(destination, source)
|
||||
if err == nil {
|
||||
log.Println("backup for 1.24 finished successfully.")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
12
api/bolt/migrator/migrate_dbversion24.go
Normal file
12
api/bolt/migrator/migrate_dbversion24.go
Normal file
@@ -0,0 +1,12 @@
|
||||
package migrator
|
||||
|
||||
func (m *Migrator) updateSettingsToDBVersion25() error {
|
||||
legacySettings, err := m.settingsService.Settings()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
legacySettings.AllowContainerCapabilitiesForRegularUsers = true
|
||||
|
||||
return m.settingsService.UpdateSettings(legacySettings)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package migrator
|
||||
|
||||
import (
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/portainer/portainer/api"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/bolt/endpoint"
|
||||
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
||||
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
||||
@@ -330,5 +330,13 @@ func (m *Migrator) Migrate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Portainer 1.24.2
|
||||
if m.currentDBVersion < 25 {
|
||||
err := m.updateSettingsToDBVersion25()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return m.versionService.StoreDBVersion(portainer.DBVersion)
|
||||
}
|
||||
|
||||
@@ -63,10 +63,16 @@ func initStore(dataStorePath string, fileService portainer.FileService) *bolt.St
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = store.Backup1_24db()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = store.MigrateData()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
@@ -269,16 +275,17 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
|
||||
portainer.LDAPGroupSearchSettings{},
|
||||
},
|
||||
},
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
AllowBindMountsForRegularUsers: true,
|
||||
AllowPrivilegedModeForRegularUsers: true,
|
||||
AllowVolumeBrowserForRegularUsers: false,
|
||||
AllowDeviceMappingForRegularUsers: true,
|
||||
AllowStackManagementForRegularUsers: true,
|
||||
EnableHostManagementFeatures: false,
|
||||
AllowHostNamespaceForRegularUsers: true,
|
||||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
OAuthSettings: portainer.OAuthSettings{},
|
||||
AllowBindMountsForRegularUsers: true,
|
||||
AllowPrivilegedModeForRegularUsers: true,
|
||||
AllowVolumeBrowserForRegularUsers: false,
|
||||
AllowDeviceMappingForRegularUsers: true,
|
||||
AllowStackManagementForRegularUsers: true,
|
||||
AllowContainerCapabilitiesForRegularUsers: true,
|
||||
EnableHostManagementFeatures: false,
|
||||
AllowHostNamespaceForRegularUsers: true,
|
||||
SnapshotInterval: *flags.SnapshotInterval,
|
||||
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
|
||||
}
|
||||
|
||||
if *flags.Templates != "" {
|
||||
|
||||
@@ -12,7 +12,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"
|
||||
)
|
||||
@@ -261,13 +261,13 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: payload.TagIDs,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
EdgeKey: edgeKey,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
TagIDs: payload.TagIDs,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
EdgeKey: edgeKey,
|
||||
}
|
||||
|
||||
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
|
||||
|
||||
@@ -10,18 +10,19 @@ 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"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
LogoURL string `json:"LogoURL"`
|
||||
AuthenticationMethod portainer.AuthenticationMethod `json:"AuthenticationMethod"`
|
||||
AllowBindMountsForRegularUsers bool `json:"AllowBindMountsForRegularUsers"`
|
||||
AllowPrivilegedModeForRegularUsers bool `json:"AllowPrivilegedModeForRegularUsers"`
|
||||
AllowVolumeBrowserForRegularUsers bool `json:"AllowVolumeBrowserForRegularUsers"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
ExternalTemplates bool `json:"ExternalTemplates"`
|
||||
OAuthLoginURI string `json:"OAuthLoginURI"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
|
||||
}
|
||||
|
||||
// GET request on /api/settings/public
|
||||
@@ -32,16 +33,17 @@ 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,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||
ExternalTemplates: false,
|
||||
LogoURL: settings.LogoURL,
|
||||
AuthenticationMethod: settings.AuthenticationMethod,
|
||||
AllowBindMountsForRegularUsers: settings.AllowBindMountsForRegularUsers,
|
||||
AllowPrivilegedModeForRegularUsers: settings.AllowPrivilegedModeForRegularUsers,
|
||||
AllowVolumeBrowserForRegularUsers: settings.AllowVolumeBrowserForRegularUsers,
|
||||
EnableHostManagementFeatures: settings.EnableHostManagementFeatures,
|
||||
EnableEdgeComputeFeatures: settings.EnableEdgeComputeFeatures,
|
||||
AllowHostNamespaceForRegularUsers: settings.AllowHostNamespaceForRegularUsers,
|
||||
AllowStackManagementForRegularUsers: settings.AllowStackManagementForRegularUsers,
|
||||
ExternalTemplates: false,
|
||||
AllowContainerCapabilitiesForRegularUsers: settings.AllowContainerCapabilitiesForRegularUsers,
|
||||
OAuthLoginURI: fmt.Sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&prompt=login",
|
||||
settings.OAuthSettings.AuthorizationURI,
|
||||
settings.OAuthSettings.ClientID,
|
||||
|
||||
@@ -12,22 +12,23 @@ import (
|
||||
)
|
||||
|
||||
type settingsUpdatePayload struct {
|
||||
LogoURL *string
|
||||
BlackListedLabels []portainer.Pair
|
||||
AuthenticationMethod *int
|
||||
LDAPSettings *portainer.LDAPSettings
|
||||
OAuthSettings *portainer.OAuthSettings
|
||||
AllowBindMountsForRegularUsers *bool
|
||||
AllowPrivilegedModeForRegularUsers *bool
|
||||
AllowVolumeBrowserForRegularUsers *bool
|
||||
EnableHostManagementFeatures *bool
|
||||
SnapshotInterval *string
|
||||
TemplatesURL *string
|
||||
EdgeAgentCheckinInterval *int
|
||||
EnableEdgeComputeFeatures *bool
|
||||
AllowStackManagementForRegularUsers *bool
|
||||
AllowHostNamespaceForRegularUsers *bool
|
||||
AllowDeviceMappingForRegularUsers *bool
|
||||
LogoURL *string
|
||||
BlackListedLabels []portainer.Pair
|
||||
AuthenticationMethod *int
|
||||
LDAPSettings *portainer.LDAPSettings
|
||||
OAuthSettings *portainer.OAuthSettings
|
||||
AllowBindMountsForRegularUsers *bool
|
||||
AllowPrivilegedModeForRegularUsers *bool
|
||||
AllowVolumeBrowserForRegularUsers *bool
|
||||
EnableHostManagementFeatures *bool
|
||||
SnapshotInterval *string
|
||||
TemplatesURL *string
|
||||
EdgeAgentCheckinInterval *int
|
||||
EnableEdgeComputeFeatures *bool
|
||||
AllowStackManagementForRegularUsers *bool
|
||||
AllowHostNamespaceForRegularUsers *bool
|
||||
AllowDeviceMappingForRegularUsers *bool
|
||||
AllowContainerCapabilitiesForRegularUsers *bool
|
||||
}
|
||||
|
||||
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
|
||||
@@ -125,6 +126,10 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
|
||||
settings.AllowHostNamespaceForRegularUsers = *payload.AllowHostNamespaceForRegularUsers
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -302,9 +302,12 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai
|
||||
}
|
||||
filteredRegistries := security.FilterRegistries(registries, securityContext)
|
||||
|
||||
user, err := handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
var user *portainer.User
|
||||
if !handler.authDisabled {
|
||||
user, err = handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
}
|
||||
}
|
||||
|
||||
config := &composeStackDeploymentConfig{
|
||||
@@ -330,26 +333,29 @@ func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig)
|
||||
return err
|
||||
}
|
||||
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (!settings.AllowBindMountsForRegularUsers ||
|
||||
!settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers) && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if !handler.authDisabled {
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
if (!settings.AllowBindMountsForRegularUsers ||
|
||||
!settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers ||
|
||||
!settings.AllowContainerCapabilitiesForRegularUsers) && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -311,9 +311,12 @@ func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portaine
|
||||
}
|
||||
filteredRegistries := security.FilterRegistries(registries, securityContext)
|
||||
|
||||
user, err := handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
var user *portainer.User
|
||||
if !handler.authDisabled {
|
||||
user, err = handler.UserService.User(securityContext.UserID)
|
||||
if err != nil {
|
||||
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to load user information from the database", err}
|
||||
}
|
||||
}
|
||||
|
||||
config := &swarmStackDeploymentConfig{
|
||||
@@ -335,23 +338,25 @@ func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) err
|
||||
return err
|
||||
}
|
||||
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if !handler.authDisabled {
|
||||
isAdminOrEndpointAdmin, err := handler.userIsAdminOrEndpointAdmin(config.user, config.endpoint.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
if !settings.AllowBindMountsForRegularUsers && !isAdminOrEndpointAdmin {
|
||||
|
||||
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
|
||||
|
||||
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = handler.isValidStackFile(stackContent, settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ type Handler struct {
|
||||
stackCreationMutex *sync.Mutex
|
||||
stackDeletionMutex *sync.Mutex
|
||||
requestBouncer *security.RequestBouncer
|
||||
authDisabled bool
|
||||
|
||||
*mux.Router
|
||||
FileService portainer.FileService
|
||||
GitService portainer.GitService
|
||||
@@ -32,9 +34,10 @@ type Handler struct {
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage stack operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
func NewHandler(bouncer *security.RequestBouncer, authDisabled bool) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
authDisabled: authDisabled,
|
||||
stackCreationMutex: &sync.Mutex{},
|
||||
stackDeletionMutex: &sync.Mutex{},
|
||||
requestBouncer: bouncer,
|
||||
@@ -57,7 +60,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
}
|
||||
|
||||
func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID, resourceControl *portainer.ResourceControl) (bool, error) {
|
||||
if securityContext.IsAdmin {
|
||||
if securityContext.IsAdmin || handler.authDisabled {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -90,7 +93,7 @@ func (handler *Handler) userCanAccessStack(securityContext *security.RestrictedR
|
||||
}
|
||||
|
||||
func (handler *Handler) userCanCreateStack(securityContext *security.RestrictedRequestContext, endpointID portainer.EndpointID) (bool, error) {
|
||||
if securityContext.IsAdmin {
|
||||
if securityContext.IsAdmin || handler.authDisabled {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -164,6 +164,10 @@ func (handler *Handler) isValidStackFile(stackFileContent []byte, settings *port
|
||||
if !settings.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) {
|
||||
return errors.New("container capabilities disabled for non administrator users")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -68,6 +68,7 @@ func (factory *ProxyFactory) newDockerHTTPProxy(endpoint *portainer.Endpoint) (h
|
||||
ExtensionService: factory.extensionService,
|
||||
SignatureService: factory.signatureService,
|
||||
DockerClientFactory: factory.dockerClientFactory,
|
||||
AuthDisabled: factory.authDisabled,
|
||||
}
|
||||
|
||||
dockerTransport, err := docker.NewTransport(transportParameters, httpTransport)
|
||||
|
||||
@@ -159,6 +159,9 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
Privileged bool `json:"Privileged"`
|
||||
PidMode string `json:"PidMode"`
|
||||
Devices []interface{} `json:"Devices"`
|
||||
CapAdd []string `json:"CapAdd"`
|
||||
CapDrop []string `json:"CapDrop"`
|
||||
Binds []string `json:"Binds"`
|
||||
} `json:"HostConfig"`
|
||||
}
|
||||
|
||||
@@ -171,25 +174,12 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := transport.userService.User(tokenData.ID)
|
||||
isAdminOrEndpointAdmin, err := transport.isAdminOrEndpointAdmin(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rbacExtension, err := transport.extensionService.Extension(portainer.RBACExtension)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpointResourceAccess := false
|
||||
_, ok := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess]
|
||||
if ok {
|
||||
endpointResourceAccess = true
|
||||
}
|
||||
|
||||
isAdmin := (rbacExtension != nil && endpointResourceAccess) || tokenData.Role == portainer.AdministratorRole
|
||||
|
||||
if !isAdmin {
|
||||
if !isAdminOrEndpointAdmin {
|
||||
settings, err := transport.settingsService.Settings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -197,7 +187,9 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
|
||||
if !settings.AllowPrivilegedModeForRegularUsers ||
|
||||
!settings.AllowHostNamespaceForRegularUsers ||
|
||||
!settings.AllowDeviceMappingForRegularUsers {
|
||||
!settings.AllowDeviceMappingForRegularUsers ||
|
||||
!settings.AllowContainerCapabilitiesForRegularUsers ||
|
||||
!settings.AllowBindMountsForRegularUsers {
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
@@ -210,18 +202,26 @@ func (transport *Transport) decorateContainerCreationOperation(request *http.Req
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if partialContainer.HostConfig.Privileged {
|
||||
if !settings.AllowPrivilegedModeForRegularUsers && partialContainer.HostConfig.Privileged {
|
||||
return forbiddenResponse, errors.New("forbidden to use privileged mode")
|
||||
}
|
||||
|
||||
if partialContainer.HostConfig.PidMode == "host" {
|
||||
if !settings.AllowHostNamespaceForRegularUsers && partialContainer.HostConfig.PidMode == "host" {
|
||||
return forbiddenResponse, errors.New("forbidden to use pid host namespace")
|
||||
}
|
||||
|
||||
if len(partialContainer.HostConfig.Devices) > 0 {
|
||||
if !settings.AllowDeviceMappingForRegularUsers && len(partialContainer.HostConfig.Devices) > 0 {
|
||||
return nil, errors.New("forbidden to use device mapping")
|
||||
}
|
||||
|
||||
if !settings.AllowContainerCapabilitiesForRegularUsers && (len(partialContainer.HostConfig.CapAdd) > 0 || len(partialContainer.HostConfig.CapDrop) > 0) {
|
||||
return nil, errors.New("forbidden to use container capabilities")
|
||||
}
|
||||
|
||||
if !settings.AllowBindMountsForRegularUsers && (len(partialContainer.HostConfig.Binds) > 0) {
|
||||
return forbiddenResponse, errors.New("forbidden to use bind mounts")
|
||||
}
|
||||
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
@@ -84,3 +88,54 @@ func selectorServiceLabels(responseObject map[string]interface{}) map[string]int
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (transport *Transport) decorateServiceCreationOperation(request *http.Request) (*http.Response, error) {
|
||||
type PartialService struct {
|
||||
TaskTemplate struct {
|
||||
ContainerSpec struct {
|
||||
Mounts []struct {
|
||||
Type string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forbiddenResponse := &http.Response{
|
||||
StatusCode: http.StatusForbidden,
|
||||
}
|
||||
|
||||
isAdminOrEndpointAdmin, err := transport.isAdminOrEndpointAdmin(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isAdminOrEndpointAdmin {
|
||||
settings, err := transport.settingsService.Settings()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
partialService := &PartialService{}
|
||||
err = json.Unmarshal(body, partialService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !settings.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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
|
||||
return transport.replaceRegistryAuthenticationHeader(request)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ type (
|
||||
extensionService portainer.ExtensionService
|
||||
dockerClient *client.Client
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
authDisabled bool
|
||||
}
|
||||
|
||||
// TransportParameters is used to create a new Transport
|
||||
@@ -54,6 +55,7 @@ type (
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
ExtensionService portainer.ExtensionService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
AuthDisabled bool
|
||||
}
|
||||
|
||||
restrictedDockerOperationContext struct {
|
||||
@@ -94,6 +96,7 @@ func NewTransport(parameters *TransportParameters, httpTransport *http.Transport
|
||||
dockerClientFactory: parameters.DockerClientFactory,
|
||||
HTTPTransport: httpTransport,
|
||||
dockerClient: dockerClient,
|
||||
authDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
|
||||
return transport, nil
|
||||
@@ -245,7 +248,7 @@ func (transport *Transport) proxyContainerRequest(request *http.Request) (*http.
|
||||
func (transport *Transport) proxyServiceRequest(request *http.Request) (*http.Response, error) {
|
||||
switch requestPath := request.URL.Path; requestPath {
|
||||
case "/services/create":
|
||||
return transport.replaceRegistryAuthenticationHeader(request)
|
||||
return transport.decorateServiceCreationOperation(request)
|
||||
|
||||
case "/services":
|
||||
return transport.rewriteOperation(request, transport.serviceListOperation)
|
||||
@@ -656,7 +659,6 @@ func (transport *Transport) createRegistryAccessContext(request *http.Request) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
accessContext := ®istryAccessContext{
|
||||
isAdmin: true,
|
||||
userID: tokenData.ID,
|
||||
@@ -734,3 +736,36 @@ func (transport *Transport) createOperationContext(request *http.Request) (*rest
|
||||
|
||||
return operationContext, nil
|
||||
}
|
||||
|
||||
func (transport *Transport) isAdminOrEndpointAdmin(request *http.Request) (bool, error) {
|
||||
if transport.authDisabled {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tokenData, err := security.RetrieveTokenData(request)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if tokenData.Role == portainer.AdministratorRole {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
user, err := transport.userService.User(tokenData.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rbacExtension, err := transport.extensionService.Extension(portainer.RBACExtension)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if rbacExtension == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, endpointResourceAccess := user.EndpointAuthorizations[portainer.EndpointID(transport.endpoint.ID)][portainer.EndpointResourcesAccess]
|
||||
|
||||
return endpointResourceAccess, nil
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ func (factory ProxyFactory) newOSBasedLocalProxy(path string, endpoint *portaine
|
||||
ExtensionService: factory.extensionService,
|
||||
SignatureService: factory.signatureService,
|
||||
DockerClientFactory: factory.dockerClientFactory,
|
||||
AuthDisabled: factory.authDisabled,
|
||||
}
|
||||
|
||||
proxy := &dockerLocalProxy{}
|
||||
|
||||
@@ -25,6 +25,7 @@ func (factory ProxyFactory) newOSBasedLocalProxy(path string, endpoint *portaine
|
||||
ExtensionService: factory.extensionService,
|
||||
SignatureService: factory.signatureService,
|
||||
DockerClientFactory: factory.dockerClientFactory,
|
||||
AuthDisabled: factory.authDisabled,
|
||||
}
|
||||
|
||||
proxy := &dockerLocalProxy{}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/portainer/portainer/api"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
)
|
||||
|
||||
@@ -32,6 +32,7 @@ type (
|
||||
reverseTunnelService portainer.ReverseTunnelService
|
||||
extensionService portainer.ExtensionService
|
||||
dockerClientFactory *docker.ClientFactory
|
||||
authDisabled bool
|
||||
}
|
||||
|
||||
// ProxyFactoryParameters is used to create a new ProxyFactory
|
||||
@@ -47,6 +48,7 @@ type (
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
ExtensionService portainer.ExtensionService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
AuthDisabled bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -64,6 +66,7 @@ func NewProxyFactory(parameters *ProxyFactoryParameters) *ProxyFactory {
|
||||
reverseTunnelService: parameters.ReverseTunnelService,
|
||||
extensionService: parameters.ExtensionService,
|
||||
dockerClientFactory: parameters.DockerClientFactory,
|
||||
authDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/orcaman/concurrent-map"
|
||||
"github.com/portainer/portainer/api"
|
||||
cmap "github.com/orcaman/concurrent-map"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/docker"
|
||||
"github.com/portainer/portainer/api/http/proxy/factory"
|
||||
)
|
||||
@@ -34,6 +34,7 @@ type (
|
||||
ReverseTunnelService portainer.ReverseTunnelService
|
||||
ExtensionService portainer.ExtensionService
|
||||
DockerClientFactory *docker.ClientFactory
|
||||
AuthDisabled bool
|
||||
}
|
||||
)
|
||||
|
||||
@@ -51,6 +52,7 @@ func NewManager(parameters *ManagerParams) *Manager {
|
||||
ReverseTunnelService: parameters.ReverseTunnelService,
|
||||
ExtensionService: parameters.ExtensionService,
|
||||
DockerClientFactory: parameters.DockerClientFactory,
|
||||
AuthDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
|
||||
return &Manager{
|
||||
|
||||
@@ -104,6 +104,7 @@ func (server *Server) Start() error {
|
||||
ReverseTunnelService: server.ReverseTunnelService,
|
||||
ExtensionService: server.ExtensionService,
|
||||
DockerClientFactory: server.DockerClientFactory,
|
||||
AuthDisabled: server.AuthDisabled,
|
||||
}
|
||||
proxyManager := proxy.NewManager(proxyManagerParameters)
|
||||
|
||||
@@ -247,7 +248,7 @@ func (server *Server) Start() error {
|
||||
settingsHandler.ExtensionService = server.ExtensionService
|
||||
settingsHandler.AuthorizationService = authorizationService
|
||||
|
||||
var stackHandler = stacks.NewHandler(requestBouncer)
|
||||
var stackHandler = stacks.NewHandler(requestBouncer, server.AuthDisabled)
|
||||
stackHandler.FileService = server.FileService
|
||||
stackHandler.StackService = server.StackService
|
||||
stackHandler.EndpointService = server.EndpointService
|
||||
|
||||
@@ -420,22 +420,23 @@ 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"`
|
||||
SnapshotInterval string `json:"SnapshotInterval"`
|
||||
TemplatesURL string `json:"TemplatesURL"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
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"`
|
||||
SnapshotInterval string `json:"SnapshotInterval"`
|
||||
TemplatesURL string `json:"TemplatesURL"`
|
||||
EnableHostManagementFeatures bool `json:"EnableHostManagementFeatures"`
|
||||
EdgeAgentCheckinInterval int `json:"EdgeAgentCheckinInterval"`
|
||||
EnableEdgeComputeFeatures bool `json:"EnableEdgeComputeFeatures"`
|
||||
AllowStackManagementForRegularUsers bool `json:"AllowStackManagementForRegularUsers"`
|
||||
AllowHostNamespaceForRegularUsers bool `json:"AllowHostNamespaceForRegularUsers"`
|
||||
AllowDeviceMappingForRegularUsers bool `json:"AllowDeviceMappingForRegularUsers"`
|
||||
AllowContainerCapabilitiesForRegularUsers bool `json:"AllowContainerCapabilitiesForRegularUsers"`
|
||||
|
||||
// Deprecated fields
|
||||
DisplayDonationHeader bool
|
||||
@@ -1010,7 +1011,7 @@ type (
|
||||
|
||||
const (
|
||||
// APIVersion is the version number of the Portainer API
|
||||
APIVersion = "1.24.1"
|
||||
APIVersion = "1.24.2"
|
||||
// DBVersion is the version number of the Portainer database
|
||||
DBVersion = 24
|
||||
// AssetsServerURL represents the URL of the Portainer asset server
|
||||
|
||||
@@ -610,6 +610,11 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
$scope.formValues.NodeName = nodeName;
|
||||
HttpRequestHelper.setPortainerAgentTargetHeader(nodeName);
|
||||
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.isAdminOrEndpointAdmin = await checkIfAdminOrEndpointAdmin();
|
||||
$scope.showDeviceMapping = await shouldShowDevices($scope.isAdminOrEndpointAdmin);
|
||||
$scope.areContainerCapabilitiesEnabled = await checkIfContainerCapabilitiesEnabled($scope.isAdminOrEndpointAdmin);
|
||||
|
||||
Volume.query(
|
||||
{},
|
||||
function (d) {
|
||||
@@ -645,7 +650,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
loadFromContainerSpec();
|
||||
} else {
|
||||
$scope.fromContainer = {};
|
||||
$scope.formValues.capabilities = new ContainerCapabilities();
|
||||
$scope.formValues.capabilities = $scope.areContainerCapabilitiesEnabled ? new ContainerCapabilities() : [];
|
||||
}
|
||||
},
|
||||
function (e) {
|
||||
@@ -672,7 +677,7 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
|
||||
SettingsService.publicSettings()
|
||||
.then(function success(data) {
|
||||
$scope.allowBindMounts = data.AllowBindMountsForRegularUsers;
|
||||
$scope.allowBindMounts = $scope.isAdminOrEndpointAdmin || data.AllowBindMountsForRegularUsers;
|
||||
$scope.allowPrivilegedMode = data.AllowPrivilegedModeForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
@@ -682,9 +687,6 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
PluginService.loggingPlugins(apiVersion < 1.25).then(function success(loggingDrivers) {
|
||||
$scope.availableLoggingDrivers = loggingDrivers;
|
||||
});
|
||||
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.showDeviceMapping = await shouldShowDevices();
|
||||
}
|
||||
|
||||
function validateForm(accessControlData, isAdmin) {
|
||||
@@ -897,17 +899,25 @@ angular.module('portainer.docker').controller('CreateContainerController', [
|
||||
}
|
||||
}
|
||||
|
||||
async function shouldShowDevices() {
|
||||
const isAdmin = !$scope.applicationState.application.authentication || Authentication.isAdmin();
|
||||
async function shouldShowDevices(isAdminOrEndpointAdmin) {
|
||||
const { allowDeviceMappingForRegularUsers } = $scope.applicationState.application;
|
||||
|
||||
if (isAdmin || allowDeviceMappingForRegularUsers) {
|
||||
return allowDeviceMappingForRegularUsers || isAdminOrEndpointAdmin;
|
||||
}
|
||||
|
||||
async function checkIfContainerCapabilitiesEnabled(isAdminOrEndpointAdmin) {
|
||||
const { allowContainerCapabilitiesForRegularUsers } = $scope.applicationState.application;
|
||||
|
||||
return allowContainerCapabilitiesForRegularUsers || isAdminOrEndpointAdmin;
|
||||
}
|
||||
|
||||
async function checkIfAdminOrEndpointAdmin() {
|
||||
if (Authentication.isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (rbacEnabled) {
|
||||
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||
}
|
||||
return rbacEnabled ? Authentication.hasAuthorizations(['EndpointResourcesAccess']) : false;
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
@@ -185,7 +185,7 @@
|
||||
<li class="interactive"><a data-target="#labels" data-toggle="tab">Labels</a></li>
|
||||
<li class="interactive"><a data-target="#restart-policy" data-toggle="tab">Restart policy</a></li>
|
||||
<li class="interactive"><a data-target="#runtime-resources" ng-click="refreshSlider()" data-toggle="tab">Runtime & Resources</a></li>
|
||||
<li class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||
<li ng-if="areContainerCapabilitiesEnabled" class="interactive"><a data-target="#container-capabilities" data-toggle="tab">Capabilities</a></li>
|
||||
</ul>
|
||||
<!-- tab-content -->
|
||||
<div class="tab-content">
|
||||
@@ -338,8 +338,8 @@
|
||||
</div>
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;" ng-if="isAdmin || allowBindMounts">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm" ng-if="allowBindMounts">
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.type" uib-btn-radio="'bind'" ng-click="volume.name = ''">Bind</label>
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,7 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
'SettingsService',
|
||||
'WebhookService',
|
||||
'EndpointProvider',
|
||||
'ExtensionService',
|
||||
function (
|
||||
$q,
|
||||
$scope,
|
||||
@@ -58,7 +59,8 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
NodeService,
|
||||
SettingsService,
|
||||
WebhookService,
|
||||
EndpointProvider
|
||||
EndpointProvider,
|
||||
ExtensionService
|
||||
) {
|
||||
$scope.formValues = {
|
||||
Name: '',
|
||||
@@ -106,6 +108,8 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
actionInProgress: false,
|
||||
};
|
||||
|
||||
$scope.allowBindMounts = false;
|
||||
|
||||
$scope.refreshSlider = function () {
|
||||
$timeout(function () {
|
||||
$scope.$broadcast('rzSliderForceRender');
|
||||
@@ -562,8 +566,8 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
secrets: apiVersion >= 1.25 ? SecretService.secrets() : [],
|
||||
configs: apiVersion >= 1.3 ? ConfigService.configs() : [],
|
||||
nodes: NodeService.nodes(),
|
||||
settings: SettingsService.publicSettings(),
|
||||
availableLoggingDrivers: PluginService.loggingPlugins(apiVersion < 1.25),
|
||||
allowBindMounts: checkIfAllowedBindMounts(),
|
||||
})
|
||||
.then(function success(data) {
|
||||
$scope.availableVolumes = data.volumes;
|
||||
@@ -572,8 +576,8 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
$scope.availableConfigs = data.configs;
|
||||
$scope.availableLoggingDrivers = data.availableLoggingDrivers;
|
||||
initSlidersMaxValuesBasedOnNodeData(data.nodes);
|
||||
$scope.allowBindMounts = data.settings.AllowBindMountsForRegularUsers;
|
||||
$scope.isAdmin = Authentication.isAdmin();
|
||||
$scope.allowBindMounts = data.allowBindMounts;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to initialize view');
|
||||
@@ -581,5 +585,22 @@ angular.module('portainer.docker').controller('CreateServiceController', [
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
async function checkIfAllowedBindMounts() {
|
||||
const isAdmin = Authentication.isAdmin();
|
||||
|
||||
const settings = await SettingsService.publicSettings();
|
||||
const { AllowBindMountsForRegularUsers } = settings;
|
||||
|
||||
if (isAdmin || AllowBindMountsForRegularUsers) {
|
||||
return true;
|
||||
}
|
||||
const rbacEnabled = await ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC);
|
||||
if (rbacEnabled) {
|
||||
return Authentication.hasAuthorizations(['EndpointResourcesAccess']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
<!-- !container-path -->
|
||||
<!-- volume-type -->
|
||||
<div class="input-group col-sm-5" style="margin-left: 5px;">
|
||||
<div class="btn-group btn-group-sm" ng-if="isAdmin || allowBindMounts">
|
||||
<div class="btn-group btn-group-sm" ng-if="allowBindMounts">
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'volume'" ng-click="volume.name = ''">Volume</label>
|
||||
<label class="btn btn-primary" ng-model="volume.Type" uib-btn-radio="'bind'" ng-click="volume.Id = ''">Bind</label>
|
||||
</div>
|
||||
|
||||
@@ -73,14 +73,16 @@ angular.module('portainer.docker').controller('VolumesController', [
|
||||
|
||||
$scope.showBrowseAction = $scope.applicationState.endpoint.mode.agentProxy;
|
||||
|
||||
ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) {
|
||||
if (!extensionEnabled) {
|
||||
var isAdmin = Authentication.isAdmin();
|
||||
if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) {
|
||||
$scope.showBrowseAction = false;
|
||||
if ($scope.applicationState.application.authentication) {
|
||||
ExtensionService.extensionEnabled(ExtensionService.EXTENSIONS.RBAC).then(function success(extensionEnabled) {
|
||||
if (!extensionEnabled) {
|
||||
var isAdmin = Authentication.isAdmin();
|
||||
if (!$scope.applicationState.application.enableVolumeBrowserForNonAdminUsers && !isAdmin) {
|
||||
$scope.showBrowseAction = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
initView();
|
||||
|
||||
@@ -16,6 +16,7 @@ export function SettingsViewModel(data) {
|
||||
this.AllowStackManagementForRegularUsers = data.AllowStackManagementForRegularUsers;
|
||||
this.AllowHostNamespaceForRegularUsers = data.AllowHostNamespaceForRegularUsers;
|
||||
this.AllowDeviceMappingForRegularUsers = data.AllowDeviceMappingForRegularUsers;
|
||||
this.AllowContainerCapabilitiesForRegularUsers = data.AllowContainerCapabilitiesForRegularUsers;
|
||||
}
|
||||
|
||||
export function PublicSettingsViewModel(settings) {
|
||||
@@ -31,6 +32,7 @@ export function PublicSettingsViewModel(settings) {
|
||||
this.AllowStackManagementForRegularUsers = settings.AllowStackManagementForRegularUsers;
|
||||
this.AllowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
||||
this.AllowHostNamespaceForRegularUsers = settings.AllowHostNamespaceForRegularUsers;
|
||||
this.AllowContainerCapabilitiesForRegularUsers = settings.AllowContainerCapabilitiesForRegularUsers;
|
||||
}
|
||||
|
||||
export function LDAPSettingsViewModel(data) {
|
||||
|
||||
@@ -225,6 +225,23 @@ angular.module('portainer.app').factory('ModalService', [
|
||||
);
|
||||
};
|
||||
|
||||
service.upgradeNotification = function () {
|
||||
bootbox.dialog({
|
||||
size: 'extra-large',
|
||||
title: 'Upgrade notification',
|
||||
message: `You are currently using version 1.x of Portainer, which is no longer supported maintained or enhanced. Continuing to use this version is at your own risk.
|
||||
<br/><br/>Please upgrade immediately by using the tag <code>portainer/portainer-ce:2.0.1</code>
|
||||
<br/><br/>If you decide to stay on version 1, please use the tag <code>portainer/portainer:1.24.2</code> to prevent automatically upgrading to version 2.`,
|
||||
onEscape: false,
|
||||
buttons: {
|
||||
Close: {
|
||||
label: 'Close',
|
||||
className: 'btn-primary',
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -101,6 +101,11 @@ angular.module('portainer.app').factory('StateManager', [
|
||||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
manager.updateAllowContainerCapabilitiesForRegularUsers = function updateAllowContainerCapabilitiesForRegularUsers(allowContainerCapabilitiesForRegularUsers) {
|
||||
state.application.allowContainerCapabilitiesForRegularUsers = allowContainerCapabilitiesForRegularUsers;
|
||||
LocalStorage.storeApplicationState(state.application);
|
||||
};
|
||||
|
||||
function assignStateFromStatusAndSettings(status, settings) {
|
||||
state.application.authentication = status.Authentication;
|
||||
state.application.analytics = status.Analytics;
|
||||
@@ -117,6 +122,7 @@ angular.module('portainer.app').factory('StateManager', [
|
||||
state.application.allowPrivilegedModeForRegularUsers = settings.AllowPrivilegedModeForRegularUsers;
|
||||
state.application.allowDeviceMappingForRegularUsers = settings.AllowDeviceMappingForRegularUsers;
|
||||
state.application.allowHostNamespaceForRegularUsers = settings.AllowHostNamespaceForRegularUsers;
|
||||
state.application.allowContainerCapabilitiesForRegularUsers = settings.AllowContainerCapabilitiesForRegularUsers;
|
||||
state.application.validity = moment().unix();
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ class AuthenticationController {
|
||||
SettingsService,
|
||||
URLHelper,
|
||||
LocalStorage,
|
||||
StatusService
|
||||
StatusService,
|
||||
ModalService
|
||||
) {
|
||||
this.$async = $async;
|
||||
this.$scope = $scope;
|
||||
@@ -37,6 +38,7 @@ class AuthenticationController {
|
||||
this.URLHelper = URLHelper;
|
||||
this.LocalStorage = LocalStorage;
|
||||
this.StatusService = StatusService;
|
||||
this.ModalService = ModalService;
|
||||
|
||||
this.logo = this.StateManager.getState().application.logo;
|
||||
this.formValues = {
|
||||
@@ -157,6 +159,7 @@ class AuthenticationController {
|
||||
}
|
||||
} finally {
|
||||
this.StateManager.setVersionInfo(versionInfo);
|
||||
this.ModalService.upgradeNotification();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,6 +153,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-12">
|
||||
<label for="toggle_disableContainerCapabilitiesForRegularUsers" class="control-label text-left">
|
||||
Disable container capabilities for non-administrators
|
||||
</label>
|
||||
<label class="switch" style="margin-left: 20px;">
|
||||
<input type="checkbox" name="toggle_disableContainerCapabilitiesForRegularUsers" ng-model="formValues.disableContainerCapabilitiesForRegularUsers" /><i></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-if="isContainerEditDisabled()">
|
||||
<span class="col-sm-12 text-muted small">
|
||||
Note: The recreate/duplicate/edit feature is currently disabled (for non-admin users) by one or more security settings.
|
||||
|
||||
@@ -36,11 +36,20 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
allowStackManagementForRegularUsers: false,
|
||||
restrictHostNamespaceForRegularUsers: false,
|
||||
allowDeviceMappingForRegularUsers: false,
|
||||
disableContainerCapabilitiesForRegularUsers: false,
|
||||
};
|
||||
|
||||
$scope.isContainerEditDisabled = function isContainerEditDisabled() {
|
||||
const { restrictBindMounts, restrictHostNamespaceForRegularUsers, restrictPrivilegedMode, disableDeviceMappingForRegularUsers } = this.formValues;
|
||||
return restrictBindMounts || restrictHostNamespaceForRegularUsers || restrictPrivilegedMode || disableDeviceMappingForRegularUsers;
|
||||
const {
|
||||
restrictBindMounts,
|
||||
restrictHostNamespaceForRegularUsers,
|
||||
restrictPrivilegedMode,
|
||||
disableDeviceMappingForRegularUsers,
|
||||
disableContainerCapabilitiesForRegularUsers,
|
||||
} = this.formValues;
|
||||
return (
|
||||
restrictBindMounts || restrictHostNamespaceForRegularUsers || restrictPrivilegedMode || disableDeviceMappingForRegularUsers || disableContainerCapabilitiesForRegularUsers
|
||||
);
|
||||
};
|
||||
|
||||
$scope.removeFilteredContainerLabel = function (index) {
|
||||
@@ -80,6 +89,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
settings.AllowStackManagementForRegularUsers = !$scope.formValues.disableStackManagementForRegularUsers;
|
||||
settings.AllowHostNamespaceForRegularUsers = !$scope.formValues.restrictHostNamespaceForRegularUsers;
|
||||
settings.AllowDeviceMappingForRegularUsers = !$scope.formValues.disableDeviceMappingForRegularUsers;
|
||||
settings.AllowContainerCapabilitiesForRegularUsers = !$scope.formValues.disableContainerCapabilitiesForRegularUsers;
|
||||
|
||||
$scope.state.actionInProgress = true;
|
||||
updateSettings(settings);
|
||||
@@ -99,6 +109,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
StateManager.updateAllowDeviceMappingForRegularUsers(settings.AllowDeviceMappingForRegularUsers);
|
||||
StateManager.updateAllowPrivilegedModeForRegularUsers(settings.AllowPrivilegedModeForRegularUsers);
|
||||
StateManager.updateAllowBindMountsForRegularUsers(settings.AllowBindMountsForRegularUsers);
|
||||
StateManager.updateAllowContainerCapabilitiesForRegularUsers(settings.AllowContainerCapabilitiesForRegularUsers);
|
||||
$state.reload();
|
||||
})
|
||||
.catch(function error(err) {
|
||||
@@ -128,6 +139,7 @@ angular.module('portainer.app').controller('SettingsController', [
|
||||
$scope.formValues.disableStackManagementForRegularUsers = !settings.AllowStackManagementForRegularUsers;
|
||||
$scope.formValues.restrictHostNamespaceForRegularUsers = !settings.AllowHostNamespaceForRegularUsers;
|
||||
$scope.formValues.disableDeviceMappingForRegularUsers = !settings.AllowDeviceMappingForRegularUsers;
|
||||
$scope.formValues.disableContainerCapabilitiesForRegularUsers = !settings.AllowContainerCapabilitiesForRegularUsers;
|
||||
})
|
||||
.catch(function error(err) {
|
||||
Notifications.error('Failure', err, 'Unable to retrieve application settings');
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
param (
|
||||
[string]$platform,
|
||||
[string]$arch
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop";
|
||||
|
||||
$binary = "portainer.exe"
|
||||
$go_path = "$($(Get-ITEM -Path env:AGENT_TEMPDIRECTORY).Value)\go"
|
||||
|
||||
Set-Item env:GOPATH "$go_path"
|
||||
|
||||
New-Item -Name dist -Path "." -ItemType Directory -Force | Out-Null
|
||||
New-Item -Name portainer -Path "$go_path\src\github.com\portainer" -ItemType Directory -Force | Out-Null
|
||||
|
||||
Copy-Item -Path "api" -Destination "$go_path\src\github.com\portainer\portainer\api" -Recurse -Force
|
||||
|
||||
Set-Location -Path "api\cmd\portainer"
|
||||
|
||||
go get -t -d -v ./...
|
||||
## go build -v
|
||||
& cmd /c 'go build -v 2>&1'
|
||||
|
||||
Copy-Item -Path "portainer.exe" -Destination "$($env:BUILD_SOURCESDIRECTORY)\dist\portainer.exe" -Force
|
||||
@@ -1,3 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
PLATFORM=$1
|
||||
ARCH=$2
|
||||
|
||||
export GOPATH="/tmp/go"
|
||||
|
||||
binary="portainer"
|
||||
@@ -10,6 +15,10 @@ cp -R api ${GOPATH}/src/github.com/portainer/portainer/api
|
||||
cd 'api/cmd/portainer'
|
||||
|
||||
go get -t -d -v ./...
|
||||
GOOS=$1 GOARCH=$2 CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s'
|
||||
GOOS=${PLATFORM} GOARCH=${ARCH} CGO_ENABLED=0 go build -a --installsuffix cgo --ldflags '-s'
|
||||
|
||||
mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/$binary" "$BUILD_SOURCESDIRECTORY/dist/portainer"
|
||||
if [ "${PLATFORM}" == 'windows' ]; then
|
||||
mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/${binary}.exe" "$BUILD_SOURCESDIRECTORY/dist/portainer.exe"
|
||||
else
|
||||
mv "$BUILD_SOURCESDIRECTORY/api/cmd/portainer/$binary" "$BUILD_SOURCESDIRECTORY/dist/portainer"
|
||||
fi
|
||||
@@ -1,13 +0,0 @@
|
||||
param (
|
||||
[string]$docker_version
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop";
|
||||
|
||||
New-Item -Path "docker-binary" -ItemType Directory | Out-Null
|
||||
|
||||
$download_folder = "docker-binary"
|
||||
|
||||
Invoke-WebRequest -O "$($download_folder)/docker-binaries.zip" "https://download.docker.com/win/static/stable/x86_64/docker-$($docker_version).zip"
|
||||
Expand-Archive -Path "$($download_folder)/docker-binaries.zip" -DestinationPath "$($download_folder)"
|
||||
Move-Item -Path "$($download_folder)/docker/docker.exe" -Destination "dist"
|
||||
14
build/windows/Dockerfile
Normal file
14
build/windows/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
ARG OSVERSION
|
||||
FROM --platform=linux/amd64 gcr.io/k8s-staging-e2e-test-images/windows-servercore-cache:1.0-linux-amd64-${OSVERSION} as core
|
||||
FROM mcr.microsoft.com/windows/nanoserver:${OSVERSION}
|
||||
|
||||
COPY --from=core /Windows/System32/netapi32.dll /Windows/System32/netapi32.dll
|
||||
|
||||
USER ContainerAdministrator
|
||||
|
||||
COPY dist /
|
||||
|
||||
EXPOSE 9000
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
@@ -1,9 +0,0 @@
|
||||
FROM microsoft/nanoserver:sac2016
|
||||
|
||||
COPY dist /
|
||||
|
||||
WORKDIR /
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
ENTRYPOINT ["/portainer.exe"]
|
||||
25
gruntfile.js
25
gruntfile.js
@@ -142,11 +142,7 @@ function shell_build_binary(p, a) {
|
||||
}
|
||||
|
||||
function shell_build_binary_azuredevops(p, a) {
|
||||
if (p === 'linux') {
|
||||
return 'build/build_binary_azuredevops.sh ' + p + ' ' + a + ';';
|
||||
} else {
|
||||
return 'powershell -Command ".\\build\\build_binary_azuredevops.ps1 -platform ' + p + ' -arch ' + a + '"';
|
||||
}
|
||||
return 'build/build_binary_azuredevops.sh ' + p + ' ' + a + ';';
|
||||
}
|
||||
|
||||
function shell_run_container() {
|
||||
@@ -164,15 +160,12 @@ function shell_download_docker_binary(p, a) {
|
||||
var ip = ps[p] === undefined ? p : ps[p];
|
||||
var ia = as[a] === undefined ? a : as[a];
|
||||
var binaryVersion = p === 'windows' ? '<%= shippedDockerVersionWindows %>' : '<%= shippedDockerVersion %>';
|
||||
if (p === 'linux' || p === 'mac') {
|
||||
return ['if [ -f dist/docker ]; then', 'echo "Docker binary exists";', 'else', 'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';', 'fi'].join(' ');
|
||||
} else {
|
||||
return [
|
||||
'powershell -Command "& {if (Get-Item -Path dist/docker.exe -ErrorAction:SilentlyContinue) {',
|
||||
'Write-Host "Docker binary exists"',
|
||||
'} else {',
|
||||
'& ".\\build\\download_docker_binary.ps1" -docker_version ' + binaryVersion + '',
|
||||
'}}"',
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
return [
|
||||
'if [ -f dist/docker ] || [ -f dist/docker.exe ]; then',
|
||||
'echo "docker binary exists";',
|
||||
'else',
|
||||
'build/download_docker_binary.sh ' + ip + ' ' + ia + ' ' + binaryVersion + ';',
|
||||
'fi',
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"author": "Portainer.io",
|
||||
"name": "portainer",
|
||||
"homepage": "http://portainer.io",
|
||||
"version": "1.24.1",
|
||||
"version": "1.24.2",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:portainer/portainer.git"
|
||||
|
||||
Reference in New Issue
Block a user