2247d8c3a2
* + endpoint and namespace level authorizations + user namespace authorization API + k8s client setup service account with k8s roles and policies by portainer role * User authorization changes refresh token cache * rbac authorizes k8s requests * CE to EE migrator to include new authorizations * code clean up * comments * * merge in the RestrictDefaultNamespace changes * - remove unnecessary check for default namespace * + updates namespace access policies when generating token * * updates namespace access policies when querying the user namespace endpoint * + k8s rule in rbac.go for endpoint access test + missing k8s cluster rules for different roles * feat(rbac): update kube rbac * feat(rbac): use the authorization directive * feat(rbac): Update namespace access policies when user/team is deleted * refactor(app): use new angular-multi-select capabilities * feat(rbac): fix authorizations * feat(rbac): fix userAccessPolicies update bug * feat(rbac): add W applications authorizations * feat(rbac): add application details W authorizations * feat(rbac): add configurations W autohorizations * feat(rbac): add configuration details W authorizations * feat(rbac): add volumes W authorizations * feat(rbac): add volume details W authorizations * feat(rbac): add componentstatus to portainer-view role and add cluster/node authorizations * fix(rbac): disable application note for non authorized user * fix(rbac): add endpoints list and components status to portainer-basic * fix(rbac): allow user to access default namespace when restrict default namespace isn't activated * fix(rbac): remove default namespace from useraccesspolicies when restrict default namespace isn't activated * fix(rbac): change some things * fix(rbac): allow standard user to access container console * - removed unused parameter * fix(rbac): fix team authorizations Co-authored-by: Maxime Bajeux <max.bajeux@gmail.com> Co-authored-by: xAt0mZ <baron_l@epitech.eu>
590 lines
17 KiB
Go
590 lines
17 KiB
Go
package bolt
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/portainer/portainer/api/bolt/license"
|
|
|
|
"github.com/boltdb/bolt"
|
|
portainer "github.com/portainer/portainer/api"
|
|
"github.com/portainer/portainer/api/bolt/customtemplate"
|
|
"github.com/portainer/portainer/api/bolt/dockerhub"
|
|
"github.com/portainer/portainer/api/bolt/edgegroup"
|
|
"github.com/portainer/portainer/api/bolt/edgejob"
|
|
"github.com/portainer/portainer/api/bolt/edgestack"
|
|
"github.com/portainer/portainer/api/bolt/endpoint"
|
|
"github.com/portainer/portainer/api/bolt/endpointgroup"
|
|
"github.com/portainer/portainer/api/bolt/endpointrelation"
|
|
bolterrors "github.com/portainer/portainer/api/bolt/errors"
|
|
"github.com/portainer/portainer/api/bolt/extension"
|
|
"github.com/portainer/portainer/api/bolt/migrator"
|
|
"github.com/portainer/portainer/api/bolt/migratoree"
|
|
"github.com/portainer/portainer/api/bolt/registry"
|
|
"github.com/portainer/portainer/api/bolt/resourcecontrol"
|
|
"github.com/portainer/portainer/api/bolt/role"
|
|
"github.com/portainer/portainer/api/bolt/schedule"
|
|
"github.com/portainer/portainer/api/bolt/settings"
|
|
"github.com/portainer/portainer/api/bolt/stack"
|
|
"github.com/portainer/portainer/api/bolt/tag"
|
|
"github.com/portainer/portainer/api/bolt/team"
|
|
"github.com/portainer/portainer/api/bolt/teammembership"
|
|
"github.com/portainer/portainer/api/bolt/tunnelserver"
|
|
"github.com/portainer/portainer/api/bolt/user"
|
|
"github.com/portainer/portainer/api/bolt/version"
|
|
"github.com/portainer/portainer/api/bolt/webhook"
|
|
"github.com/portainer/portainer/api/cli"
|
|
"github.com/portainer/portainer/api/internal/authorization"
|
|
)
|
|
|
|
const (
|
|
databaseFileName = "portainer.db"
|
|
)
|
|
|
|
// Store defines the implementation of portainer.DataStore using
|
|
// BoltDB as the storage system.
|
|
type Store struct {
|
|
path string
|
|
db *bolt.DB
|
|
isNew bool
|
|
fileService portainer.FileService
|
|
CustomTemplateService *customtemplate.Service
|
|
DockerHubService *dockerhub.Service
|
|
EdgeGroupService *edgegroup.Service
|
|
EdgeJobService *edgejob.Service
|
|
EdgeStackService *edgestack.Service
|
|
EndpointGroupService *endpointgroup.Service
|
|
EndpointService *endpoint.Service
|
|
EndpointRelationService *endpointrelation.Service
|
|
ExtensionService *extension.Service
|
|
LicenseService *license.Service
|
|
RegistryService *registry.Service
|
|
ResourceControlService *resourcecontrol.Service
|
|
RoleService *role.Service
|
|
ScheduleService *schedule.Service
|
|
SettingsService *settings.Service
|
|
StackService *stack.Service
|
|
TagService *tag.Service
|
|
TeamMembershipService *teammembership.Service
|
|
TeamService *team.Service
|
|
TunnelServerService *tunnelserver.Service
|
|
UserService *user.Service
|
|
VersionService *version.Service
|
|
WebhookService *webhook.Service
|
|
}
|
|
|
|
// NewStore initializes a new Store and the associated services
|
|
func NewStore(storePath string, fileService portainer.FileService) (*Store, error) {
|
|
store := &Store{
|
|
path: storePath,
|
|
fileService: fileService,
|
|
isNew: true,
|
|
}
|
|
|
|
databasePath := path.Join(storePath, databaseFileName)
|
|
databaseFileExists, err := fileService.FileExists(databasePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if databaseFileExists {
|
|
store.isNew = false
|
|
}
|
|
|
|
return store, nil
|
|
}
|
|
|
|
// Open opens and initializes the BoltDB database.
|
|
func (store *Store) Open() error {
|
|
databasePath := path.Join(store.path, databaseFileName)
|
|
db, err := bolt.Open(databasePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.db = db
|
|
|
|
return store.initServices()
|
|
}
|
|
|
|
// Close closes the BoltDB database.
|
|
func (store *Store) Close() error {
|
|
if store.db != nil {
|
|
return store.db.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsNew returns true if the database was just created and false if it is re-using
|
|
// existing data.
|
|
func (store *Store) IsNew() bool {
|
|
return store.isNew
|
|
}
|
|
|
|
// MigrateData automatically migrate the data based on the DBVersion.
|
|
// This process is only triggered on an existing database, not if the database was just created.
|
|
func (store *Store) MigrateData() error {
|
|
if store.isNew {
|
|
err := store.VersionService.StoreDBVersion(portainer.DBVersionEE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = store.VersionService.StoreEdition(portainer.PortainerEE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
version, err := store.VersionService.DBVersion()
|
|
if err == bolterrors.ErrObjectNotFound {
|
|
version = 0
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
edition, err := store.VersionService.Edition()
|
|
if err == bolterrors.ErrObjectNotFound {
|
|
edition = portainer.PortainerCE
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
if edition == portainer.PortainerCE && version < portainer.DBVersion {
|
|
migratorParams := &migrator.Parameters{
|
|
DB: store.db,
|
|
DatabaseVersion: version,
|
|
EndpointGroupService: store.EndpointGroupService,
|
|
EndpointService: store.EndpointService,
|
|
EndpointRelationService: store.EndpointRelationService,
|
|
ExtensionService: store.ExtensionService,
|
|
RegistryService: store.RegistryService,
|
|
ResourceControlService: store.ResourceControlService,
|
|
RoleService: store.RoleService,
|
|
ScheduleService: store.ScheduleService,
|
|
SettingsService: store.SettingsService,
|
|
StackService: store.StackService,
|
|
TagService: store.TagService,
|
|
TeamMembershipService: store.TeamMembershipService,
|
|
UserService: store.UserService,
|
|
VersionService: store.VersionService,
|
|
FileService: store.fileService,
|
|
AuthorizationService: authorization.NewService(store),
|
|
}
|
|
migrator := migrator.NewMigrator(migratorParams)
|
|
|
|
log.Printf("[INFO] [bolt, migrate] [message: Migrating CE database from version %v to %v.]", version, portainer.DBVersion)
|
|
err = migrator.Migrate()
|
|
if err != nil {
|
|
log.Printf("[ERROR] [bolt, migrate] [message: An error occurred during database migration: %s]", err)
|
|
return err
|
|
}
|
|
|
|
version = portainer.DBVersion
|
|
}
|
|
|
|
if edition < portainer.PortainerEE {
|
|
migratorParams := &migratoree.Parameters{
|
|
CurrentEdition: edition,
|
|
DB: store.db,
|
|
DatabaseVersion: version,
|
|
|
|
AuthorizationService: authorization.NewService(store),
|
|
EndpointGroupService: store.EndpointGroupService,
|
|
EndpointService: store.EndpointService,
|
|
ExtensionService: store.ExtensionService,
|
|
SettingsService: store.SettingsService,
|
|
UserService: store.UserService,
|
|
VersionService: store.VersionService,
|
|
RoleService: store.RoleService,
|
|
}
|
|
migrator := migratoree.NewMigrator(migratorParams)
|
|
|
|
log.Printf("[INFO] [bolt, migrate] [message: Migrating CE database version %d to EE database version %d.]", version, portainer.DBVersionEE)
|
|
err = migrator.MigrateFromCEdbv25()
|
|
if err != nil {
|
|
log.Printf("[ERROR] [bolt, migrate] [message: An error occurred during database migration: %s]", err)
|
|
return err
|
|
}
|
|
|
|
version = portainer.DBVersionEE
|
|
edition = portainer.PortainerEE
|
|
}
|
|
|
|
if edition == portainer.PortainerBE && version < portainer.DBVersionEE {
|
|
migratorParams := &migratoree.Parameters{
|
|
CurrentEdition: edition,
|
|
DB: store.db,
|
|
DatabaseVersion: version,
|
|
|
|
AuthorizationService: authorization.NewService(store),
|
|
EndpointGroupService: store.EndpointGroupService,
|
|
EndpointService: store.EndpointService,
|
|
ExtensionService: store.ExtensionService,
|
|
SettingsService: store.SettingsService,
|
|
UserService: store.UserService,
|
|
VersionService: store.VersionService,
|
|
RoleService: store.RoleService,
|
|
}
|
|
migrator := migratoree.NewMigrator(migratorParams)
|
|
|
|
log.Printf("[INFO] [bolt, migrate] Migrating EE database from version %v to %v.", version, portainer.DBVersionEE)
|
|
err = migrator.Migrate()
|
|
if err != nil {
|
|
log.Printf("[ERROR] [bolt, migrate] [message: An error occurred during database migration: %s]", err)
|
|
return err
|
|
}
|
|
|
|
version = portainer.DBVersionEE
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (store *Store) backupDBAndRestoreIfFailed(action func() error) error {
|
|
databasePath := path.Join(store.path, databaseFileName)
|
|
backupPath := databasePath + ".old"
|
|
log.Printf("[INFO] [bolt, backup] [message: creating db backup at %s]", backupPath)
|
|
|
|
err := store.fileService.Copy(databasePath, backupPath, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = action()
|
|
if err != nil {
|
|
databasePath := path.Join(store.path, databaseFileName)
|
|
backupPath := databasePath + ".old"
|
|
log.Printf("[INFO] [bolt, backup] [message: restoring db backup from %s after failure] [error: %s]", backupPath, err)
|
|
|
|
copyErr := store.fileService.Copy(backupPath, databasePath, true)
|
|
if copyErr != nil {
|
|
log.Printf("[ERROR] [bolt, backup] [message: failed restoring db backup from %s] [error: %s]", backupPath, copyErr)
|
|
return copyErr
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RollbackToCE rollbacks the store to the current ce version
|
|
func (store *Store) RollbackToCE() error {
|
|
version, err := store.Version().DBVersion()
|
|
if err == bolterrors.ErrObjectNotFound {
|
|
version = 0
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
edition, err := store.Version().Edition()
|
|
if err == bolterrors.ErrObjectNotFound {
|
|
edition = portainer.PortainerCE
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("Current Software Edition: %s\n", getEditionLabel(edition))
|
|
log.Printf("Current DB Version: %d\n", version)
|
|
|
|
if edition == portainer.PortainerCE {
|
|
return errors.New("DB is already on CE edition")
|
|
}
|
|
|
|
confirmed, err := cli.Confirm("Are you sure you want to rollback your database?")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !confirmed {
|
|
return nil
|
|
}
|
|
|
|
err = store.backupDBAndRestoreIfFailed(func() error {
|
|
migratorParams := &migratoree.Parameters{
|
|
CurrentEdition: edition,
|
|
DB: store.db,
|
|
DatabaseVersion: version,
|
|
|
|
AuthorizationService: authorization.NewService(store),
|
|
EndpointGroupService: store.EndpointGroupService,
|
|
EndpointService: store.EndpointService,
|
|
ExtensionService: store.ExtensionService,
|
|
UserService: store.UserService,
|
|
VersionService: store.VersionService,
|
|
}
|
|
migrator := migratoree.NewMigrator(migratorParams)
|
|
|
|
return migrator.Rollback()
|
|
})
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Printf("Rolled back to CE Edition, DB Version %d\n", portainer.DBVersion)
|
|
return nil
|
|
}
|
|
|
|
func getEditionLabel(edition portainer.SoftwareEdition) string {
|
|
switch edition {
|
|
case portainer.PortainerCE:
|
|
return "CE"
|
|
case portainer.PortainerEE:
|
|
return "EE"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (store *Store) initServices() error {
|
|
authorizationsetService, err := role.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.RoleService = authorizationsetService
|
|
|
|
customTemplateService, err := customtemplate.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.CustomTemplateService = customTemplateService
|
|
|
|
dockerhubService, err := dockerhub.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.DockerHubService = dockerhubService
|
|
|
|
edgeStackService, err := edgestack.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.EdgeStackService = edgeStackService
|
|
|
|
edgeGroupService, err := edgegroup.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.EdgeGroupService = edgeGroupService
|
|
|
|
edgeJobService, err := edgejob.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.EdgeJobService = edgeJobService
|
|
|
|
endpointgroupService, err := endpointgroup.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.EndpointGroupService = endpointgroupService
|
|
|
|
endpointService, err := endpoint.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.EndpointService = endpointService
|
|
|
|
endpointRelationService, err := endpointrelation.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.EndpointRelationService = endpointRelationService
|
|
|
|
extensionService, err := extension.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.ExtensionService = extensionService
|
|
|
|
licenseService, err := license.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.LicenseService = licenseService
|
|
|
|
registryService, err := registry.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.RegistryService = registryService
|
|
|
|
resourcecontrolService, err := resourcecontrol.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.ResourceControlService = resourcecontrolService
|
|
|
|
settingsService, err := settings.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.SettingsService = settingsService
|
|
|
|
stackService, err := stack.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.StackService = stackService
|
|
|
|
tagService, err := tag.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.TagService = tagService
|
|
|
|
teammembershipService, err := teammembership.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.TeamMembershipService = teammembershipService
|
|
|
|
teamService, err := team.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.TeamService = teamService
|
|
|
|
tunnelServerService, err := tunnelserver.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.TunnelServerService = tunnelServerService
|
|
|
|
userService, err := user.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.UserService = userService
|
|
|
|
versionService, err := version.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.VersionService = versionService
|
|
|
|
webhookService, err := webhook.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.WebhookService = webhookService
|
|
|
|
scheduleService, err := schedule.NewService(store.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
store.ScheduleService = scheduleService
|
|
|
|
return nil
|
|
}
|
|
|
|
// CustomTemplate gives access to the CustomTemplate data management layer
|
|
func (store *Store) CustomTemplate() portainer.CustomTemplateService {
|
|
return store.CustomTemplateService
|
|
}
|
|
|
|
// DockerHub gives access to the DockerHub data management layer
|
|
func (store *Store) DockerHub() portainer.DockerHubService {
|
|
return store.DockerHubService
|
|
}
|
|
|
|
// EdgeGroup gives access to the EdgeGroup data management layer
|
|
func (store *Store) EdgeGroup() portainer.EdgeGroupService {
|
|
return store.EdgeGroupService
|
|
}
|
|
|
|
// EdgeJob gives access to the EdgeJob data management layer
|
|
func (store *Store) EdgeJob() portainer.EdgeJobService {
|
|
return store.EdgeJobService
|
|
}
|
|
|
|
// EdgeStack gives access to the EdgeStack data management layer
|
|
func (store *Store) EdgeStack() portainer.EdgeStackService {
|
|
return store.EdgeStackService
|
|
}
|
|
|
|
// Endpoint gives access to the Endpoint data management layer
|
|
func (store *Store) Endpoint() portainer.EndpointService {
|
|
return store.EndpointService
|
|
}
|
|
|
|
// EndpointGroup gives access to the EndpointGroup data management layer
|
|
func (store *Store) EndpointGroup() portainer.EndpointGroupService {
|
|
return store.EndpointGroupService
|
|
}
|
|
|
|
// EndpointRelation gives access to the EndpointRelation data management layer
|
|
func (store *Store) EndpointRelation() portainer.EndpointRelationService {
|
|
return store.EndpointRelationService
|
|
}
|
|
|
|
// License provides access to the License data management layer
|
|
func (store *Store) License() portainer.LicenseRepository {
|
|
return store.LicenseService
|
|
}
|
|
|
|
// Registry gives access to the Registry data management layer
|
|
func (store *Store) Registry() portainer.RegistryService {
|
|
return store.RegistryService
|
|
}
|
|
|
|
// ResourceControl gives access to the ResourceControl data management layer
|
|
func (store *Store) ResourceControl() portainer.ResourceControlService {
|
|
return store.ResourceControlService
|
|
}
|
|
|
|
// Role gives access to the Role data management layer
|
|
func (store *Store) Role() portainer.RoleService {
|
|
return store.RoleService
|
|
}
|
|
|
|
// Settings gives access to the Settings data management layer
|
|
func (store *Store) Settings() portainer.SettingsService {
|
|
return store.SettingsService
|
|
}
|
|
|
|
// Stack gives access to the Stack data management layer
|
|
func (store *Store) Stack() portainer.StackService {
|
|
return store.StackService
|
|
}
|
|
|
|
// Tag gives access to the Tag data management layer
|
|
func (store *Store) Tag() portainer.TagService {
|
|
return store.TagService
|
|
}
|
|
|
|
// TeamMembership gives access to the TeamMembership data management layer
|
|
func (store *Store) TeamMembership() portainer.TeamMembershipService {
|
|
return store.TeamMembershipService
|
|
}
|
|
|
|
// Team gives access to the Team data management layer
|
|
func (store *Store) Team() portainer.TeamService {
|
|
return store.TeamService
|
|
}
|
|
|
|
// TunnelServer gives access to the TunnelServer data management layer
|
|
func (store *Store) TunnelServer() portainer.TunnelServerService {
|
|
return store.TunnelServerService
|
|
}
|
|
|
|
// User gives access to the User data management layer
|
|
func (store *Store) User() portainer.UserService {
|
|
return store.UserService
|
|
}
|
|
|
|
// Version gives access to the Version data management layer
|
|
func (store *Store) Version() portainer.VersionService {
|
|
return store.VersionService
|
|
}
|
|
|
|
// Webhook gives access to the Webhook data management layer
|
|
func (store *Store) Webhook() portainer.WebhookService {
|
|
return store.WebhookService
|
|
}
|