Compare commits

..

15 Commits

Author SHA1 Message Date
Anthony Lapenna ecf52616e2 feat(edge): update deployment instructions 2019-06-17 11:37:09 +12:00
Anthony Lapenna 69bc599b5b feat(edge): refactor key creation 2019-06-17 09:18:17 +12:00
Anthony Lapenna e58b019ffa feat(edge): wip edge 2019-06-12 12:06:59 +12:00
Anthony Lapenna 1fc4e7bddb Merge branch 'develop' into edge 2019-06-05 09:24:22 +12:00
Anthony Lapenna 2cabfd574c Merge branch 'develop' into edge
# Conflicts:
#	api/portainer.go
2019-05-27 11:29:35 +12:00
Anthony Lapenna 3b946d84ac feat(endpoint-creation): update Edge agent deployment instructions 2019-05-24 18:01:50 +12:00
Anthony Lapenna 28abe55179 refactor(api): rename AgentIoTEnvironment to AgentEdgeEnvironment 2019-05-24 12:02:46 +12:00
Anthony Lapenna e31365c6a5 refactor(api): rename AgentIoTEnvironment to AgentEdgeEnvironment 2019-05-24 11:46:18 +12:00
Anthony Lapenna bedb4fc7f4 style(endpoint-creation): refactor IoT agent to Edge agent 2019-05-24 11:26:59 +12:00
Anthony Lapenna 8f05ba77b4 Merge branch 'develop' into edge 2019-05-24 11:11:57 +12:00
Anthony Lapenna d03e22e26e feat(intel): add -v /:/host and --name portainer_agent_iot to agent command 2019-04-24 20:15:05 +12:00
Anthony Lapenna ec667a19a0 feat(intel): add -e CAP_HOST_MANAGEMENT=1 to agent command 2019-04-24 20:05:28 +12:00
Anthony Lapenna 8afe1ac37b feat(intel): display agent features when connected to IoT endpoint 2019-04-24 19:35:26 +12:00
Anthony Lapenna 9dc3188cc0 feat(intel): fix webconsole and agent deployment command 2019-04-24 19:31:22 +12:00
Anthony Lapenna 9cf014adab feat(intel): POC Intel 2019-04-24 14:59:15 +12:00
354 changed files with 2345 additions and 7727 deletions
+6
View File
@@ -21,18 +21,24 @@ Also, be sure to check our FAQ and documentation first: https://portainer.readth
-->
**Bug description**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
Briefly describe what you were expecting.
**Steps to reproduce the issue:**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Technical details:**
* Portainer version:
* Docker version (managed by Portainer):
* Platform (windows/linux):
-55
View File
@@ -1,55 +0,0 @@
# Config for Stalebot, limited to only `issues`
only: issues
# Issues config
issues:
daysUntilStale: 60
daysUntilClose: 7
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Issues with these labels will never be considered stale
exemptLabels:
- kind/enhancement
- kind/feature
- kind/question
- kind/style
- kind/workaround
- bug/need-confirmation
- bug/confirmed
- status/discuss
# Only issues with all of these labels are checked if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: true
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: true
# Label to use when marking an issue as stale
staleLabel: status/stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been marked as stale as it has not had recent activity,
it will be closed if no further activity occurs in the next 7 days.
If you believe that it has been incorrectly labelled as stale,
leave a comment and the label will be removed.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: >
Since no further activity has appeared on this issue it will be closed.
If you believe that it has been incorrectly closed, leave a comment
and mention @itsconquest. One of our staff will then review the issue.
Note - If it is an old bug report, make sure that it is reproduceable in the
latest version of Portainer as it may have already been fixed.
+1 -5
View File
@@ -11,7 +11,7 @@
**_Portainer_** is a lightweight management UI which allows you to **easily** manage your different Docker environments (Docker hosts or Swarm clusters).
**_Portainer_** is meant to be as **simple** to deploy as it is to use. It consists of a single container that can run on any Docker engine (can be deployed as Linux container or a Windows native container, supports other platforms too).
**_Portainer_** allows you to manage all your Docker resources (containers, images, volumes, networks and more) ! It is compatible with the *standalone Docker* engine and with *Docker Swarm mode*.
**_Portainer_** allows you to manage your all your Docker resources (containers, images, volumes, networks and more) ! It is compatible with the *standalone Docker* engine and with *Docker Swarm mode*.
## Demo
@@ -45,10 +45,6 @@ Unlike the public demo, the playground sessions are deleted after 4 hours. Apart
* Want to report a bug or request a feature? Please open [an issue](https://github.com/portainer/portainer/issues/new).
* Want to help us build **_portainer_**? Follow our [contribution guidelines](https://portainer.readthedocs.io/en/latest/contribute.html) to build it locally and make a pull request. We need all the help we can get!
## Security
* Here at Portainer, we believe in [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure) of security issues. If you have found a security issue, please report it to <security@portainer.io>.
## Limitations
**_Portainer_** has full support for the following Docker versions:
-419
View File
@@ -1,419 +0,0 @@
package portainer
// AuthorizationService represents a service used to
// update authorizations associated to a user or team.
type AuthorizationService struct {
endpointService EndpointService
endpointGroupService EndpointGroupService
registryService RegistryService
roleService RoleService
teamMembershipService TeamMembershipService
userService UserService
}
// AuthorizationServiceParameters are the required parameters
// used to create a new AuthorizationService.
type AuthorizationServiceParameters struct {
EndpointService EndpointService
EndpointGroupService EndpointGroupService
RegistryService RegistryService
RoleService RoleService
TeamMembershipService TeamMembershipService
UserService UserService
}
// NewAuthorizationService returns a point to a new AuthorizationService instance.
func NewAuthorizationService(parameters *AuthorizationServiceParameters) *AuthorizationService {
return &AuthorizationService{
endpointService: parameters.EndpointService,
endpointGroupService: parameters.EndpointGroupService,
registryService: parameters.RegistryService,
roleService: parameters.RoleService,
teamMembershipService: parameters.TeamMembershipService,
userService: parameters.UserService,
}
}
// DefaultPortainerAuthorizations returns the default Portainer authorizations used by non-admin users.
func DefaultPortainerAuthorizations() Authorizations {
return map[Authorization]bool{
OperationPortainerDockerHubInspect: true,
OperationPortainerEndpointGroupList: true,
OperationPortainerEndpointList: true,
OperationPortainerEndpointInspect: true,
OperationPortainerEndpointExtensionAdd: true,
OperationPortainerEndpointExtensionRemove: true,
OperationPortainerExtensionList: true,
OperationPortainerMOTD: true,
OperationPortainerRegistryList: true,
OperationPortainerRegistryInspect: true,
OperationPortainerTeamList: true,
OperationPortainerTemplateList: true,
OperationPortainerTemplateInspect: true,
OperationPortainerUserList: true,
OperationPortainerUserInspect: true,
OperationPortainerUserMemberships: true,
}
}
// 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()
if err != nil {
return err
}
for _, endpoint := range endpoints {
for policyTeamID := range endpoint.TeamAccessPolicies {
if policyTeamID == teamID {
delete(endpoint.TeamAccessPolicies, policyTeamID)
err := service.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
break
}
}
}
endpointGroups, err := service.endpointGroupService.EndpointGroups()
if err != nil {
return err
}
for _, endpointGroup := range endpointGroups {
for policyTeamID := range endpointGroup.TeamAccessPolicies {
if policyTeamID == teamID {
delete(endpointGroup.TeamAccessPolicies, policyTeamID)
err := service.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
if err != nil {
return err
}
break
}
}
}
registries, err := service.registryService.Registries()
if err != nil {
return err
}
for _, registry := range registries {
for policyTeamID := range registry.TeamAccessPolicies {
if policyTeamID == teamID {
delete(registry.TeamAccessPolicies, policyTeamID)
err := service.registryService.UpdateRegistry(registry.ID, &registry)
if err != nil {
return err
}
break
}
}
}
return nil
}
// RemoveUserAccessPolicies will remove all existing access policies associated to the specified user
func (service *AuthorizationService) RemoveUserAccessPolicies(userID UserID) error {
endpoints, err := service.endpointService.Endpoints()
if err != nil {
return err
}
for _, endpoint := range endpoints {
for policyUserID := range endpoint.UserAccessPolicies {
if policyUserID == userID {
delete(endpoint.UserAccessPolicies, policyUserID)
err := service.endpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return err
}
break
}
}
}
endpointGroups, err := service.endpointGroupService.EndpointGroups()
if err != nil {
return err
}
for _, endpointGroup := range endpointGroups {
for policyUserID := range endpointGroup.UserAccessPolicies {
if policyUserID == userID {
delete(endpointGroup.UserAccessPolicies, policyUserID)
err := service.endpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
if err != nil {
return err
}
break
}
}
}
registries, err := service.registryService.Registries()
if err != nil {
return err
}
for _, registry := range registries {
for policyUserID := range registry.UserAccessPolicies {
if policyUserID == userID {
delete(registry.UserAccessPolicies, policyUserID)
err := service.registryService.UpdateRegistry(registry.ID, &registry)
if err != nil {
return err
}
break
}
}
}
return nil
}
// UpdateUsersAuthorizations will trigger an update of the authorizations for all the users.
func (service *AuthorizationService) UpdateUsersAuthorizations() error {
users, err := service.userService.Users()
if err != nil {
return err
}
for _, user := range users {
err := service.updateUserAuthorizations(user.ID)
if err != nil {
return err
}
}
return nil
}
func (service *AuthorizationService) updateUserAuthorizations(userID UserID) error {
user, err := service.userService.User(userID)
if err != nil {
return err
}
endpointAuthorizations, err := service.getAuthorizations(user)
if err != nil {
return err
}
user.EndpointAuthorizations = endpointAuthorizations
return service.userService.UpdateUser(userID, user)
}
func (service *AuthorizationService) getAuthorizations(user *User) (EndpointAuthorizations, error) {
endpointAuthorizations := EndpointAuthorizations{}
if user.Role == AdministratorRole {
return endpointAuthorizations, nil
}
userMemberships, err := service.teamMembershipService.TeamMembershipsByUserID(user.ID)
if err != nil {
return endpointAuthorizations, err
}
endpoints, err := service.endpointService.Endpoints()
if err != nil {
return endpointAuthorizations, err
}
endpointGroups, err := service.endpointGroupService.EndpointGroups()
if err != nil {
return endpointAuthorizations, err
}
roles, err := service.roleService.Roles()
if err != nil {
return endpointAuthorizations, err
}
endpointAuthorizations = getUserEndpointAuthorizations(user, endpoints, endpointGroups, roles, userMemberships)
return endpointAuthorizations, nil
}
func getUserEndpointAuthorizations(user *User, endpoints []Endpoint, endpointGroups []EndpointGroup, roles []Role, userMemberships []TeamMembership) EndpointAuthorizations {
endpointAuthorizations := make(EndpointAuthorizations)
groupUserAccessPolicies := map[EndpointGroupID]UserAccessPolicies{}
groupTeamAccessPolicies := map[EndpointGroupID]TeamAccessPolicies{}
for _, endpointGroup := range endpointGroups {
groupUserAccessPolicies[endpointGroup.ID] = endpointGroup.UserAccessPolicies
groupTeamAccessPolicies[endpointGroup.ID] = endpointGroup.TeamAccessPolicies
}
for _, endpoint := range endpoints {
authorizations := getAuthorizationsFromUserEndpointPolicy(user, &endpoint, roles)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
continue
}
authorizations = getAuthorizationsFromUserEndpointGroupPolicy(user, &endpoint, roles, groupUserAccessPolicies)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
continue
}
authorizations = getAuthorizationsFromTeamEndpointPolicies(userMemberships, &endpoint, roles)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
continue
}
authorizations = getAuthorizationsFromTeamEndpointGroupPolicies(userMemberships, &endpoint, roles, groupTeamAccessPolicies)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
}
}
return endpointAuthorizations
}
func getAuthorizationsFromUserEndpointPolicy(user *User, endpoint *Endpoint, roles []Role) Authorizations {
policyRoles := make([]RoleID, 0)
policy, ok := endpoint.UserAccessPolicies[user.ID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromUserEndpointGroupPolicy(user *User, endpoint *Endpoint, roles []Role, groupAccessPolicies map[EndpointGroupID]UserAccessPolicies) Authorizations {
policyRoles := make([]RoleID, 0)
policy, ok := groupAccessPolicies[endpoint.GroupID][user.ID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromTeamEndpointPolicies(memberships []TeamMembership, endpoint *Endpoint, roles []Role) Authorizations {
policyRoles := make([]RoleID, 0)
for _, membership := range memberships {
policy, ok := endpoint.TeamAccessPolicies[membership.TeamID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromTeamEndpointGroupPolicies(memberships []TeamMembership, endpoint *Endpoint, roles []Role, groupAccessPolicies map[EndpointGroupID]TeamAccessPolicies) Authorizations {
policyRoles := make([]RoleID, 0)
for _, membership := range memberships {
policy, ok := groupAccessPolicies[endpoint.GroupID][membership.TeamID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromRoles(roleIdentifiers []RoleID, roles []Role) Authorizations {
var roleAuthorizations []Authorizations
for _, id := range roleIdentifiers {
for _, role := range roles {
if role.ID == id {
roleAuthorizations = append(roleAuthorizations, role.Authorizations)
break
}
}
}
processedAuthorizations := make(Authorizations)
if len(roleAuthorizations) > 0 {
processedAuthorizations = roleAuthorizations[0]
for idx, authorizations := range roleAuthorizations {
if idx == 0 {
continue
}
processedAuthorizations = mergeAuthorizations(processedAuthorizations, authorizations)
}
}
return processedAuthorizations
}
func mergeAuthorizations(a, b Authorizations) Authorizations {
c := make(map[Authorization]bool)
for k := range b {
if _, ok := a[k]; ok {
c[k] = true
}
}
return c
}
-12
View File
@@ -5,8 +5,6 @@ import (
"path"
"time"
"github.com/portainer/portainer/api/bolt/tunnelserver"
"github.com/boltdb/bolt"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/dockerhub"
@@ -53,7 +51,6 @@ type Store struct {
TeamMembershipService *teammembership.Service
TeamService *team.Service
TemplateService *template.Service
TunnelServerService *tunnelserver.Service
UserService *user.Service
VersionService *version.Service
WebhookService *webhook.Service
@@ -124,11 +121,8 @@ func (store *Store) MigrateData() error {
ExtensionService: store.ExtensionService,
RegistryService: store.RegistryService,
ResourceControlService: store.ResourceControlService,
RoleService: store.RoleService,
ScheduleService: store.ScheduleService,
SettingsService: store.SettingsService,
StackService: store.StackService,
TeamMembershipService: store.TeamMembershipService,
TemplateService: store.TemplateService,
UserService: store.UserService,
VersionService: store.VersionService,
@@ -226,12 +220,6 @@ func (store *Store) initServices() error {
}
store.TemplateService = templateService
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
+3 -2
View File
@@ -1,9 +1,10 @@
package endpoint
import (
"github.com/boltdb/bolt"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/internal"
"github.com/boltdb/bolt"
)
const (
@@ -63,7 +64,7 @@ func (service *Service) Endpoints() ([]portainer.Endpoint, error) {
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var endpoint portainer.Endpoint
err := internal.UnmarshalObjectWithJsoniter(v, &endpoint)
err := internal.UnmarshalObject(v, &endpoint)
if err != nil {
return err
}
+9
View File
@@ -218,6 +218,8 @@ 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,
@@ -340,6 +342,11 @@ 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,
@@ -406,6 +413,8 @@ 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,
-10
View File
@@ -2,8 +2,6 @@ package internal
import (
"encoding/json"
jsoniter "github.com/json-iterator/go"
)
// MarshalObject encodes an object to binary format
@@ -15,11 +13,3 @@ func MarshalObject(object interface{}) ([]byte, error) {
func UnmarshalObject(data []byte, object interface{}) error {
return json.Unmarshal(data, object)
}
// UnmarshalObjectWithJsoniter decodes an object from binary data
// using the jsoniter library. It is mainly used to accelerate endpoint
// decoding at the moment.
func UnmarshalObjectWithJsoniter(data []byte, object interface{}) error {
var jsoni = jsoniter.ConfigCompatibleWithStandardLibrary
return jsoni.Unmarshal(data, &object)
}
-16
View File
@@ -1,16 +0,0 @@
package migrator
import portainer "github.com/portainer/portainer/api"
func (m *Migrator) updateSettingsToDBVersion19() error {
legacySettings, err := m.settingsService.Settings()
if err != nil {
return err
}
if legacySettings.EdgeAgentCheckinInterval == 0 {
legacySettings.EdgeAgentCheckinInterval = portainer.DefaultEdgeAgentCheckinIntervalInSeconds
}
return m.settingsService.UpdateSettings(legacySettings)
}
-67
View File
@@ -1,67 +0,0 @@
package migrator
import (
"strings"
portainer "github.com/portainer/portainer/api"
)
func (m *Migrator) updateUsersToDBVersion20() error {
authorizationServiceParameters := &portainer.AuthorizationServiceParameters{
EndpointService: m.endpointService,
EndpointGroupService: m.endpointGroupService,
RegistryService: m.registryService,
RoleService: m.roleService,
TeamMembershipService: m.teamMembershipService,
UserService: m.userService,
}
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)
}
func (m *Migrator) updateSchedulesToDBVersion20() error {
legacySchedules, err := m.scheduleService.Schedules()
if err != nil {
return err
}
for _, schedule := range legacySchedules {
if schedule.JobType == portainer.ScriptExecutionJobType {
if schedule.CronExpression == "0 0 * * *" {
schedule.CronExpression = "0 * * * *"
} else if schedule.CronExpression == "0 0 0/2 * *" {
schedule.CronExpression = "0 */2 * * *"
} else if schedule.CronExpression == "0 0 0 * *" {
schedule.CronExpression = "0 0 * * *"
} else {
revisedCronExpression := strings.Split(schedule.CronExpression, " ")
if len(revisedCronExpression) == 5 {
continue
}
revisedCronExpression = revisedCronExpression[1:]
schedule.CronExpression = strings.Join(revisedCronExpression, " ")
}
err := m.scheduleService.UpdateSchedule(schedule.ID, &schedule)
if err != nil {
return err
}
}
}
return nil
}
-22
View File
@@ -1,22 +0,0 @@
package migrator
import (
portainer "github.com/portainer/portainer/api"
)
func (m *Migrator) updateUsersToDBVersion21() error {
legacyUsers, err := m.userService.Users()
if err != nil {
return err
}
for _, user := range legacyUsers {
user.PortainerAuthorizations = portainer.DefaultPortainerAuthorizations()
err = m.userService.UpdateUser(user.ID, &user)
if err != nil {
return err
}
}
return nil
}
-46
View File
@@ -8,11 +8,8 @@ import (
"github.com/portainer/portainer/api/bolt/extension"
"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/teammembership"
"github.com/portainer/portainer/api/bolt/template"
"github.com/portainer/portainer/api/bolt/user"
"github.com/portainer/portainer/api/bolt/version"
@@ -28,11 +25,8 @@ type (
extensionService *extension.Service
registryService *registry.Service
resourceControlService *resourcecontrol.Service
roleService *role.Service
scheduleService *schedule.Service
settingsService *settings.Service
stackService *stack.Service
teamMembershipService *teammembership.Service
templateService *template.Service
userService *user.Service
versionService *version.Service
@@ -48,11 +42,8 @@ type (
ExtensionService *extension.Service
RegistryService *registry.Service
ResourceControlService *resourcecontrol.Service
RoleService *role.Service
ScheduleService *schedule.Service
SettingsService *settings.Service
StackService *stack.Service
TeamMembershipService *teammembership.Service
TemplateService *template.Service
UserService *user.Service
VersionService *version.Service
@@ -70,10 +61,7 @@ func NewMigrator(parameters *Parameters) *Migrator {
extensionService: parameters.ExtensionService,
registryService: parameters.RegistryService,
resourceControlService: parameters.ResourceControlService,
roleService: parameters.RoleService,
scheduleService: parameters.ScheduleService,
settingsService: parameters.SettingsService,
teamMembershipService: parameters.TeamMembershipService,
templateService: parameters.TemplateService,
stackService: parameters.StackService,
userService: parameters.UserService,
@@ -261,39 +249,5 @@ func (m *Migrator) Migrate() error {
}
}
// Portainer 1.22.0
if m.currentDBVersion < 19 {
err := m.updateSettingsToDBVersion19()
if err != nil {
return err
}
}
// Portainer 1.22.1
if m.currentDBVersion < 20 {
err := m.updateUsersToDBVersion20()
if err != nil {
return err
}
err = m.updateSettingsToDBVersion20()
if err != nil {
return err
}
err = m.updateSchedulesToDBVersion20()
if err != nil {
return err
}
}
// Portainer 1.22.2
if m.currentDBVersion < 21 {
err := m.updateUsersToDBVersion21()
if err != nil {
return err
}
}
return m.versionService.StoreDBVersion(portainer.DBVersion)
}
+4 -10
View File
@@ -66,24 +66,18 @@ func (service *Service) Roles() ([]portainer.Role, error) {
}
// CreateRole creates a new Role.
func (service *Service) CreateRole(role *portainer.Role) error {
func (service *Service) CreateRole(set *portainer.Role) error {
return service.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
id, _ := bucket.NextSequence()
role.ID = portainer.RoleID(id)
set.ID = portainer.RoleID(id)
data, err := internal.MarshalObject(role)
data, err := internal.MarshalObject(set)
if err != nil {
return err
}
return bucket.Put(internal.Itob(int(role.ID)), data)
return bucket.Put(internal.Itob(int(set.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)
}
-48
View File
@@ -1,48 +0,0 @@
package tunnelserver
import (
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/internal"
"github.com/boltdb/bolt"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "tunnel_server"
infoKey = "INFO"
)
// Service represents a service for managing endpoint data.
type Service struct {
db *bolt.DB
}
// NewService creates a new instance of a service.
func NewService(db *bolt.DB) (*Service, error) {
err := internal.CreateBucket(db, BucketName)
if err != nil {
return nil, err
}
return &Service{
db: db,
}, nil
}
// Info retrieve the TunnelServerInfo object.
func (service *Service) Info() (*portainer.TunnelServerInfo, error) {
var info portainer.TunnelServerInfo
err := internal.GetObject(service.db, BucketName, []byte(infoKey), &info)
if err != nil {
return nil, err
}
return &info, nil
}
// UpdateInfo persists a TunnelServerInfo object.
func (service *Service) UpdateInfo(settings *portainer.TunnelServerInfo) error {
return internal.UpdateObject(service.db, BucketName, []byte(infoKey), settings)
}
-24
View File
@@ -1,24 +0,0 @@
package chisel
import (
"encoding/base64"
"fmt"
"strconv"
"strings"
)
// GenerateEdgeKey will generate a key that can be used by an Edge agent to register with a Portainer instance.
// The key represents the following data in this particular format:
// portainer_instance_url|tunnel_server_addr|tunnel_server_fingerprint|endpoint_ID
// The key returned by this function is a base64 encoded version of the data.
func (service *Service) GenerateEdgeKey(url, host string, endpointIdentifier int) string {
keyInformation := []string{
url,
fmt.Sprintf("%s:%s", host, service.serverPort),
service.serverFingerprint,
strconv.Itoa(endpointIdentifier),
}
key := strings.Join(keyInformation, "|")
return base64.RawStdEncoding.EncodeToString([]byte(key))
}
-47
View File
@@ -1,47 +0,0 @@
package chisel
import (
"strconv"
portainer "github.com/portainer/portainer/api"
)
// AddSchedule register a schedule inside the tunnel details associated to an endpoint.
func (service *Service) AddSchedule(endpointID portainer.EndpointID, schedule *portainer.EdgeSchedule) {
tunnel := service.GetTunnelDetails(endpointID)
existingScheduleIndex := -1
for idx, existingSchedule := range tunnel.Schedules {
if existingSchedule.ID == schedule.ID {
existingScheduleIndex = idx
break
}
}
if existingScheduleIndex == -1 {
tunnel.Schedules = append(tunnel.Schedules, *schedule)
} else {
tunnel.Schedules[existingScheduleIndex] = *schedule
}
key := strconv.Itoa(int(endpointID))
service.tunnelDetailsMap.Set(key, tunnel)
}
// RemoveSchedule will remove the specified schedule from each tunnel it was registered with.
func (service *Service) RemoveSchedule(scheduleID portainer.ScheduleID) {
for item := range service.tunnelDetailsMap.IterBuffered() {
tunnelDetails := item.Val.(*portainer.TunnelDetails)
updatedSchedules := make([]portainer.EdgeSchedule, 0)
for _, schedule := range tunnelDetails.Schedules {
if schedule.ID == scheduleID {
continue
}
updatedSchedules = append(updatedSchedules, schedule)
}
tunnelDetails.Schedules = updatedSchedules
service.tunnelDetailsMap.Set(item.Key, tunnelDetails)
}
}
+43
View File
@@ -0,0 +1,43 @@
package chisel
import (
chserver "github.com/jpillora/chisel/server"
)
type Server struct {
address string
port string
fingerprint string
}
func NewServer(address string, port string) *Server {
return &Server{
address: address,
port: port,
}
}
// Start starts the reverse tunnel server
func (server *Server) Start() error {
// TODO: keyseed management (persistence)
// + auth management
// Consider multiple users for auth?
config := &chserver.Config{
Reverse: true,
KeySeed: "keyseedexample",
Auth: "agent@randomstring",
}
chiselServer, err := chserver.NewServer(config)
if err != nil {
return err
}
server.fingerprint = chiselServer.GetFingerprint()
return chiselServer.Start(server.address, server.port)
}
func (server *Server) GetFingerprint() string {
return server.fingerprint
}
-191
View File
@@ -1,191 +0,0 @@
package chisel
import (
"fmt"
"log"
"strconv"
"time"
"github.com/dchest/uniuri"
cmap "github.com/orcaman/concurrent-map"
chserver "github.com/jpillora/chisel/server"
portainer "github.com/portainer/portainer/api"
)
const (
tunnelCleanupInterval = 10 * time.Second
requiredTimeout = 15 * time.Second
activeTimeout = 4*time.Minute + 30*time.Second
)
// Service represents a service to manage the state of multiple reverse tunnels.
// It is used to start a reverse tunnel server and to manage the connection status of each tunnel
// connected to the tunnel server.
type Service struct {
serverFingerprint string
serverPort string
tunnelDetailsMap cmap.ConcurrentMap
endpointService portainer.EndpointService
tunnelServerService portainer.TunnelServerService
snapshotter portainer.Snapshotter
chiselServer *chserver.Server
}
// NewService returns a pointer to a new instance of Service
func NewService(endpointService portainer.EndpointService, tunnelServerService portainer.TunnelServerService) *Service {
return &Service{
tunnelDetailsMap: cmap.New(),
endpointService: endpointService,
tunnelServerService: tunnelServerService,
}
}
// StartTunnelServer starts a tunnel server on the specified addr and port.
// It uses a seed to generate a new private/public key pair. If the seed cannot
// be found inside the database, it will generate a new one randomly and persist it.
// It starts the tunnel status verification process in the background.
// The snapshotter is used in the tunnel status verification process.
func (service *Service) StartTunnelServer(addr, port string, snapshotter portainer.Snapshotter) error {
keySeed, err := service.retrievePrivateKeySeed()
if err != nil {
return err
}
config := &chserver.Config{
Reverse: true,
KeySeed: keySeed,
}
chiselServer, err := chserver.NewServer(config)
if err != nil {
return err
}
service.serverFingerprint = chiselServer.GetFingerprint()
service.serverPort = port
err = chiselServer.Start(addr, port)
if err != nil {
return err
}
service.chiselServer = chiselServer
// TODO: work-around Chisel default behavior.
// By default, Chisel will allow anyone to connect if no user exists.
username, password := generateRandomCredentials()
err = service.chiselServer.AddUser(username, password, "127.0.0.1")
if err != nil {
return err
}
service.snapshotter = snapshotter
go service.startTunnelVerificationLoop()
return nil
}
func (service *Service) retrievePrivateKeySeed() (string, error) {
var serverInfo *portainer.TunnelServerInfo
serverInfo, err := service.tunnelServerService.Info()
if err == portainer.ErrObjectNotFound {
keySeed := uniuri.NewLen(16)
serverInfo = &portainer.TunnelServerInfo{
PrivateKeySeed: keySeed,
}
err := service.tunnelServerService.UpdateInfo(serverInfo)
if err != nil {
return "", err
}
} else if err != nil {
return "", err
}
return serverInfo.PrivateKeySeed, nil
}
func (service *Service) startTunnelVerificationLoop() {
log.Printf("[DEBUG] [chisel, monitoring] [check_interval_seconds: %f] [message: starting tunnel management process]", tunnelCleanupInterval.Seconds())
ticker := time.NewTicker(tunnelCleanupInterval)
stopSignal := make(chan struct{})
for {
select {
case <-ticker.C:
service.checkTunnels()
case <-stopSignal:
ticker.Stop()
return
}
}
}
func (service *Service) checkTunnels() {
for item := range service.tunnelDetailsMap.IterBuffered() {
tunnel := item.Val.(*portainer.TunnelDetails)
if tunnel.LastActivity.IsZero() || tunnel.Status == portainer.EdgeAgentIdle {
continue
}
elapsed := time.Since(tunnel.LastActivity)
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %s] [status: %s] [status_time_seconds: %f] [message: endpoint tunnel monitoring]", item.Key, tunnel.Status, elapsed.Seconds())
if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() < requiredTimeout.Seconds() {
continue
} else if tunnel.Status == portainer.EdgeAgentManagementRequired && elapsed.Seconds() > requiredTimeout.Seconds() {
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %s] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: REQUIRED state timeout exceeded]", item.Key, tunnel.Status, elapsed.Seconds(), requiredTimeout.Seconds())
}
if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() < activeTimeout.Seconds() {
continue
} else if tunnel.Status == portainer.EdgeAgentActive && elapsed.Seconds() > activeTimeout.Seconds() {
log.Printf("[DEBUG] [chisel,monitoring] [endpoint_id: %s] [status: %s] [status_time_seconds: %f] [timeout_seconds: %f] [message: ACTIVE state timeout exceeded]", item.Key, tunnel.Status, elapsed.Seconds(), activeTimeout.Seconds())
endpointID, err := strconv.Atoi(item.Key)
if err != nil {
log.Printf("[ERROR] [chisel,snapshot,conversion] Invalid endpoint identifier (id: %s): %s", item.Key, err)
}
err = service.snapshotEnvironment(portainer.EndpointID(endpointID), tunnel.Port)
if err != nil {
log.Printf("[ERROR] [snapshot] Unable to snapshot Edge endpoint (id: %s): %s", item.Key, err)
}
}
if len(tunnel.Schedules) > 0 {
endpointID, err := strconv.Atoi(item.Key)
if err != nil {
log.Printf("[ERROR] [chisel,conversion] Invalid endpoint identifier (id: %s): %s", item.Key, err)
continue
}
service.SetTunnelStatusToIdle(portainer.EndpointID(endpointID))
} else {
service.tunnelDetailsMap.Remove(item.Key)
}
}
}
func (service *Service) snapshotEnvironment(endpointID portainer.EndpointID, tunnelPort int) error {
endpoint, err := service.endpointService.Endpoint(portainer.EndpointID(endpointID))
if err != nil {
return err
}
endpointURL := endpoint.URL
endpoint.URL = fmt.Sprintf("tcp://localhost:%d", tunnelPort)
snapshot, err := service.snapshotter.CreateSnapshot(endpoint)
if err != nil {
return err
}
endpoint.Snapshots = []portainer.Snapshot{*snapshot}
endpoint.URL = endpointURL
return service.endpointService.UpdateEndpoint(endpoint.ID, endpoint)
}
-144
View File
@@ -1,144 +0,0 @@
package chisel
import (
"encoding/base64"
"fmt"
"math/rand"
"strconv"
"strings"
"time"
"github.com/portainer/libcrypto"
"github.com/dchest/uniuri"
portainer "github.com/portainer/portainer/api"
)
const (
minAvailablePort = 49152
maxAvailablePort = 65535
)
// getUnusedPort is used to generate an unused random port in the dynamic port range.
// Dynamic ports (also called private ports) are 49152 to 65535.
func (service *Service) getUnusedPort() int {
port := randomInt(minAvailablePort, maxAvailablePort)
for item := range service.tunnelDetailsMap.IterBuffered() {
tunnel := item.Val.(*portainer.TunnelDetails)
if tunnel.Port == port {
return service.getUnusedPort()
}
}
return port
}
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}
// GetTunnelDetails returns information about the tunnel associated to an endpoint.
func (service *Service) GetTunnelDetails(endpointID portainer.EndpointID) *portainer.TunnelDetails {
key := strconv.Itoa(int(endpointID))
if item, ok := service.tunnelDetailsMap.Get(key); ok {
tunnelDetails := item.(*portainer.TunnelDetails)
return tunnelDetails
}
schedules := make([]portainer.EdgeSchedule, 0)
return &portainer.TunnelDetails{
Status: portainer.EdgeAgentIdle,
Port: 0,
Schedules: schedules,
Credentials: "",
}
}
// SetTunnelStatusToActive update the status of the tunnel associated to the specified endpoint.
// It sets the status to ACTIVE.
func (service *Service) SetTunnelStatusToActive(endpointID portainer.EndpointID) {
tunnel := service.GetTunnelDetails(endpointID)
tunnel.Status = portainer.EdgeAgentActive
tunnel.Credentials = ""
tunnel.LastActivity = time.Now()
key := strconv.Itoa(int(endpointID))
service.tunnelDetailsMap.Set(key, tunnel)
}
// SetTunnelStatusToIdle update the status of the tunnel associated to the specified endpoint.
// It sets the status to IDLE.
// It removes any existing credentials associated to the tunnel.
func (service *Service) SetTunnelStatusToIdle(endpointID portainer.EndpointID) {
tunnel := service.GetTunnelDetails(endpointID)
tunnel.Status = portainer.EdgeAgentIdle
tunnel.Port = 0
tunnel.LastActivity = time.Now()
credentials := tunnel.Credentials
if credentials != "" {
tunnel.Credentials = ""
service.chiselServer.DeleteUser(strings.Split(credentials, ":")[0])
}
key := strconv.Itoa(int(endpointID))
service.tunnelDetailsMap.Set(key, tunnel)
}
// SetTunnelStatusToRequired update the status of the tunnel associated to the specified endpoint.
// It sets the status to REQUIRED.
// If no port is currently associated to the tunnel, it will associate a random unused port to the tunnel
// and generate temporary credentials that can be used to establish a reverse tunnel on that port.
// Credentials are encrypted using the Edge ID associated to the endpoint.
func (service *Service) SetTunnelStatusToRequired(endpointID portainer.EndpointID) error {
tunnel := service.GetTunnelDetails(endpointID)
if tunnel.Port == 0 {
endpoint, err := service.endpointService.Endpoint(endpointID)
if err != nil {
return err
}
tunnel.Status = portainer.EdgeAgentManagementRequired
tunnel.Port = service.getUnusedPort()
tunnel.LastActivity = time.Now()
username, password := generateRandomCredentials()
authorizedRemote := fmt.Sprintf("^R:0.0.0.0:%d$", tunnel.Port)
err = service.chiselServer.AddUser(username, password, authorizedRemote)
if err != nil {
return err
}
credentials, err := encryptCredentials(username, password, endpoint.EdgeID)
if err != nil {
return err
}
tunnel.Credentials = credentials
key := strconv.Itoa(int(endpointID))
service.tunnelDetailsMap.Set(key, tunnel)
}
return nil
}
func generateRandomCredentials() (string, string) {
username := uniuri.NewLen(8)
password := uniuri.NewLen(8)
return username, password
}
func encryptCredentials(username, password, key string) (string, error) {
credentials := fmt.Sprintf("%s:%s", username, password)
encryptedCredentials, err := libcrypto.Encrypt([]byte(credentials), []byte(key))
if err != nil {
return "", err
}
return base64.RawStdEncoding.EncodeToString(encryptedCredentials), nil
}
+75 -75
View File
@@ -7,10 +7,9 @@ import (
"strings"
"time"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt"
"github.com/portainer/portainer/api/chisel"
"github.com/portainer/portainer/api/cli"
"github.com/portainer/portainer/api/cron"
"github.com/portainer/portainer/api/crypto"
@@ -70,12 +69,12 @@ func initStore(dataStorePath string, fileService portainer.FileService) *bolt.St
return store
}
func initComposeStackManager(dataStorePath string, reverseTunnelService portainer.ReverseTunnelService) portainer.ComposeStackManager {
return libcompose.NewComposeStackManager(dataStorePath, reverseTunnelService)
func initComposeStackManager(dataStorePath string) portainer.ComposeStackManager {
return libcompose.NewComposeStackManager(dataStorePath)
}
func initSwarmStackManager(assetsPath string, dataStorePath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) (portainer.SwarmStackManager, error) {
return exec.NewSwarmStackManager(assetsPath, dataStorePath, signatureService, fileService, reverseTunnelService)
func initSwarmStackManager(assetsPath string, dataStorePath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService) (portainer.SwarmStackManager, error) {
return exec.NewSwarmStackManager(assetsPath, dataStorePath, signatureService, fileService)
}
func initJWTService(authenticationEnabled bool) portainer.JWTService {
@@ -102,11 +101,11 @@ func initLDAPService() portainer.LDAPService {
}
func initGitService() portainer.GitService {
return git.NewService()
return &git.Service{}
}
func initClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService) *docker.ClientFactory {
return docker.NewClientFactory(signatureService, reverseTunnelService)
func initClientFactory(signatureService portainer.DigitalSignatureService) *docker.ClientFactory {
return docker.NewClientFactory(signatureService)
}
func initSnapshotter(clientFactory *docker.ClientFactory) portainer.Snapshotter {
@@ -197,7 +196,7 @@ func loadEndpointSyncSystemSchedule(jobScheduler portainer.JobScheduler, schedul
return scheduleService.CreateSchedule(endpointSyncSchedule)
}
func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService portainer.JobService, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) error {
func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService portainer.JobService, scheduleService portainer.ScheduleService, endpointService portainer.EndpointService, fileService portainer.FileService) error {
schedules, err := scheduleService.Schedules()
if err != nil {
return err
@@ -214,13 +213,6 @@ func loadSchedulesFromDatabase(jobScheduler portainer.JobScheduler, jobService p
return err
}
}
if schedule.EdgeSchedule != nil {
for _, endpointID := range schedule.EdgeSchedule.Endpoints {
reverseTunnelService.AddSchedule(endpointID, schedule.EdgeSchedule)
}
}
}
return nil
@@ -271,10 +263,8 @@ func initSettings(settingsService portainer.SettingsService, flags *portainer.CL
OAuthSettings: portainer.OAuthSettings{},
AllowBindMountsForRegularUsers: true,
AllowPrivilegedModeForRegularUsers: true,
AllowVolumeBrowserForRegularUsers: false,
EnableHostManagementFeatures: false,
SnapshotInterval: *flags.SnapshotInterval,
EdgeAgentCheckinInterval: portainer.DefaultEdgeAgentCheckinIntervalInSeconds,
}
if *flags.Templates != "" {
@@ -500,13 +490,8 @@ func initExtensionManager(fileService portainer.FileService, extensionService po
log.Printf("Unable to enable extension: %s [extension: %s]", err.Error(), extension.Name)
extension.Enabled = false
extension.License.Valid = false
extensionService.Persist(&extension)
}
err = extensionService.Persist(&extension)
if err != nil {
return nil, err
}
}
return extensionManager, nil
@@ -555,9 +540,7 @@ func main() {
log.Fatal(err)
}
reverseTunnelService := chisel.NewService(store.EndpointService, store.TunnelServerService)
clientFactory := initClientFactory(digitalSignatureService, reverseTunnelService)
clientFactory := initClientFactory(digitalSignatureService)
jobService := initJobService(clientFactory)
@@ -568,12 +551,12 @@ func main() {
endpointManagement = false
}
swarmStackManager, err := initSwarmStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService, reverseTunnelService)
swarmStackManager, err := initSwarmStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService)
if err != nil {
log.Fatal(err)
}
composeStackManager := initComposeStackManager(*flags.Data, reverseTunnelService)
composeStackManager := initComposeStackManager(*flags.Data)
err = initTemplates(store.TemplateService, fileService, *flags.Templates, *flags.TemplateFile)
if err != nil {
@@ -587,7 +570,7 @@ func main() {
jobScheduler := initJobScheduler()
err = loadSchedulesFromDatabase(jobScheduler, jobService, store.ScheduleService, store.EndpointService, fileService, reverseTunnelService)
err = loadSchedulesFromDatabase(jobScheduler, jobService, store.ScheduleService, store.EndpointService, fileService)
if err != nil {
log.Fatal(err)
}
@@ -624,7 +607,7 @@ func main() {
if err != nil {
log.Fatal(err)
}
adminPasswordHash, err = cryptoService.Hash(strings.TrimSuffix(string(content), "\n"))
adminPasswordHash, err = cryptoService.Hash(string(content))
if err != nil {
log.Fatal(err)
}
@@ -641,10 +624,26 @@ func main() {
if len(users) == 0 {
log.Printf("Creating admin user with password hash %s", adminPasswordHash)
user := &portainer.User{
Username: "admin",
Role: portainer.AdministratorRole,
Password: adminPasswordHash,
PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
Username: "admin",
Role: portainer.AdministratorRole,
Password: adminPasswordHash,
PortainerAuthorizations: map[portainer.Authorization]bool{
portainer.OperationPortainerDockerHubInspect: true,
portainer.OperationPortainerEndpointGroupList: true,
portainer.OperationPortainerEndpointList: true,
portainer.OperationPortainerEndpointInspect: true,
portainer.OperationPortainerEndpointExtensionAdd: true,
portainer.OperationPortainerEndpointExtensionRemove: true,
portainer.OperationPortainerExtensionList: true,
portainer.OperationPortainerMOTD: true,
portainer.OperationPortainerRegistryList: true,
portainer.OperationPortainerRegistryInspect: true,
portainer.OperationPortainerTeamList: true,
portainer.OperationPortainerTemplateList: true,
portainer.OperationPortainerTemplateInspect: true,
portainer.OperationPortainerUserList: true,
portainer.OperationPortainerUserMemberships: true,
},
}
err := store.UserService.CreateUser(user)
if err != nil {
@@ -659,50 +658,51 @@ func main() {
go terminateIfNoAdminCreated(store.UserService)
}
err = reverseTunnelService.StartTunnelServer(*flags.TunnelAddr, *flags.TunnelPort, snapshotter)
var tunnelServer portainer.TunnelServer = chisel.NewServer(*flags.TunnelAddr, *flags.TunnelPort)
err = tunnelServer.Start()
if err != nil {
log.Fatal(err)
}
var server portainer.Server = &http.Server{
ReverseTunnelService: reverseTunnelService,
Status: applicationStatus,
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
AuthDisabled: *flags.NoAuth,
EndpointManagement: endpointManagement,
RoleService: store.RoleService,
UserService: store.UserService,
TeamService: store.TeamService,
TeamMembershipService: store.TeamMembershipService,
EndpointService: store.EndpointService,
EndpointGroupService: store.EndpointGroupService,
ExtensionService: store.ExtensionService,
ResourceControlService: store.ResourceControlService,
SettingsService: store.SettingsService,
RegistryService: store.RegistryService,
DockerHubService: store.DockerHubService,
StackService: store.StackService,
ScheduleService: store.ScheduleService,
TagService: store.TagService,
TemplateService: store.TemplateService,
WebhookService: store.WebhookService,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
ExtensionManager: extensionManager,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
GitService: gitService,
SignatureService: digitalSignatureService,
JobScheduler: jobScheduler,
Snapshotter: snapshotter,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: clientFactory,
JobService: jobService,
TunnelServerFingerprint: tunnelServer.GetFingerprint(),
Status: applicationStatus,
BindAddress: *flags.Addr,
AssetsPath: *flags.Assets,
AuthDisabled: *flags.NoAuth,
EndpointManagement: endpointManagement,
RoleService: store.RoleService,
UserService: store.UserService,
TeamService: store.TeamService,
TeamMembershipService: store.TeamMembershipService,
EndpointService: store.EndpointService,
EndpointGroupService: store.EndpointGroupService,
ExtensionService: store.ExtensionService,
ResourceControlService: store.ResourceControlService,
SettingsService: store.SettingsService,
RegistryService: store.RegistryService,
DockerHubService: store.DockerHubService,
StackService: store.StackService,
ScheduleService: store.ScheduleService,
TagService: store.TagService,
TemplateService: store.TemplateService,
WebhookService: store.WebhookService,
SwarmStackManager: swarmStackManager,
ComposeStackManager: composeStackManager,
ExtensionManager: extensionManager,
CryptoService: cryptoService,
JWTService: jwtService,
FileService: fileService,
LDAPService: ldapService,
GitService: gitService,
SignatureService: digitalSignatureService,
JobScheduler: jobScheduler,
Snapshotter: snapshotter,
SSL: *flags.SSL,
SSLCert: *flags.SSLCert,
SSLKey: *flags.SSLKey,
DockerClientFactory: clientFactory,
JobService: jobService,
}
log.Printf("Starting Portainer %s on %s", portainer.APIVersion, *flags.Addr)
+1 -1
View File
@@ -53,7 +53,7 @@ func (runner *SnapshotJobRunner) Run() {
}
for _, endpoint := range endpoints {
if endpoint.Type == portainer.AzureEnvironment || endpoint.Type == portainer.EdgeAgentEnvironment {
if endpoint.Type == portainer.AzureEnvironment {
continue
}
+3 -4
View File
@@ -19,8 +19,7 @@ func NewJobScheduler() *JobScheduler {
// ScheduleJob schedules the execution of a job via a runner
func (scheduler *JobScheduler) ScheduleJob(runner portainer.JobRunner) error {
_, err := scheduler.cron.AddJob(runner.GetSchedule().CronExpression, runner)
return err
return scheduler.cron.AddJob(runner.GetSchedule().CronExpression, runner)
}
// UpdateSystemJobSchedule updates the first occurence of the specified
@@ -36,7 +35,7 @@ func (scheduler *JobScheduler) UpdateSystemJobSchedule(jobType portainer.JobType
for _, entry := range cronEntries {
if entry.Job.(portainer.JobRunner).GetSchedule().JobType == jobType {
_, err := newCron.AddJob(newCronExpression, entry.Job)
err := newCron.AddJob(newCronExpression, entry.Job)
if err != nil {
return err
}
@@ -70,7 +69,7 @@ func (scheduler *JobScheduler) UpdateJobSchedule(runner portainer.JobRunner) err
jobRunner = entry.Job
}
_, err := newCron.AddJob(runner.GetSchedule().CronExpression, jobRunner)
err := newCron.AddJob(runner.GetSchedule().CronExpression, jobRunner)
if err != nil {
return err
}
+1 -3
View File
@@ -8,8 +8,6 @@ import (
"encoding/base64"
"encoding/hex"
"math/big"
"github.com/portainer/libcrypto"
)
const (
@@ -113,7 +111,7 @@ func (service *ECDSAService) CreateSignature(message string) (string, error) {
message = service.secret
}
hash := libcrypto.HashFromBytes([]byte(message))
hash := HashFromBytes([]byte(message))
r := big.NewInt(0)
s := big.NewInt(0)
+10
View File
@@ -0,0 +1,10 @@
package crypto
import "crypto/md5"
// HashFromBytes returns the hash of the specified data
func HashFromBytes(data []byte) []byte {
digest := md5.New()
digest.Write(data)
return digest.Sum(nil)
}
+5 -33
View File
@@ -1,7 +1,6 @@
package docker
import (
"fmt"
"net/http"
"strings"
"time"
@@ -12,21 +11,18 @@ import (
)
const (
unsupportedEnvironmentType = portainer.Error("Environment not supported")
defaultDockerRequestTimeout = 60
unsupportedEnvironmentType = portainer.Error("Environment not supported")
)
// ClientFactory is used to create Docker clients
type ClientFactory struct {
signatureService portainer.DigitalSignatureService
reverseTunnelService portainer.ReverseTunnelService
signatureService portainer.DigitalSignatureService
}
// NewClientFactory returns a new instance of a ClientFactory
func NewClientFactory(signatureService portainer.DigitalSignatureService, reverseTunnelService portainer.ReverseTunnelService) *ClientFactory {
func NewClientFactory(signatureService portainer.DigitalSignatureService) *ClientFactory {
return &ClientFactory{
signatureService: signatureService,
reverseTunnelService: reverseTunnelService,
signatureService: signatureService,
}
}
@@ -38,8 +34,6 @@ func (factory *ClientFactory) CreateClient(endpoint *portainer.Endpoint, nodeNam
return nil, unsupportedEnvironmentType
} else if endpoint.Type == portainer.AgentOnDockerEnvironment {
return createAgentClient(endpoint, factory.signatureService, nodeName)
} else if endpoint.Type == portainer.EdgeAgentEnvironment {
return createEdgeClient(endpoint, factory.reverseTunnelService, nodeName)
}
if strings.HasPrefix(endpoint.URL, "unix://") || strings.HasPrefix(endpoint.URL, "npipe://") {
@@ -68,28 +62,6 @@ func createTCPClient(endpoint *portainer.Endpoint) (*client.Client, error) {
)
}
func createEdgeClient(endpoint *portainer.Endpoint, reverseTunnelService portainer.ReverseTunnelService, nodeName string) (*client.Client, error) {
httpCli, err := httpClient(endpoint)
if err != nil {
return nil, err
}
headers := map[string]string{}
if nodeName != "" {
headers[portainer.PortainerAgentTargetHeader] = nodeName
}
tunnel := reverseTunnelService.GetTunnelDetails(endpoint.ID)
endpointURL := fmt.Sprintf("http://localhost:%d", tunnel.Port)
return client.NewClientWithOpts(
client.WithHost(endpointURL),
client.WithVersion(portainer.SupportedDockerAPIVersion),
client.WithHTTPClient(httpCli),
client.WithHTTPHeaders(headers),
)
}
func createAgentClient(endpoint *portainer.Endpoint, signatureService portainer.DigitalSignatureService, nodeName string) (*client.Client, error) {
httpCli, err := httpClient(endpoint)
if err != nil {
@@ -131,6 +103,6 @@ func httpClient(endpoint *portainer.Endpoint) (*http.Client, error) {
return &http.Client{
Transport: transport,
Timeout: defaultDockerRequestTimeout * time.Second,
Timeout: 30 * time.Second,
}, nil
}
+9 -10
View File
@@ -2,7 +2,6 @@ package docker
import (
"context"
"log"
"time"
"github.com/docker/docker/api/types"
@@ -11,7 +10,7 @@ import (
"github.com/portainer/portainer/api"
)
func snapshot(cli *client.Client, endpoint *portainer.Endpoint) (*portainer.Snapshot, error) {
func snapshot(cli *client.Client) (*portainer.Snapshot, error) {
_, err := cli.Ping(context.Background())
if err != nil {
return nil, err
@@ -23,44 +22,44 @@ func snapshot(cli *client.Client, endpoint *portainer.Endpoint) (*portainer.Snap
err = snapshotInfo(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot engine information] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
if snapshot.Swarm {
err = snapshotSwarmServices(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot Swarm services] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
err = snapshotNodes(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot Swarm nodes] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
}
err = snapshotContainers(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot containers] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
err = snapshotImages(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot images] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
err = snapshotVolumes(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot volumes] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
err = snapshotNetworks(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot networks] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
err = snapshotVersion(snapshot, cli)
if err != nil {
log.Printf("[WARN] [docker,snapshot] [message: unable to snapshot engine version] [endpoint: %s] [err: %s]", endpoint.Name, err)
return nil, err
}
snapshot.Time = time.Now().Unix()
+1 -1
View File
@@ -24,5 +24,5 @@ func (snapshotter *Snapshotter) CreateSnapshot(endpoint *portainer.Endpoint) (*p
}
defer cli.Close()
return snapshot(cli, endpoint)
return snapshot(cli)
}
-2
View File
@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"errors"
"log"
"os/exec"
"path"
"runtime"
@@ -194,7 +193,6 @@ func validateLicense(binaryPath, licenseKey string) ([]string, error) {
err := licenseCheckProcess.Run()
if err != nil {
log.Printf("[DEBUG] [exec,extension] [message: unable to run extension process] [err: %s]", err)
return nil, errors.New("Invalid extension license key")
}
+15 -25
View File
@@ -3,7 +3,6 @@ package exec
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"path"
@@ -14,22 +13,20 @@ import (
// SwarmStackManager represents a service for managing stacks.
type SwarmStackManager struct {
binaryPath string
dataPath string
signatureService portainer.DigitalSignatureService
fileService portainer.FileService
reverseTunnelService portainer.ReverseTunnelService
binaryPath string
dataPath string
signatureService portainer.DigitalSignatureService
fileService portainer.FileService
}
// NewSwarmStackManager initializes a new SwarmStackManager service.
// It also updates the configuration of the Docker CLI binary.
func NewSwarmStackManager(binaryPath, dataPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService, reverseTunnelService portainer.ReverseTunnelService) (*SwarmStackManager, error) {
func NewSwarmStackManager(binaryPath, dataPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService) (*SwarmStackManager, error) {
manager := &SwarmStackManager{
binaryPath: binaryPath,
dataPath: dataPath,
signatureService: signatureService,
fileService: fileService,
reverseTunnelService: reverseTunnelService,
binaryPath: binaryPath,
dataPath: dataPath,
signatureService: signatureService,
fileService: fileService,
}
err := manager.updateDockerCLIConfiguration(dataPath)
@@ -42,7 +39,7 @@ func NewSwarmStackManager(binaryPath, dataPath string, signatureService portaine
// Login executes the docker login command against a list of registries (including DockerHub).
func (manager *SwarmStackManager) Login(dockerhub *portainer.DockerHub, registries []portainer.Registry, endpoint *portainer.Endpoint) {
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
for _, registry := range registries {
if registry.Authentication {
registryArgs := append(args, "login", "--username", registry.Username, "--password", registry.Password, registry.URL)
@@ -58,7 +55,7 @@ func (manager *SwarmStackManager) Login(dockerhub *portainer.DockerHub, registri
// Logout executes the docker logout command.
func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
args = append(args, "logout")
return runCommandAndCaptureStdErr(command, args, nil, "")
}
@@ -66,7 +63,7 @@ func (manager *SwarmStackManager) Logout(endpoint *portainer.Endpoint) error {
// Deploy executes the docker stack deploy command.
func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error {
stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint)
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
if prune {
args = append(args, "stack", "deploy", "--prune", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name)
@@ -85,7 +82,7 @@ func (manager *SwarmStackManager) Deploy(stack *portainer.Stack, prune bool, end
// Remove executes the docker stack rm command.
func (manager *SwarmStackManager) Remove(stack *portainer.Stack, endpoint *portainer.Endpoint) error {
command, args := manager.prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint)
args = append(args, "stack", "rm", stack.Name)
return runCommandAndCaptureStdErr(command, args, nil, "")
}
@@ -109,7 +106,7 @@ func runCommandAndCaptureStdErr(command string, args []string, env []string, wor
return nil
}
func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, dataPath string, endpoint *portainer.Endpoint) (string, []string) {
func prepareDockerCommandAndArgs(binaryPath, dataPath string, endpoint *portainer.Endpoint) (string, []string) {
// Assume Linux as a default
command := path.Join(binaryPath, "docker")
@@ -119,14 +116,7 @@ func (manager *SwarmStackManager) prepareDockerCommandAndArgs(binaryPath, dataPa
args := make([]string, 0)
args = append(args, "--config", dataPath)
endpointURL := endpoint.URL
if endpoint.Type == portainer.EdgeAgentEnvironment {
tunnel := manager.reverseTunnelService.GetTunnelDetails(endpoint.ID)
endpointURL = fmt.Sprintf("tcp://localhost:%d", tunnel.Port)
}
args = append(args, "-H", endpointURL)
args = append(args, "-H", endpoint.URL)
if endpoint.TLSConfig.TLS {
args = append(args, "--tls")
+5 -21
View File
@@ -1,37 +1,21 @@
package git
import (
"crypto/tls"
"net/http"
"net/url"
"strings"
"time"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/transport/client"
githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http"
)
// Service represents a service for managing Git.
type Service struct {
httpsCli *http.Client
}
type Service struct{}
// NewService initializes a new service.
func NewService() *Service {
httpsCli := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Timeout: 300 * time.Second,
}
func NewService(dataStorePath string) (*Service, error) {
service := &Service{}
client.InstallProtocol("https", githttp.NewClient(httpsCli))
return &Service{
httpsCli: httpsCli,
}
return service, nil
}
// ClonePublicRepository clones a public git repository using the specified URL in the specified
@@ -48,7 +32,7 @@ func (service *Service) ClonePrivateRepositoryWithBasicAuth(repositoryURL, refer
return cloneRepository(repositoryURL, referenceName, destination)
}
func cloneRepository(repositoryURL, referenceName, destination string) error {
func cloneRepository(repositoryURL, referenceName string, destination string) error {
options := &git.CloneOptions{
URL: repositoryURL,
}
+68 -7
View File
@@ -52,7 +52,7 @@ func (handler *Handler) authenticate(w http.ResponseWriter, r *http.Request) *ht
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a user with the specified username from the database", err}
}
if err == portainer.ErrObjectNotFound && (settings.AuthenticationMethod == portainer.AuthenticationInternal || settings.AuthenticationMethod == portainer.AuthenticationOAuth) {
if err == portainer.ErrObjectNotFound && settings.AuthenticationMethod == portainer.AuthenticationInternal {
return &httperror.HandlerError{http.StatusUnprocessableEntity, "Invalid credentials", portainer.ErrUnauthorized}
}
@@ -98,9 +98,25 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
}
user := &portainer.User{
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: map[portainer.Authorization]bool{
portainer.OperationPortainerDockerHubInspect: true,
portainer.OperationPortainerEndpointGroupList: true,
portainer.OperationPortainerEndpointList: true,
portainer.OperationPortainerEndpointInspect: true,
portainer.OperationPortainerEndpointExtensionAdd: true,
portainer.OperationPortainerEndpointExtensionRemove: true,
portainer.OperationPortainerExtensionList: true,
portainer.OperationPortainerMOTD: true,
portainer.OperationPortainerRegistryList: true,
portainer.OperationPortainerRegistryInspect: true,
portainer.OperationPortainerTeamList: true,
portainer.OperationPortainerTemplateList: true,
portainer.OperationPortainerTemplateInspect: true,
portainer.OperationPortainerUserList: true,
portainer.OperationPortainerUserMemberships: true,
},
}
err = handler.UserService.CreateUser(user)
@@ -118,14 +134,59 @@ func (handler *Handler) authenticateLDAPAndCreateUser(w http.ResponseWriter, use
func (handler *Handler) writeToken(w http.ResponseWriter, user *portainer.User) *httperror.HandlerError {
tokenData := &portainer.TokenData{
ID: user.ID,
Username: user.Username,
Role: user.Role,
ID: user.ID,
Username: user.Username,
Role: user.Role,
PortainerAuthorizations: user.PortainerAuthorizations,
}
_, err := handler.ExtensionService.Extension(portainer.RBACExtension)
if err == portainer.ErrObjectNotFound {
return handler.persistAndWriteToken(w, tokenData)
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a extension with the specified identifier inside the database", err}
}
endpointAuthorizations, err := handler.getAuthorizations(user)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve authorizations associated to the user", err}
}
tokenData.EndpointAuthorizations = endpointAuthorizations
return handler.persistAndWriteToken(w, tokenData)
}
func (handler *Handler) getAuthorizations(user *portainer.User) (portainer.EndpointAuthorizations, error) {
endpointAuthorizations := portainer.EndpointAuthorizations{}
if user.Role == portainer.AdministratorRole {
return endpointAuthorizations, nil
}
userMemberships, err := handler.TeamMembershipService.TeamMembershipsByUserID(user.ID)
if err != nil {
return endpointAuthorizations, err
}
endpoints, err := handler.EndpointService.Endpoints()
if err != nil {
return endpointAuthorizations, err
}
endpointGroups, err := handler.EndpointGroupService.EndpointGroups()
if err != nil {
return endpointAuthorizations, err
}
roles, err := handler.RoleService.Roles()
if err != nil {
return endpointAuthorizations, err
}
endpointAuthorizations = getUserEndpointAuthorizations(user, endpoints, endpointGroups, roles, userMemberships)
return endpointAuthorizations, nil
}
func (handler *Handler) persistAndWriteToken(w http.ResponseWriter, tokenData *portainer.TokenData) *httperror.HandlerError {
token, err := handler.JWTService.GenerateToken(tokenData)
if err != nil {
+19 -3
View File
@@ -111,9 +111,25 @@ func (handler *Handler) validateOAuth(w http.ResponseWriter, r *http.Request) *h
if user == nil {
user = &portainer.User{
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
Username: username,
Role: portainer.StandardUserRole,
PortainerAuthorizations: map[portainer.Authorization]bool{
portainer.OperationPortainerDockerHubInspect: true,
portainer.OperationPortainerEndpointGroupList: true,
portainer.OperationPortainerEndpointList: true,
portainer.OperationPortainerEndpointInspect: true,
portainer.OperationPortainerEndpointExtensionAdd: true,
portainer.OperationPortainerEndpointExtensionRemove: true,
portainer.OperationPortainerExtensionList: true,
portainer.OperationPortainerMOTD: true,
portainer.OperationPortainerRegistryList: true,
portainer.OperationPortainerRegistryInspect: true,
portainer.OperationPortainerTeamList: true,
portainer.OperationPortainerTemplateList: true,
portainer.OperationPortainerTemplateInspect: true,
portainer.OperationPortainerUserList: true,
portainer.OperationPortainerUserMemberships: true,
},
}
err = handler.UserService.CreateUser(user)
+122
View File
@@ -0,0 +1,122 @@
package auth
import portainer "github.com/portainer/portainer/api"
func getUserEndpointAuthorizations(user *portainer.User, endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, roles []portainer.Role, userMemberships []portainer.TeamMembership) portainer.EndpointAuthorizations {
endpointAuthorizations := make(portainer.EndpointAuthorizations)
groupUserAccessPolicies := map[portainer.EndpointGroupID]portainer.UserAccessPolicies{}
groupTeamAccessPolicies := map[portainer.EndpointGroupID]portainer.TeamAccessPolicies{}
for _, endpointGroup := range endpointGroups {
groupUserAccessPolicies[endpointGroup.ID] = endpointGroup.UserAccessPolicies
groupTeamAccessPolicies[endpointGroup.ID] = endpointGroup.TeamAccessPolicies
}
for _, endpoint := range endpoints {
authorizations := getAuthorizationsFromUserEndpointPolicy(user, &endpoint, roles)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
continue
}
authorizations = getAuthorizationsFromUserEndpointGroupPolicy(user, &endpoint, roles, groupUserAccessPolicies)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
continue
}
authorizations = getAuthorizationsFromTeamEndpointPolicies(userMemberships, &endpoint, roles)
if len(authorizations) > 0 {
endpointAuthorizations[endpoint.ID] = authorizations
continue
}
endpointAuthorizations[endpoint.ID] = getAuthorizationsFromTeamEndpointGroupPolicies(userMemberships, &endpoint, roles, groupTeamAccessPolicies)
}
return endpointAuthorizations
}
func getAuthorizationsFromUserEndpointPolicy(user *portainer.User, endpoint *portainer.Endpoint, roles []portainer.Role) portainer.Authorizations {
policyRoles := make([]portainer.RoleID, 0)
policy, ok := endpoint.UserAccessPolicies[user.ID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromUserEndpointGroupPolicy(user *portainer.User, endpoint *portainer.Endpoint, roles []portainer.Role, groupAccessPolicies map[portainer.EndpointGroupID]portainer.UserAccessPolicies) portainer.Authorizations {
policyRoles := make([]portainer.RoleID, 0)
policy, ok := groupAccessPolicies[endpoint.GroupID][user.ID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromTeamEndpointPolicies(memberships []portainer.TeamMembership, endpoint *portainer.Endpoint, roles []portainer.Role) portainer.Authorizations {
policyRoles := make([]portainer.RoleID, 0)
for _, membership := range memberships {
policy, ok := endpoint.TeamAccessPolicies[membership.TeamID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromTeamEndpointGroupPolicies(memberships []portainer.TeamMembership, endpoint *portainer.Endpoint, roles []portainer.Role, groupAccessPolicies map[portainer.EndpointGroupID]portainer.TeamAccessPolicies) portainer.Authorizations {
policyRoles := make([]portainer.RoleID, 0)
for _, membership := range memberships {
policy, ok := groupAccessPolicies[endpoint.GroupID][membership.TeamID]
if ok {
policyRoles = append(policyRoles, policy.RoleID)
}
}
return getAuthorizationsFromRoles(policyRoles, roles)
}
func getAuthorizationsFromRoles(roleIdentifiers []portainer.RoleID, roles []portainer.Role) portainer.Authorizations {
var roleAuthorizations []portainer.Authorizations
for _, id := range roleIdentifiers {
for _, role := range roles {
if role.ID == id {
roleAuthorizations = append(roleAuthorizations, role.Authorizations)
break
}
}
}
processedAuthorizations := make(portainer.Authorizations)
if len(roleAuthorizations) > 0 {
processedAuthorizations = roleAuthorizations[0]
for idx, authorizations := range roleAuthorizations {
if idx == 0 {
continue
}
processedAuthorizations = mergeAuthorizations(processedAuthorizations, authorizations)
}
}
return processedAuthorizations
}
func mergeAuthorizations(a, b portainer.Authorizations) portainer.Authorizations {
c := make(map[portainer.Authorization]bool)
for k := range b {
if _, ok := a[k]; ok {
c[k] = true
}
}
return c
}
+2 -2
View File
@@ -25,9 +25,9 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/dockerhub",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.dockerhubInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.dockerhubInspect))).Methods(http.MethodGet)
h.Handle("/dockerhub",
bouncer.AdminAccess(httperror.LoggerHandler(h.dockerhubUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.dockerhubUpdate))).Methods(http.MethodPut)
return h
}
@@ -53,17 +53,11 @@ func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
}
for _, id := range payload.AssociatedEndpoints {
for _, endpoint := range endpoints {
if endpoint.ID == id {
endpoint.GroupID = endpointGroup.ID
err := handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint", err}
}
break
for _, endpoint := range endpoints {
if endpoint.GroupID == portainer.EndpointGroupID(1) {
err = handler.checkForGroupAssignment(endpoint, endpointGroup.ID, payload.AssociatedEndpoints)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint", err}
}
}
}
@@ -37,10 +37,8 @@ func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
}
updateAuthorizations := false
for _, endpoint := range endpoints {
if endpoint.GroupID == portainer.EndpointGroupID(endpointGroupID) {
updateAuthorizations = true
endpoint.GroupID = portainer.EndpointGroupID(1)
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
if err != nil {
@@ -49,12 +47,5 @@ func (handler *Handler) endpointGroupDelete(w http.ResponseWriter, r *http.Reque
}
}
if updateAuthorizations {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
return response.Empty(w)
}
@@ -1,46 +0,0 @@
package endpointgroups
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
// PUT request on /api/endpoint_groups/:id/endpoints/:endpointId
func (handler *Handler) endpointGroupAddEndpoint(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint group identifier route variable", err}
}
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "endpointId")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
}
endpointGroup, err := handler.EndpointGroupService.EndpointGroup(portainer.EndpointGroupID(endpointGroupID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint group with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint group with the specified identifier inside the database", err}
}
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
endpoint.GroupID = endpointGroup.ID
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint changes inside the database", err}
}
return response.Empty(w)
}
@@ -1,46 +0,0 @@
package endpointgroups
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
// DELETE request on /api/endpoint_groups/:id/endpoints/:endpointId
func (handler *Handler) endpointGroupDeleteEndpoint(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointGroupID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint group identifier route variable", err}
}
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "endpointId")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
}
_, err = handler.EndpointGroupService.EndpointGroup(portainer.EndpointGroupID(endpointGroupID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint group with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint group with the specified identifier inside the database", err}
}
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
endpoint.GroupID = portainer.EndpointGroupID(1)
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint changes inside the database", err}
}
return response.Empty(w)
}
@@ -2,7 +2,6 @@ package endpointgroups
import (
"net/http"
"reflect"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@@ -11,11 +10,12 @@ import (
)
type endpointGroupUpdatePayload struct {
Name string
Description string
Tags []string
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
Name string
Description string
AssociatedEndpoints []portainer.EndpointID
Tags []string
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
}
func (payload *endpointGroupUpdatePayload) Validate(r *http.Request) error {
@@ -54,15 +54,12 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
endpointGroup.Tags = payload.Tags
}
updateAuthorizations := false
if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpointGroup.UserAccessPolicies) {
if payload.UserAccessPolicies != nil {
endpointGroup.UserAccessPolicies = payload.UserAccessPolicies
updateAuthorizations = true
}
if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpointGroup.TeamAccessPolicies) {
if payload.TeamAccessPolicies != nil {
endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies
updateAuthorizations = true
}
err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, endpointGroup)
@@ -70,10 +67,17 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint group changes inside the database", err}
}
if updateAuthorizations {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if payload.AssociatedEndpoints != nil {
endpoints, err := handler.EndpointService.Endpoints()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
}
for _, endpoint := range endpoints {
err = handler.updateEndpointGroup(endpoint, portainer.EndpointGroupID(endpointGroupID), payload.AssociatedEndpoints)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint", err}
}
}
}
+37 -10
View File
@@ -14,7 +14,6 @@ type Handler struct {
*mux.Router
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
AuthorizationService *portainer.AuthorizationService
}
// NewHandler creates a handler to manage endpoint group operations.
@@ -23,18 +22,46 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/endpoint_groups",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointGroupCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupCreate))).Methods(http.MethodPost)
h.Handle("/endpoint_groups",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.endpointGroupList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupList))).Methods(http.MethodGet)
h.Handle("/endpoint_groups/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointGroupInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupInspect))).Methods(http.MethodGet)
h.Handle("/endpoint_groups/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointGroupUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupUpdate))).Methods(http.MethodPut)
h.Handle("/endpoint_groups/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointGroupDelete))).Methods(http.MethodDelete)
h.Handle("/endpoint_groups/{id}/endpoints/{endpointId}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointGroupAddEndpoint))).Methods(http.MethodPut)
h.Handle("/endpoint_groups/{id}/endpoints/{endpointId}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointGroupDeleteEndpoint))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupDelete))).Methods(http.MethodDelete)
return h
}
func (handler *Handler) checkForGroupUnassignment(endpoint portainer.Endpoint, associatedEndpoints []portainer.EndpointID) error {
for _, id := range associatedEndpoints {
if id == endpoint.ID {
return nil
}
}
endpoint.GroupID = portainer.EndpointGroupID(1)
return handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
}
func (handler *Handler) checkForGroupAssignment(endpoint portainer.Endpoint, groupID portainer.EndpointGroupID, associatedEndpoints []portainer.EndpointID) error {
for _, id := range associatedEndpoints {
if id == endpoint.ID {
endpoint.GroupID = groupID
return handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
}
}
return nil
}
func (handler *Handler) updateEndpointGroup(endpoint portainer.Endpoint, groupID portainer.EndpointGroupID, associatedEndpoints []portainer.EndpointID) error {
if endpoint.GroupID == groupID {
return handler.checkForGroupUnassignment(endpoint, associatedEndpoints)
} else if endpoint.GroupID == portainer.EndpointGroupID(1) {
return handler.checkForGroupAssignment(endpoint, groupID, associatedEndpoints)
}
return nil
}
+6 -8
View File
@@ -11,11 +11,9 @@ import (
// Handler is the HTTP handler used to proxy requests to external APIs.
type Handler struct {
*mux.Router
requestBouncer *security.RequestBouncer
EndpointService portainer.EndpointService
SettingsService portainer.SettingsService
ProxyManager *proxy.Manager
ReverseTunnelService portainer.ReverseTunnelService
requestBouncer *security.RequestBouncer
EndpointService portainer.EndpointService
ProxyManager *proxy.Manager
}
// NewHandler creates a handler to proxy requests to external APIs.
@@ -25,10 +23,10 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
requestBouncer: bouncer,
}
h.PathPrefix("/{id}/azure").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToAzureAPI)))
bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToAzureAPI)))
h.PathPrefix("/{id}/docker").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToDockerAPI)))
h.PathPrefix("/{id}/storidge").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
return h
}
@@ -29,7 +29,7 @@ func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.R
}
var proxy http.Handler
proxy = handler.ProxyManager.GetProxy(endpoint)
proxy = handler.ProxyManager.GetProxy(string(endpointID))
if proxy == nil {
proxy, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
if err != nil {
+5 -26
View File
@@ -3,7 +3,6 @@ package endpointproxy
import (
"errors"
"strconv"
"time"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@@ -25,37 +24,17 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
if endpoint.Type != 4 && endpoint.Status == portainer.EndpointStatusDown {
return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to query endpoint", errors.New("Endpoint is down")}
}
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
if err != nil {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
}
if endpoint.Type == portainer.EdgeAgentEnvironment {
if endpoint.EdgeID == "" {
return &httperror.HandlerError{http.StatusInternalServerError, "No Edge agent registered with the endpoint", errors.New("No agent available")}
}
tunnel := handler.ReverseTunnelService.GetTunnelDetails(endpoint.ID)
if tunnel.Status == portainer.EdgeAgentIdle {
handler.ProxyManager.DeleteProxy(endpoint)
err := handler.ReverseTunnelService.SetTunnelStatusToRequired(endpoint.ID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update tunnel status", err}
}
settings, err := handler.SettingsService.Settings()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
}
waitForAgentToConnect := time.Duration(settings.EdgeAgentCheckinInterval) * time.Second
time.Sleep(waitForAgentToConnect * 2)
}
}
var proxy http.Handler
proxy = handler.ProxyManager.GetProxy(endpoint)
proxy = handler.ProxyManager.GetProxy(string(endpointID))
if proxy == nil {
proxy, err = handler.ProxyManager.CreateAndRegisterProxy(endpoint)
if err != nil {
+37 -46
View File
@@ -1,10 +1,10 @@
package endpoints
import (
"errors"
"net"
"encoding/base64"
"log"
"math/rand"
"net/http"
"net/url"
"runtime"
"strconv"
"strings"
@@ -192,38 +192,49 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
Snapshots: []portainer.Snapshot{},
}
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
err = handler.EndpointService.CreateEndpoint(endpoint)
if err != nil {
return nil, &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err}
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
}
return endpoint, nil
}
// TODO: relocate in a service
// must be unique (e.g. not used / referenced)
func randomInt(min, max int) int {
// should be randomize at service creation time?
// if not seeded, will always get same port order
// might not be a problem and maybe not required
//rand.Seed(time.Now().UnixNano())
return min + rand.Intn(max-min)
}
func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
endpointType := portainer.EdgeAgentEnvironment
endpointID := handler.EndpointService.GetNextIdentifier()
portainerURL, err := url.Parse(payload.URL)
if err != nil {
return nil, &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint URL", err}
// get random port
// Dynamic ports (also called private ports) are 49152 to 65535.
// TODO: register this port somewhere
portnumber := randomInt(49152, 65535)
keyInformation := []string{
strings.TrimPrefix(payload.URL, "tcp://"),
"8000",
handler.TunnelServerFingerprint,
strconv.Itoa(portnumber),
"agent:randomstring",
}
portainerHost, _, err := net.SplitHostPort(portainerURL.Host)
if err != nil {
portainerHost = portainerURL.Host
}
if portainerHost == "localhost" {
return nil, &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint URL", errors.New("cannot use localhost as endpoint URL")}
}
edgeKey := handler.ReverseTunnelService.GenerateEdgeKey(payload.URL, portainerHost, endpointID)
key := strings.Join(keyInformation, "|")
encodedKey := base64.RawStdEncoding.EncodeToString([]byte(key))
endpoint := &portainer.Endpoint{
ID: portainer.EndpointID(endpointID),
Name: payload.Name,
URL: portainerHost,
URL: "tcp://localhost:" + strconv.Itoa(portnumber),
Type: endpointType,
GroupID: portainer.EndpointGroupID(payload.GroupID),
TLSConfig: portainer.TLSConfiguration{
@@ -235,12 +246,12 @@ func (handler *Handler) createEdgeAgentEndpoint(payload *endpointCreatePayload)
Tags: payload.Tags,
Status: portainer.EndpointStatusUp,
Snapshots: []portainer.Snapshot{},
EdgeKey: edgeKey,
EdgeKey: string(encodedKey),
}
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
err := handler.EndpointService.CreateEndpoint(endpoint)
if err != nil {
return nil, &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err}
return nil, &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
}
return endpoint, nil
@@ -344,37 +355,17 @@ func (handler *Handler) snapshotAndPersistEndpoint(endpoint *portainer.Endpoint)
snapshot, err := handler.Snapshotter.CreateSnapshot(endpoint)
endpoint.Status = portainer.EndpointStatusUp
if err != nil {
if strings.Contains(err.Error(), "Invalid request signature") {
err = errors.New("agent already paired with another Portainer instance")
}
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to initiate communications with endpoint", err}
log.Printf("http error: endpoint snapshot error (endpoint=%s, URL=%s) (err=%s)\n", endpoint.Name, endpoint.URL, err)
endpoint.Status = portainer.EndpointStatusDown
}
if snapshot != nil {
endpoint.Snapshots = []portainer.Snapshot{*snapshot}
}
err = handler.saveEndpointAndUpdateAuthorizations(endpoint)
err = handler.EndpointService.CreateEndpoint(endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "An error occured while trying to create the endpoint", err}
}
return nil
}
func (handler *Handler) saveEndpointAndUpdateAuthorizations(endpoint *portainer.Endpoint) error {
err := handler.EndpointService.CreateEndpoint(endpoint)
if err != nil {
return err
}
group, err := handler.EndpointGroupService.EndpointGroup(endpoint.GroupID)
if err != nil {
return err
}
if len(group.UserAccessPolicies) > 0 || len(group.TeamAccessPolicies) > 0 {
return handler.AuthorizationService.UpdateUsersAuthorizations()
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint inside the database", err}
}
return nil
@@ -41,14 +41,7 @@ func (handler *Handler) endpointDelete(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove endpoint from the database", err}
}
handler.ProxyManager.DeleteProxy(endpoint)
if len(endpoint.UserAccessPolicies) > 0 || len(endpoint.TeamAccessPolicies) > 0 {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
handler.ProxyManager.DeleteProxy(string(endpointID))
return response.Empty(w)
}
+7 -130
View File
@@ -2,43 +2,24 @@ package endpoints
import (
"net/http"
"strconv"
"strings"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/libhttp/request"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api/http/security"
)
// GET request on /api/endpoints?(start=<start>)&(limit=<limit>)&(search=<search>)&(groupId=<groupId)
// GET request on /api/endpoints
func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
start, _ := request.RetrieveNumericQueryParameter(r, "start", true)
if start != 0 {
start--
endpoints, err := handler.EndpointService.Endpoints()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
}
search, _ := request.RetrieveQueryParameter(r, "search", true)
if search != "" {
search = strings.ToLower(search)
}
groupID, _ := request.RetrieveNumericQueryParameter(r, "groupId", true)
limit, _ := request.RetrieveNumericQueryParameter(r, "limit", true)
endpointGroups, err := handler.EndpointGroupService.EndpointGroups()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoint groups from the database", err}
}
endpoints, err := handler.EndpointService.Endpoints()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve endpoints from the database", err}
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
@@ -46,113 +27,9 @@ func (handler *Handler) endpointList(w http.ResponseWriter, r *http.Request) *ht
filteredEndpoints := security.FilterEndpoints(endpoints, endpointGroups, securityContext)
if groupID != 0 {
filteredEndpoints = filterEndpointsByGroupID(filteredEndpoints, portainer.EndpointGroupID(groupID))
for idx := range filteredEndpoints {
hideFields(&filteredEndpoints[idx])
}
if search != "" {
filteredEndpoints = filterEndpointsBySearchCriteria(filteredEndpoints, endpointGroups, search)
}
filteredEndpointCount := len(filteredEndpoints)
paginatedEndpoints := paginateEndpoints(filteredEndpoints, start, limit)
for idx := range paginatedEndpoints {
hideFields(&paginatedEndpoints[idx])
}
w.Header().Set("X-Total-Count", strconv.Itoa(filteredEndpointCount))
return response.JSON(w, paginatedEndpoints)
}
func paginateEndpoints(endpoints []portainer.Endpoint, start, limit int) []portainer.Endpoint {
if limit == 0 {
return endpoints
}
endpointCount := len(endpoints)
if start > endpointCount {
start = endpointCount
}
end := start + limit
if end > endpointCount {
end = endpointCount
}
return endpoints[start:end]
}
func filterEndpointsByGroupID(endpoints []portainer.Endpoint, endpointGroupID portainer.EndpointGroupID) []portainer.Endpoint {
filteredEndpoints := make([]portainer.Endpoint, 0)
for _, endpoint := range endpoints {
if endpoint.GroupID == endpointGroupID {
filteredEndpoints = append(filteredEndpoints, endpoint)
}
}
return filteredEndpoints
}
func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, searchCriteria string) []portainer.Endpoint {
filteredEndpoints := make([]portainer.Endpoint, 0)
for _, endpoint := range endpoints {
if endpointMatchSearchCriteria(&endpoint, searchCriteria) {
filteredEndpoints = append(filteredEndpoints, endpoint)
continue
}
if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, searchCriteria) {
filteredEndpoints = append(filteredEndpoints, endpoint)
}
}
return filteredEndpoints
}
func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, searchCriteria string) bool {
if strings.Contains(strings.ToLower(endpoint.Name), searchCriteria) {
return true
}
if strings.Contains(strings.ToLower(endpoint.URL), searchCriteria) {
return true
}
if endpoint.Status == portainer.EndpointStatusUp && searchCriteria == "up" {
return true
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
return true
}
for _, tag := range endpoint.Tags {
if strings.Contains(strings.ToLower(tag), searchCriteria) {
return true
}
}
return false
}
func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGroups []portainer.EndpointGroup, searchCriteria string) bool {
for _, group := range endpointGroups {
if group.ID == endpoint.GroupID {
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
return true
}
for _, tag := range group.Tags {
if strings.Contains(strings.ToLower(tag), searchCriteria) {
return true
}
}
}
}
return false
return response.JSON(w, filteredEndpoints)
}
@@ -1,77 +0,0 @@
package endpoints
import (
"errors"
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
type endpointStatusInspectResponse struct {
Status string `json:"status"`
Port int `json:"port"`
Schedules []portainer.EdgeSchedule `json:"schedules"`
CheckinInterval int `json:"checkin"`
Credentials string `json:"credentials"`
}
// GET request on /api/endpoints/:id/status
func (handler *Handler) endpointStatusInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
}
endpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(endpointID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
}
if endpoint.Type != portainer.EdgeAgentEnvironment {
return &httperror.HandlerError{http.StatusInternalServerError, "Status unavailable for non Edge agent endpoints", errors.New("Status unavailable")}
}
edgeIdentifier := r.Header.Get(portainer.PortainerAgentEdgeIDHeader)
if edgeIdentifier == "" {
return &httperror.HandlerError{http.StatusForbidden, "Missing Edge identifier", errors.New("missing Edge identifier")}
}
if endpoint.EdgeID != "" && endpoint.EdgeID != edgeIdentifier {
return &httperror.HandlerError{http.StatusForbidden, "Invalid Edge identifier", errors.New("invalid Edge identifier")}
}
if endpoint.EdgeID == "" {
endpoint.EdgeID = edgeIdentifier
err := handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to Unable to persist endpoint changes inside the database", err}
}
}
settings, err := handler.SettingsService.Settings()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve settings from the database", err}
}
tunnel := handler.ReverseTunnelService.GetTunnelDetails(endpoint.ID)
statusResponse := endpointStatusInspectResponse{
Status: tunnel.Status,
Port: tunnel.Port,
Schedules: tunnel.Schedules,
CheckinInterval: settings.EdgeAgentCheckinInterval,
Credentials: tunnel.Credentials,
}
if tunnel.Status == portainer.EdgeAgentManagementRequired {
handler.ReverseTunnelService.SetTunnelStatusToActive(endpoint.ID)
}
return response.JSON(w, statusResponse)
}
+2 -13
View File
@@ -2,7 +2,6 @@ package endpoints
import (
"net/http"
"reflect"
"strconv"
httperror "github.com/portainer/libhttp/error"
@@ -77,15 +76,12 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
endpoint.Tags = payload.Tags
}
updateAuthorizations := false
if payload.UserAccessPolicies != nil && !reflect.DeepEqual(payload.UserAccessPolicies, endpoint.UserAccessPolicies) {
if payload.UserAccessPolicies != nil {
endpoint.UserAccessPolicies = payload.UserAccessPolicies
updateAuthorizations = true
}
if payload.TeamAccessPolicies != nil && !reflect.DeepEqual(payload.TeamAccessPolicies, endpoint.TeamAccessPolicies) {
if payload.TeamAccessPolicies != nil {
endpoint.TeamAccessPolicies = payload.TeamAccessPolicies
updateAuthorizations = true
}
if payload.Status != nil {
@@ -177,12 +173,5 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint changes inside the database", err}
}
if updateAuthorizations {
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
}
return response.JSON(w, endpoint)
}
+12 -19
View File
@@ -19,9 +19,6 @@ const (
func hideFields(endpoint *portainer.Endpoint) {
endpoint.AzureCredentials = portainer.AzureCredentials{}
if len(endpoint.Snapshots) > 0 {
endpoint.Snapshots[0].SnapshotRaw = portainer.SnapshotRaw{}
}
}
// Handler is the HTTP handler used to handle endpoint operations.
@@ -35,9 +32,8 @@ type Handler struct {
ProxyManager *proxy.Manager
Snapshotter portainer.Snapshotter
JobService portainer.JobService
ReverseTunnelService portainer.ReverseTunnelService
SettingsService portainer.SettingsService
AuthorizationService *portainer.AuthorizationService
// TODO: figure out a way to manage this (service?)
TunnelServerFingerprint string
}
// NewHandler creates a handler to manage endpoint operations.
@@ -49,27 +45,24 @@ func NewHandler(bouncer *security.RequestBouncer, authorizeEndpointManagement bo
}
h.Handle("/endpoints",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointCreate))).Methods(http.MethodPost)
h.Handle("/endpoints/snapshot",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointSnapshots))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointSnapshots))).Methods(http.MethodPost)
h.Handle("/endpoints",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.endpointList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointList))).Methods(http.MethodGet)
h.Handle("/endpoints/{id}",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.endpointInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointInspect))).Methods(http.MethodGet)
h.Handle("/endpoints/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointUpdate))).Methods(http.MethodPut)
h.Handle("/endpoints/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointDelete))).Methods(http.MethodDelete)
h.Handle("/endpoints/{id}/extensions",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.endpointExtensionAdd))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointExtensionAdd))).Methods(http.MethodPost)
h.Handle("/endpoints/{id}/extensions/{extensionType}",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.endpointExtensionRemove))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointExtensionRemove))).Methods(http.MethodDelete)
h.Handle("/endpoints/{id}/job",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointJob))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointJob))).Methods(http.MethodPost)
h.Handle("/endpoints/{id}/snapshot",
bouncer.AdminAccess(httperror.LoggerHandler(h.endpointSnapshot))).Methods(http.MethodPost)
h.Handle("/endpoints/{id}/status",
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointStatusInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointSnapshot))).Methods(http.MethodPost)
return h
}
+5 -6
View File
@@ -17,7 +17,6 @@ type Handler struct {
EndpointGroupService portainer.EndpointGroupService
EndpointService portainer.EndpointService
RegistryService portainer.RegistryService
AuthorizationService *portainer.AuthorizationService
}
// NewHandler creates a handler to manage extension operations.
@@ -27,15 +26,15 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
}
h.Handle("/extensions",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
h.Handle("/extensions",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost)
h.Handle("/extensions/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionInspect))).Methods(http.MethodGet)
h.Handle("/extensions/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionDelete))).Methods(http.MethodDelete)
h.Handle("/extensions/{id}/update",
bouncer.AdminAccess(httperror.LoggerHandler(h.extensionUpdate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionUpdate))).Methods(http.MethodPost)
return h
}
+3 -5
View File
@@ -1,8 +1,6 @@
package extensions
import (
portainer "github.com/portainer/portainer/api"
)
import portainer "github.com/portainer/portainer/api"
func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) {
tmp := policies[key]
@@ -35,6 +33,7 @@ func (handler *Handler) upgradeRBACData() error {
if err != nil {
return err
}
}
endpoints, err := handler.EndpointService.Endpoints()
@@ -56,6 +55,5 @@ func (handler *Handler) upgradeRBACData() error {
return err
}
}
return handler.AuthorizationService.UpdateUsersAuthorizations()
return nil
}
-5
View File
@@ -4,8 +4,6 @@ import (
"net/http"
"strings"
"github.com/portainer/portainer/api/http/handler/support"
"github.com/portainer/portainer/api/http/handler/schedules"
"github.com/portainer/portainer/api/http/handler/roles"
@@ -50,7 +48,6 @@ type Handler struct {
SettingsHandler *settings.Handler
StackHandler *stacks.Handler
StatusHandler *status.Handler
SupportHandler *support.Handler
TagHandler *tags.Handler
TeamMembershipHandler *teammemberships.Handler
TeamHandler *teams.Handler
@@ -99,8 +96,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/api", h.StackHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/status"):
http.StripPrefix("/api", h.StatusHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/support"):
http.StripPrefix("/api", h.SupportHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/tags"):
http.StripPrefix("/api", h.TagHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/templates"):
+1 -1
View File
@@ -18,7 +18,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/motd",
bouncer.RestrictedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet)
return h
}
+2 -2
View File
@@ -5,9 +5,9 @@ import (
"net/http"
"strings"
"github.com/portainer/libcrypto"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/http/client"
)
@@ -42,7 +42,7 @@ func (handler *Handler) motd(w http.ResponseWriter, r *http.Request) {
message := strings.Join(data.Message, "\n")
hash := libcrypto.HashFromBytes([]byte(message))
hash := crypto.HashFromBytes([]byte(message))
resp := motdResponse{
Title: data.Title,
Message: message,
+7 -7
View File
@@ -33,19 +33,19 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
}
h.Handle("/registries",
bouncer.AdminAccess(httperror.LoggerHandler(h.registryCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryCreate))).Methods(http.MethodPost)
h.Handle("/registries",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.registryList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryList))).Methods(http.MethodGet)
h.Handle("/registries/{id}",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.registryInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryInspect))).Methods(http.MethodGet)
h.Handle("/registries/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.registryUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryUpdate))).Methods(http.MethodPut)
h.Handle("/registries/{id}/configure",
bouncer.AdminAccess(httperror.LoggerHandler(h.registryConfigure))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryConfigure))).Methods(http.MethodPost)
h.Handle("/registries/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
h.PathPrefix("/registries/{id}/v2").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToRegistryAPI)))
bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToRegistryAPI)))
return h
}
+30 -35
View File
@@ -3,6 +3,7 @@ package registries
import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
@@ -10,16 +11,19 @@ import (
)
type registryUpdatePayload struct {
Name *string
URL *string
Authentication *bool
Username *string
Password *string
Name string
URL string
Authentication bool
Username string
Password string
UserAccessPolicies portainer.UserAccessPolicies
TeamAccessPolicies portainer.TeamAccessPolicies
}
func (payload *registryUpdatePayload) Validate(r *http.Request) error {
if payload.Authentication && (govalidator.IsNull(payload.Username) || govalidator.IsNull(payload.Password)) {
return portainer.Error("Invalid credentials. Username and password must be specified when authentication is enabled")
}
return nil
}
@@ -43,41 +47,32 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
}
if payload.Name != nil {
registry.Name = *payload.Name
registries, err := handler.RegistryService.Registries()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
}
for _, r := range registries {
if r.URL == payload.URL && r.ID != registry.ID {
return &httperror.HandlerError{http.StatusConflict, "Another registry with the same URL already exists", portainer.ErrRegistryAlreadyExists}
}
}
if payload.URL != nil {
registries, err := handler.RegistryService.Registries()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve registries from the database", err}
}
for _, r := range registries {
if r.URL == *payload.URL && r.ID != registry.ID {
return &httperror.HandlerError{http.StatusConflict, "Another registry with the same URL already exists", portainer.ErrRegistryAlreadyExists}
}
}
registry.URL = *payload.URL
if payload.Name != "" {
registry.Name = payload.Name
}
if payload.Authentication != nil {
if *payload.Authentication {
registry.Authentication = true
if payload.URL != "" {
registry.URL = payload.URL
}
if payload.Username != nil {
registry.Username = *payload.Username
}
if payload.Password != nil {
registry.Password = *payload.Password
}
} else {
registry.Authentication = false
registry.Username = ""
registry.Password = ""
}
if payload.Authentication {
registry.Authentication = true
registry.Username = payload.Username
registry.Password = payload.Password
} else {
registry.Authentication = false
registry.Username = ""
registry.Password = ""
}
if payload.UserAccessPolicies != nil {
+3 -3
View File
@@ -21,11 +21,11 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/resource_controls",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.resourceControlCreate))).Methods(http.MethodPost)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.resourceControlCreate))).Methods(http.MethodPost)
h.Handle("/resource_controls/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.resourceControlUpdate))).Methods(http.MethodPut)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.resourceControlUpdate))).Methods(http.MethodPut)
h.Handle("/resource_controls/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.resourceControlDelete))).Methods(http.MethodDelete)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.resourceControlDelete))).Methods(http.MethodDelete)
return h
}
+1 -1
View File
@@ -21,7 +21,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/roles",
bouncer.AdminAccess(httperror.LoggerHandler(h.roleList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.roleList))).Methods(http.MethodGet)
return h
}
+13 -14
View File
@@ -12,13 +12,12 @@ import (
// Handler is the HTTP handler used to handle schedule operations.
type Handler struct {
*mux.Router
ScheduleService portainer.ScheduleService
EndpointService portainer.EndpointService
SettingsService portainer.SettingsService
FileService portainer.FileService
JobService portainer.JobService
JobScheduler portainer.JobScheduler
ReverseTunnelService portainer.ReverseTunnelService
ScheduleService portainer.ScheduleService
EndpointService portainer.EndpointService
SettingsService portainer.SettingsService
FileService portainer.FileService
JobService portainer.JobService
JobScheduler portainer.JobScheduler
}
// NewHandler creates a handler to manage schedule operations.
@@ -28,18 +27,18 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
}
h.Handle("/schedules",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleList))).Methods(http.MethodGet)
h.Handle("/schedules",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleCreate))).Methods(http.MethodPost)
h.Handle("/schedules/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleInspect))).Methods(http.MethodGet)
h.Handle("/schedules/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleUpdate))).Methods(http.MethodPut)
h.Handle("/schedules/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleDelete))).Methods(http.MethodDelete)
h.Handle("/schedules/{id}/file",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleFile))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleFile))).Methods(http.MethodGet)
h.Handle("/schedules/{id}/tasks",
bouncer.AdminAccess(httperror.LoggerHandler(h.scheduleTasks))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleTasks))).Methods(http.MethodGet)
return h
}
+1 -43
View File
@@ -1,11 +1,9 @@
package schedules
import (
"encoding/base64"
"errors"
"net/http"
"strconv"
"strings"
"time"
"github.com/asaskevich/govalidator"
@@ -115,7 +113,7 @@ func (payload *scheduleCreateFromFileContentPayload) Validate(r *http.Request) e
return nil
}
// POST /api/schedules?method=file|string
// POST /api/schedules?method=file/string
func (handler *Handler) scheduleCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
settings, err := handler.SettingsService.Settings()
if err != nil {
@@ -221,46 +219,6 @@ func (handler *Handler) createScheduleObjectFromFileContentPayload(payload *sche
}
func (handler *Handler) addAndPersistSchedule(schedule *portainer.Schedule, file []byte) error {
nonEdgeEndpointIDs := make([]portainer.EndpointID, 0)
edgeEndpointIDs := make([]portainer.EndpointID, 0)
edgeCronExpression := strings.Split(schedule.CronExpression, " ")
if len(edgeCronExpression) == 6 {
edgeCronExpression = edgeCronExpression[1:]
}
for _, ID := range schedule.ScriptExecutionJob.Endpoints {
endpoint, err := handler.EndpointService.Endpoint(ID)
if err != nil {
return err
}
if endpoint.Type != portainer.EdgeAgentEnvironment {
nonEdgeEndpointIDs = append(nonEdgeEndpointIDs, endpoint.ID)
} else {
edgeEndpointIDs = append(edgeEndpointIDs, endpoint.ID)
}
}
if len(edgeEndpointIDs) > 0 {
edgeSchedule := &portainer.EdgeSchedule{
ID: schedule.ID,
CronExpression: strings.Join(edgeCronExpression, " "),
Script: base64.RawStdEncoding.EncodeToString(file),
Endpoints: edgeEndpointIDs,
Version: 1,
}
for _, endpointID := range edgeEndpointIDs {
handler.ReverseTunnelService.AddSchedule(endpointID, edgeSchedule)
}
schedule.EdgeSchedule = edgeSchedule
}
schedule.ScriptExecutionJob.Endpoints = nonEdgeEndpointIDs
scriptPath, err := handler.FileService.StoreScheduledJobFileFromBytes(strconv.Itoa(int(schedule.ID)), file)
if err != nil {
return err
@@ -42,8 +42,6 @@ func (handler *Handler) scheduleDelete(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the files associated to the schedule on the filesystem", err}
}
handler.ReverseTunnelService.RemoveSchedule(schedule.ID)
handler.JobScheduler.UnscheduleJob(schedule.ID)
err = handler.ScheduleService.DeleteSchedule(portainer.ScheduleID(scheduleID))
@@ -3,7 +3,6 @@ package schedules
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
@@ -19,7 +18,6 @@ type taskContainer struct {
Status string `json:"Status"`
Created float64 `json:"Created"`
Labels map[string]string `json:"Labels"`
Edge bool `json:"Edge"`
}
// GET request on /api/schedules/:id/tasks
@@ -66,22 +64,6 @@ func (handler *Handler) scheduleTasks(w http.ResponseWriter, r *http.Request) *h
tasks = append(tasks, endpointTasks...)
}
if schedule.EdgeSchedule != nil {
for _, endpointID := range schedule.EdgeSchedule.Endpoints {
cronTask := taskContainer{
ID: fmt.Sprintf("schedule_%d", schedule.EdgeSchedule.ID),
EndpointID: endpointID,
Edge: true,
Status: "",
Created: 0,
Labels: map[string]string{},
}
tasks = append(tasks, cronTask)
}
}
return response.JSON(w, tasks)
}
@@ -105,7 +87,6 @@ func extractTasksFromContainerSnasphot(endpoint *portainer.Endpoint, scheduleID
for _, container := range containers {
if container.Labels["io.portainer.schedule.id"] == strconv.Itoa(int(scheduleID)) {
container.EndpointID = endpoint.ID
container.Edge = false
endpointTasks = append(endpointTasks, container)
}
}
+1 -50
View File
@@ -1,7 +1,6 @@
package schedules
import (
"encoding/base64"
"errors"
"net/http"
"strconv"
@@ -59,15 +58,7 @@ func (handler *Handler) scheduleUpdate(w http.ResponseWriter, r *http.Request) *
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a schedule with the specified identifier inside the database", err}
}
updateJobSchedule := false
if schedule.EdgeSchedule != nil {
err := handler.updateEdgeSchedule(schedule, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update Edge schedule", err}
}
} else {
updateJobSchedule = updateSchedule(schedule, &payload)
}
updateJobSchedule := updateSchedule(schedule, &payload)
if payload.FileContent != nil {
_, err := handler.FileService.StoreScheduledJobFileFromBytes(strconv.Itoa(scheduleID), []byte(*payload.FileContent))
@@ -94,46 +85,6 @@ func (handler *Handler) scheduleUpdate(w http.ResponseWriter, r *http.Request) *
return response.JSON(w, schedule)
}
func (handler *Handler) updateEdgeSchedule(schedule *portainer.Schedule, payload *scheduleUpdatePayload) error {
if payload.Name != nil {
schedule.Name = *payload.Name
}
if payload.Endpoints != nil {
edgeEndpointIDs := make([]portainer.EndpointID, 0)
for _, ID := range payload.Endpoints {
endpoint, err := handler.EndpointService.Endpoint(ID)
if err != nil {
return err
}
if endpoint.Type == portainer.EdgeAgentEnvironment {
edgeEndpointIDs = append(edgeEndpointIDs, endpoint.ID)
}
}
schedule.EdgeSchedule.Endpoints = edgeEndpointIDs
}
if payload.CronExpression != nil {
schedule.EdgeSchedule.CronExpression = *payload.CronExpression
schedule.EdgeSchedule.Version++
}
if payload.FileContent != nil {
schedule.EdgeSchedule.Script = base64.RawStdEncoding.EncodeToString([]byte(*payload.FileContent))
schedule.EdgeSchedule.Version++
}
for _, endpointID := range schedule.EdgeSchedule.Endpoints {
handler.ReverseTunnelService.AddSchedule(endpointID, schedule.EdgeSchedule)
}
return nil
}
func updateSchedule(schedule *portainer.Schedule, payload *scheduleUpdatePayload) bool {
updateJobSchedule := false
+8 -11
View File
@@ -17,14 +17,11 @@ 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
RoleService portainer.RoleService
ExtensionService portainer.ExtensionService
AuthorizationService *portainer.AuthorizationService
SettingsService portainer.SettingsService
LDAPService portainer.LDAPService
FileService portainer.FileService
JobScheduler portainer.JobScheduler
ScheduleService portainer.ScheduleService
}
// NewHandler creates a handler to manage settings operations.
@@ -33,13 +30,13 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/settings",
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.settingsInspect))).Methods(http.MethodGet)
h.Handle("/settings",
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.settingsUpdate))).Methods(http.MethodPut)
h.Handle("/settings/public",
bouncer.PublicAccess(httperror.LoggerHandler(h.settingsPublic))).Methods(http.MethodGet)
h.Handle("/settings/authentication/checkLDAP",
bouncer.AdminAccess(httperror.LoggerHandler(h.settingsLDAPCheck))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.settingsLDAPCheck))).Methods(http.MethodPut)
return h
}
@@ -14,7 +14,6 @@ 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"`
@@ -32,7 +31,6 @@ 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",
@@ -19,11 +19,9 @@ type settingsUpdatePayload struct {
OAuthSettings *portainer.OAuthSettings
AllowBindMountsForRegularUsers *bool
AllowPrivilegedModeForRegularUsers *bool
AllowVolumeBrowserForRegularUsers *bool
EnableHostManagementFeatures *bool
SnapshotInterval *string
TemplatesURL *string
EdgeAgentCheckinInterval *int
}
func (payload *settingsUpdatePayload) Validate(r *http.Request) error {
@@ -94,12 +92,6 @@ 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
}
@@ -111,10 +103,6 @@ func (handler *Handler) settingsUpdate(w http.ResponseWriter, r *http.Request) *
}
}
if payload.EdgeAgentCheckinInterval != nil {
settings.EdgeAgentCheckinInterval = *payload.EdgeAgentCheckinInterval
}
tlsError := handler.updateTLS(settings)
if tlsError != nil {
return tlsError
@@ -125,37 +113,9 @@ 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
@@ -1,9 +1,7 @@
package stacks
import (
"errors"
"net/http"
"path"
"strconv"
"strings"
@@ -240,7 +238,7 @@ func (handler *Handler) createComposeStackFromFileUpload(w http.ResponseWriter,
}
stackFolder := strconv.Itoa(int(stack.ID))
projectPath, err := handler.FileService.StoreStackFileFromBytes(stackFolder, stack.EntryPoint, payload.StackFileContent)
projectPath, err := handler.FileService.StoreStackFileFromBytes(stackFolder, stack.EntryPoint, []byte(payload.StackFileContent))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist Compose file on disk", err}
}
@@ -273,7 +271,6 @@ type composeStackDeploymentConfig struct {
endpoint *portainer.Endpoint
dockerhub *portainer.DockerHub
registries []portainer.Registry
isAdmin bool
}
func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint) (*composeStackDeploymentConfig, *httperror.HandlerError) {
@@ -298,7 +295,6 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai
endpoint: endpoint,
dockerhub: dockerhub,
registries: filteredRegistries,
isAdmin: securityContext.IsAdmin,
}
return config, nil
@@ -310,34 +306,12 @@ func (handler *Handler) createComposeDeployConfig(r *http.Request, stack *portai
// clean it. Hence the use of the mutex.
// We should contribute to libcompose to support authentication without using the config.json file.
func (handler *Handler) deployComposeStack(config *composeStackDeploymentConfig) error {
settings, err := handler.SettingsService.Settings()
if err != nil {
return err
}
if !settings.AllowBindMountsForRegularUsers && !config.isAdmin {
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
if err != nil {
return err
}
valid, err := handler.isValidStackFile(stackContent)
if err != nil {
return err
}
if !valid {
return errors.New("bind-mount disabled for non administrator users")
}
}
handler.stackCreationMutex.Lock()
defer handler.stackCreationMutex.Unlock()
handler.SwarmStackManager.Login(config.dockerhub, config.registries, config.endpoint)
err = handler.ComposeStackManager.Up(config.stack, config.endpoint)
err := handler.ComposeStackManager.Up(config.stack, config.endpoint)
if err != nil {
return err
}
+1 -27
View File
@@ -1,9 +1,7 @@
package stacks
import (
"errors"
"net/http"
"path"
"strconv"
"strings"
@@ -292,7 +290,6 @@ type swarmStackDeploymentConfig struct {
dockerhub *portainer.DockerHub
registries []portainer.Registry
prune bool
isAdmin bool
}
func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portainer.Stack, endpoint *portainer.Endpoint, prune bool) (*swarmStackDeploymentConfig, *httperror.HandlerError) {
@@ -318,41 +315,18 @@ func (handler *Handler) createSwarmDeployConfig(r *http.Request, stack *portaine
dockerhub: dockerhub,
registries: filteredRegistries,
prune: prune,
isAdmin: securityContext.IsAdmin,
}
return config, nil
}
func (handler *Handler) deploySwarmStack(config *swarmStackDeploymentConfig) error {
settings, err := handler.SettingsService.Settings()
if err != nil {
return err
}
if !settings.AllowBindMountsForRegularUsers && !config.isAdmin {
composeFilePath := path.Join(config.stack.ProjectPath, config.stack.EntryPoint)
stackContent, err := handler.FileService.GetFileContent(composeFilePath)
if err != nil {
return err
}
valid, err := handler.isValidStackFile(stackContent)
if err != nil {
return err
}
if !valid {
return errors.New("bind-mount disabled for non administrator users")
}
}
handler.stackCreationMutex.Lock()
defer handler.stackCreationMutex.Unlock()
handler.SwarmStackManager.Login(config.dockerhub, config.registries, config.endpoint)
err = handler.SwarmStackManager.Deploy(config.stack, config.prune, config.endpoint)
err := handler.SwarmStackManager.Deploy(config.stack, config.prune, config.endpoint)
if err != nil {
return err
}
+7 -8
View File
@@ -25,7 +25,6 @@ type Handler struct {
DockerHubService portainer.DockerHubService
SwarmStackManager portainer.SwarmStackManager
ComposeStackManager portainer.ComposeStackManager
SettingsService portainer.SettingsService
}
// NewHandler creates a handler to manage stack operations.
@@ -37,18 +36,18 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
requestBouncer: bouncer,
}
h.Handle("/stacks",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackCreate))).Methods(http.MethodPost)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackCreate))).Methods(http.MethodPost)
h.Handle("/stacks",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackList))).Methods(http.MethodGet)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackList))).Methods(http.MethodGet)
h.Handle("/stacks/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackInspect))).Methods(http.MethodGet)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackInspect))).Methods(http.MethodGet)
h.Handle("/stacks/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackDelete))).Methods(http.MethodDelete)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackDelete))).Methods(http.MethodDelete)
h.Handle("/stacks/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackUpdate))).Methods(http.MethodPut)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackUpdate))).Methods(http.MethodPut)
h.Handle("/stacks/{id}/file",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackFile))).Methods(http.MethodGet)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackFile))).Methods(http.MethodGet)
h.Handle("/stacks/{id}/migrate",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.stackMigrate))).Methods(http.MethodPost)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.stackMigrate))).Methods(http.MethodPost)
return h
}
-38
View File
@@ -5,9 +5,6 @@ import (
"log"
"net/http"
"github.com/docker/cli/cli/compose/types"
"github.com/docker/cli/cli/compose/loader"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/portainer/api"
@@ -90,38 +87,3 @@ func (handler *Handler) createSwarmStack(w http.ResponseWriter, r *http.Request,
return &httperror.HandlerError{http.StatusBadRequest, "Invalid value for query parameter: method. Value must be one of: string, repository or file", errors.New(request.ErrInvalidQueryParameter)}
}
func (handler *Handler) isValidStackFile(stackFileContent []byte) (bool, error) {
composeConfigYAML, err := loader.ParseYAML(stackFileContent)
if err != nil {
return false, err
}
composeConfigFile := types.ConfigFile{
Config: composeConfigYAML,
}
composeConfigDetails := types.ConfigDetails{
ConfigFiles: []types.ConfigFile{composeConfigFile},
Environment: map[string]string{},
}
composeConfig, err := loader.Load(composeConfigDetails, func(options *loader.Options) {
options.SkipValidation = true
options.SkipInterpolation = true
})
if err != nil {
return false, err
}
for key := range composeConfig.Services {
service := composeConfig.Services[key]
for _, volume := range service.Volumes {
if volume.Type == "bind" {
return false, nil
}
}
}
return true, nil
}
-2
View File
@@ -23,8 +23,6 @@ func NewHandler(bouncer *security.RequestBouncer, status *portainer.Status) *Han
}
h.Handle("/status",
bouncer.PublicAccess(httperror.LoggerHandler(h.statusInspect))).Methods(http.MethodGet)
h.Handle("/status/version",
bouncer.AuthenticatedAccess(http.HandlerFunc(h.statusInspectVersion))).Methods(http.MethodGet)
return h
}
@@ -1,51 +0,0 @@
package status
import (
"encoding/json"
"net/http"
"github.com/coreos/go-semver/semver"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/libhttp/response"
)
type inspectVersionResponse struct {
UpdateAvailable bool `json:"UpdateAvailable"`
LatestVersion string `json:"LatestVersion"`
}
type githubData struct {
TagName string `json:"tag_name"`
}
// GET request on /api/status/version
func (handler *Handler) statusInspectVersion(w http.ResponseWriter, r *http.Request) {
motd, err := client.Get(portainer.VersionCheckURL, 5)
if err != nil {
response.JSON(w, &inspectVersionResponse{UpdateAvailable: false})
return
}
var data githubData
err = json.Unmarshal(motd, &data)
if err != nil {
response.JSON(w, &inspectVersionResponse{UpdateAvailable: false})
return
}
resp := inspectVersionResponse{
UpdateAvailable: false,
}
currentVersion := semver.New(portainer.APIVersion)
latestVersion := semver.New(data.TagName)
if currentVersion.LessThan(*latestVersion) {
resp.UpdateAvailable = true
resp.LatestVersion = data.TagName
}
response.JSON(w, &resp)
}
-26
View File
@@ -1,26 +0,0 @@
package support
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/gorilla/mux"
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle support operations.
type Handler struct {
*mux.Router
}
// NewHandler returns a new Handler
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/support",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.supportList))).Methods(http.MethodGet)
return h
}
-39
View File
@@ -1,39 +0,0 @@
package support
import (
"encoding/json"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"net/http"
"github.com/portainer/portainer/api/http/client"
"github.com/portainer/libhttp/response"
)
type supportProduct struct {
ID int `json:"Id"`
Name string `json:"Name"`
ShortDescription string `json:"ShortDescription"`
Price string `json:"Price"`
PriceDescription string `json:"PriceDescription"`
Description string `json:"Description"`
ProductID string `json:"ProductId"`
}
func (handler *Handler) supportList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
supportData, err := client.Get(portainer.SupportProductsURL, 30)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to fetch support options", err}
}
var supportProducts []supportProduct
err = json.Unmarshal(supportData, &supportProducts)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to fetch support options", err}
}
return response.JSON(w, supportProducts)
}
+3 -3
View File
@@ -21,11 +21,11 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/tags",
bouncer.AdminAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
h.Handle("/tags",
bouncer.AdminAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
h.Handle("/tags/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
return h
}
+6 -6
View File
@@ -13,8 +13,8 @@ import (
// Handler is the HTTP handler used to handle team membership operations.
type Handler struct {
*mux.Router
TeamMembershipService portainer.TeamMembershipService
AuthorizationService *portainer.AuthorizationService
TeamMembershipService portainer.TeamMembershipService
ResourceControlService portainer.ResourceControlService
}
// NewHandler creates a handler to manage team membership operations.
@@ -23,13 +23,13 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/team_memberships",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamMembershipCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipCreate))).Methods(http.MethodPost)
h.Handle("/team_memberships",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamMembershipList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipList))).Methods(http.MethodGet)
h.Handle("/team_memberships/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamMembershipUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipUpdate))).Methods(http.MethodPut)
h.Handle("/team_memberships/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamMembershipDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipDelete))).Methods(http.MethodDelete)
return h
}
@@ -70,10 +70,5 @@ func (handler *Handler) teamMembershipCreate(w http.ResponseWriter, r *http.Requ
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist team memberships inside the database", err}
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
return response.JSON(w, membership)
}
@@ -38,10 +38,5 @@ func (handler *Handler) teamMembershipDelete(w http.ResponseWriter, r *http.Requ
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the team membership from the database", err}
}
err = handler.AuthorizationService.UpdateUsersAuthorizations()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update user authorizations", err}
}
return response.Empty(w)
}
+9 -9
View File
@@ -12,9 +12,9 @@ import (
// Handler is the HTTP handler used to handle team operations.
type Handler struct {
*mux.Router
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
AuthorizationService *portainer.AuthorizationService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
ResourceControlService portainer.ResourceControlService
}
// NewHandler creates a handler to manage team operations.
@@ -23,17 +23,17 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/teams",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamCreate))).Methods(http.MethodPost)
h.Handle("/teams",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamList))).Methods(http.MethodGet)
h.Handle("/teams/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamInspect))).Methods(http.MethodGet)
h.Handle("/teams/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamUpdate))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamUpdate))).Methods(http.MethodPut)
h.Handle("/teams/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamDelete))).Methods(http.MethodDelete)
h.Handle("/teams/{id}/memberships",
bouncer.AdminAccess(httperror.LoggerHandler(h.teamMemberships))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMemberships))).Methods(http.MethodGet)
return h
}
-5
View File
@@ -33,10 +33,5 @@ func (handler *Handler) teamDelete(w http.ResponseWriter, r *http.Request) *http
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to delete associated team memberships from the database", err}
}
err = handler.AuthorizationService.RemoveTeamAccessPolicies(portainer.TeamID(teamID))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clean-up team access policies", err}
}
return response.Empty(w)
}
+5 -5
View File
@@ -27,15 +27,15 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
}
h.Handle("/templates",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.templateList))).Methods(http.MethodGet)
h.Handle("/templates",
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateCreate)))).Methods(http.MethodPost)
h.Handle("/templates/{id}",
bouncer.RestrictedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateInspect)))).Methods(http.MethodGet)
h.Handle("/templates/{id}",
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut)
bouncer.AuthorizedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateUpdate)))).Methods(http.MethodPut)
h.Handle("/templates/{id}",
bouncer.AdminAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
return h
}
+1 -1
View File
@@ -22,6 +22,6 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/upload/tls/{certificate:(?:ca|cert|key)}",
bouncer.AdminAccess(httperror.LoggerHandler(h.uploadTLS))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.uploadTLS))).Methods(http.MethodPost)
return h
}
+19 -3
View File
@@ -43,9 +43,25 @@ func (handler *Handler) adminInit(w http.ResponseWriter, r *http.Request) *httpe
}
user := &portainer.User{
Username: payload.Username,
Role: portainer.AdministratorRole,
PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
Username: payload.Username,
Role: portainer.AdministratorRole,
PortainerAuthorizations: map[portainer.Authorization]bool{
portainer.OperationPortainerDockerHubInspect: true,
portainer.OperationPortainerEndpointGroupList: true,
portainer.OperationPortainerEndpointList: true,
portainer.OperationPortainerEndpointInspect: true,
portainer.OperationPortainerEndpointExtensionAdd: true,
portainer.OperationPortainerEndpointExtensionRemove: true,
portainer.OperationPortainerExtensionList: true,
portainer.OperationPortainerMOTD: true,
portainer.OperationPortainerRegistryList: true,
portainer.OperationPortainerRegistryInspect: true,
portainer.OperationPortainerTeamList: true,
portainer.OperationPortainerTemplateList: true,
portainer.OperationPortainerTemplateInspect: true,
portainer.OperationPortainerUserList: true,
portainer.OperationPortainerUserMemberships: true,
},
}
user.Password, err = handler.CryptoService.Hash(payload.Password)
+6 -7
View File
@@ -23,7 +23,6 @@ type Handler struct {
ResourceControlService portainer.ResourceControlService
CryptoService portainer.CryptoService
SettingsService portainer.SettingsService
AuthorizationService *portainer.AuthorizationService
}
// NewHandler creates a handler to manage user operations.
@@ -32,19 +31,19 @@ func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimi
Router: mux.NewRouter(),
}
h.Handle("/users",
bouncer.AdminAccess(httperror.LoggerHandler(h.userCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.userCreate))).Methods(http.MethodPost)
h.Handle("/users",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.userList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.userList))).Methods(http.MethodGet)
h.Handle("/users/{id}",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.userInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.userInspect))).Methods(http.MethodGet)
h.Handle("/users/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.userUpdate))).Methods(http.MethodPut)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.userUpdate))).Methods(http.MethodPut)
h.Handle("/users/{id}",
bouncer.AdminAccess(httperror.LoggerHandler(h.userDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.userDelete))).Methods(http.MethodDelete)
h.Handle("/users/{id}/memberships",
bouncer.RestrictedAccess(httperror.LoggerHandler(h.userMemberships))).Methods(http.MethodGet)
h.Handle("/users/{id}/passwd",
rateLimiter.LimitAccess(bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.userUpdatePassword)))).Methods(http.MethodPut)
rateLimiter.LimitAccess(bouncer.RestrictedAccess(httperror.LoggerHandler(h.userUpdatePassword)))).Methods(http.MethodPut)
h.Handle("/users/admin/check",
bouncer.PublicAccess(httperror.LoggerHandler(h.adminCheck))).Methods(http.MethodGet)
h.Handle("/users/admin/init",
+19 -3
View File
@@ -58,9 +58,25 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http
}
user = &portainer.User{
Username: payload.Username,
Role: portainer.UserRole(payload.Role),
PortainerAuthorizations: portainer.DefaultPortainerAuthorizations(),
Username: payload.Username,
Role: portainer.UserRole(payload.Role),
PortainerAuthorizations: map[portainer.Authorization]bool{
portainer.OperationPortainerDockerHubInspect: true,
portainer.OperationPortainerEndpointGroupList: true,
portainer.OperationPortainerEndpointList: true,
portainer.OperationPortainerEndpointInspect: true,
portainer.OperationPortainerEndpointExtensionAdd: true,
portainer.OperationPortainerEndpointExtensionRemove: true,
portainer.OperationPortainerExtensionList: true,
portainer.OperationPortainerMOTD: true,
portainer.OperationPortainerRegistryList: true,
portainer.OperationPortainerRegistryInspect: true,
portainer.OperationPortainerTeamList: true,
portainer.OperationPortainerTemplateList: true,
portainer.OperationPortainerTemplateInspect: true,
portainer.OperationPortainerUserList: true,
portainer.OperationPortainerUserMemberships: true,
},
}
settings, err := handler.SettingsService.Settings()
+2 -7
View File
@@ -65,20 +65,15 @@ func (handler *Handler) deleteAdminUser(w http.ResponseWriter, user *portainer.U
}
func (handler *Handler) deleteUser(w http.ResponseWriter, user *portainer.User) *httperror.HandlerError {
err := handler.UserService.DeleteUser(user.ID)
err := handler.UserService.DeleteUser(portainer.UserID(user.ID))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove user from the database", err}
}
err = handler.TeamMembershipService.DeleteTeamMembershipByUserID(user.ID)
err = handler.TeamMembershipService.DeleteTeamMembershipByUserID(portainer.UserID(user.ID))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove user memberships from the database", err}
}
err = handler.AuthorizationService.RemoveUserAccessPolicies(user.ID)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to clean-up user access policies", err}
}
return response.Empty(w)
}
-11
View File
@@ -3,8 +3,6 @@ package users
import (
"net/http"
"github.com/portainer/portainer/api/http/security"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
@@ -18,15 +16,6 @@ func (handler *Handler) userInspect(w http.ResponseWriter, r *http.Request) *htt
return &httperror.HandlerError{http.StatusBadRequest, "Invalid user identifier route variable", err}
}
securityContext, err := security.RetrieveRestrictedRequestContext(r)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve info from request context", err}
}
if !securityContext.IsAdmin && securityContext.UserID != portainer.UserID(userID) {
return &httperror.HandlerError{http.StatusForbidden, "Permission denied inspect user", portainer.ErrResourceAccessDenied}
}
user, err := handler.UserService.User(portainer.UserID(userID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a user with the specified identifier inside the database", err}
+3 -3
View File
@@ -24,11 +24,11 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/webhooks",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookCreate))).Methods(http.MethodPost)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.webhookCreate))).Methods(http.MethodPost)
h.Handle("/webhooks",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookList))).Methods(http.MethodGet)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.webhookList))).Methods(http.MethodGet)
h.Handle("/webhooks/{id}",
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.webhookDelete))).Methods(http.MethodDelete)
bouncer.RestrictedAccess(httperror.LoggerHandler(h.webhookDelete))).Methods(http.MethodDelete)
h.Handle("/webhooks/{token}",
bouncer.PublicAccess(httperror.LoggerHandler(h.webhookExecute))).Methods(http.MethodPost)
return h
+2 -4
View File
@@ -62,10 +62,8 @@ func (handler *Handler) handleAttachRequest(w http.ResponseWriter, r *http.Reque
r.Header.Del("Origin")
if params.endpoint.Type == portainer.AgentOnDockerEnvironment {
return handler.proxyAgentWebsocketRequest(w, r, params)
} else if params.endpoint.Type == portainer.EdgeAgentEnvironment {
return handler.proxyEdgeAgentWebsocketRequest(w, r, params)
if params.nodeName != "" || params.endpoint.Type == portainer.AgentOnDockerEnvironment {
return handler.proxyWebsocketRequest(w, r, params)
}
websocketConn, err := handler.connectionUpgrader.Upgrade(w, r, nil)
+2 -4
View File
@@ -68,10 +68,8 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h
func (handler *Handler) handleExecRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
r.Header.Del("Origin")
if params.endpoint.Type == portainer.AgentOnDockerEnvironment {
return handler.proxyAgentWebsocketRequest(w, r, params)
} else if params.endpoint.Type == portainer.EdgeAgentEnvironment {
return handler.proxyEdgeAgentWebsocketRequest(w, r, params)
if params.nodeName != "" || (params.endpoint.Type == portainer.AgentOnDockerEnvironment || params.endpoint.Type == portainer.EdgeAgentEnvironment) {
return handler.proxyWebsocketRequest(w, r, params)
}
websocketConn, err := handler.connectionUpgrader.Upgrade(w, r, nil)
+6 -7
View File
@@ -11,11 +11,10 @@ import (
// Handler is the HTTP handler used to handle websocket operations.
type Handler struct {
*mux.Router
EndpointService portainer.EndpointService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
requestBouncer *security.RequestBouncer
connectionUpgrader websocket.Upgrader
EndpointService portainer.EndpointService
SignatureService portainer.DigitalSignatureService
requestBouncer *security.RequestBouncer
connectionUpgrader websocket.Upgrader
}
// NewHandler creates a handler to manage websocket operations.
@@ -26,8 +25,8 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
requestBouncer: bouncer,
}
h.PathPrefix("/websocket/exec").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.websocketExec)))
bouncer.RestrictedAccess(httperror.LoggerHandler(h.websocketExec)))
h.PathPrefix("/websocket/attach").Handler(
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.websocketAttach)))
bouncer.RestrictedAccess(httperror.LoggerHandler(h.websocketAttach)))
return h
}
+3 -26
View File
@@ -2,37 +2,14 @@ package websocket
import (
"crypto/tls"
"fmt"
"net/http"
"net/url"
"github.com/gorilla/websocket"
"github.com/koding/websocketproxy"
"github.com/portainer/portainer/api"
"net/http"
"net/url"
)
func (handler *Handler) proxyEdgeAgentWebsocketRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
tunnel := handler.ReverseTunnelService.GetTunnelDetails(params.endpoint.ID)
endpointURL, err := url.Parse(fmt.Sprintf("http://localhost:%d", tunnel.Port))
if err != nil {
return err
}
endpointURL.Scheme = "ws"
proxy := websocketproxy.NewProxy(endpointURL)
proxy.Director = func(incoming *http.Request, out http.Header) {
out.Set(portainer.PortainerAgentTargetHeader, params.nodeName)
}
handler.ReverseTunnelService.SetTunnelStatusToActive(params.endpoint.ID)
proxy.ServeHTTP(w, r)
return nil
}
func (handler *Handler) proxyAgentWebsocketRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
func (handler *Handler) proxyWebsocketRequest(w http.ResponseWriter, r *http.Request, params *webSocketRequestParams) error {
agentURL, err := url.Parse(params.endpoint.URL)
if err != nil {
return err
+2 -97
View File
@@ -19,16 +19,12 @@ type (
dockerTransport *http.Transport
enableSignature bool
ResourceControlService portainer.ResourceControlService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
SettingsService portainer.SettingsService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
ExtensionService portainer.ExtensionService
endpointIdentifier portainer.EndpointID
endpointType portainer.EndpointType
}
restrictedDockerOperationContext struct {
isAdmin bool
@@ -62,19 +58,7 @@ func (p *proxyTransport) RoundTrip(request *http.Request) (*http.Response, error
}
func (p *proxyTransport) executeDockerRequest(request *http.Request) (*http.Response, error) {
response, err := p.dockerTransport.RoundTrip(request)
if p.endpointType != portainer.EdgeAgentEnvironment {
return response, err
}
if err == nil {
p.ReverseTunnelService.SetTunnelStatusToActive(p.endpointIdentifier)
} else {
p.ReverseTunnelService.SetTunnelStatusToIdle(p.endpointIdentifier)
}
return response, err
return p.dockerTransport.RoundTrip(request)
}
func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Response, error) {
@@ -114,29 +98,11 @@ func (p *proxyTransport) proxyDockerRequest(request *http.Request) (*http.Respon
return p.proxyBuildRequest(request)
case strings.HasPrefix(path, "/images"):
return p.proxyImageRequest(request)
case strings.HasPrefix(path, "/v2"):
return p.proxyAgentRequest(request)
default:
return p.executeDockerRequest(request)
}
}
func (p *proxyTransport) proxyAgentRequest(r *http.Request) (*http.Response, error) {
requestPath := strings.TrimPrefix(r.URL.Path, "/v2")
switch {
case strings.HasPrefix(requestPath, "/browse"):
volumeIDParameter, found := r.URL.Query()["volumeID"]
if !found || len(volumeIDParameter) < 1 {
return p.administratorOperation(r)
}
return p.restrictedVolumeBrowserOperation(r, volumeIDParameter[0])
}
return p.executeDockerRequest(r)
}
func (p *proxyTransport) proxyConfigRequest(request *http.Request) (*http.Response, error) {
switch requestPath := request.URL.Path; requestPath {
case "/configs/create":
@@ -388,62 +354,6 @@ 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.
@@ -574,12 +484,7 @@ func (p *proxyTransport) createOperationContext(request *http.Request) (*restric
if tokenData.Role != portainer.AdministratorRole {
operationContext.isAdmin = false
user, err := p.UserService.User(operationContext.userID)
if err != nil {
return nil, err
}
_, ok := user.EndpointAuthorizations[p.endpointIdentifier][portainer.EndpointResourcesAccess]
_, ok := tokenData.EndpointAuthorizations[p.endpointIdentifier][portainer.EndpointResourcesAccess]
if ok {
operationContext.endpointResourceAccess = true
}
+8 -21
View File
@@ -16,14 +16,11 @@ const AzureAPIBaseURL = "https://management.azure.com"
// proxyFactory is a factory to create reverse proxies to Docker endpoints
type proxyFactory struct {
ResourceControlService portainer.ResourceControlService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService
SettingsService portainer.SettingsService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
ExtensionService portainer.ExtensionService
}
func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
@@ -32,21 +29,21 @@ func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
}
func newAzureProxy(credentials *portainer.AzureCredentials) (http.Handler, error) {
remoteURL, err := url.Parse(AzureAPIBaseURL)
url, err := url.Parse(AzureAPIBaseURL)
if err != nil {
return nil, err
}
proxy := newSingleHostReverseProxyWithHostHeader(remoteURL)
proxy := newSingleHostReverseProxyWithHostHeader(url)
proxy.Transport = NewAzureTransport(credentials)
return proxy, nil
}
func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, tlsConfig *portainer.TLSConfiguration, endpoint *portainer.Endpoint) (http.Handler, error) {
func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, tlsConfig *portainer.TLSConfiguration, enableSignature bool, endpointID portainer.EndpointID) (http.Handler, error) {
u.Scheme = "https"
proxy := factory.createDockerReverseProxy(u, endpoint)
proxy := factory.createDockerReverseProxy(u, enableSignature, endpointID)
config, err := crypto.CreateTLSConfigurationFromDisk(tlsConfig.TLSCACertPath, tlsConfig.TLSCertPath, tlsConfig.TLSKeyPath, tlsConfig.TLSSkipVerify)
if err != nil {
return nil, err
@@ -56,32 +53,22 @@ func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, tlsConfig *portaine
return proxy, nil
}
func (factory *proxyFactory) newDockerHTTPProxy(u *url.URL, endpoint *portainer.Endpoint) http.Handler {
func (factory *proxyFactory) newDockerHTTPProxy(u *url.URL, enableSignature bool, endpointID portainer.EndpointID) http.Handler {
u.Scheme = "http"
return factory.createDockerReverseProxy(u, endpoint)
return factory.createDockerReverseProxy(u, enableSignature, endpointID)
}
func (factory *proxyFactory) createDockerReverseProxy(u *url.URL, endpoint *portainer.Endpoint) *httputil.ReverseProxy {
func (factory *proxyFactory) createDockerReverseProxy(u *url.URL, enableSignature bool, endpointID portainer.EndpointID) *httputil.ReverseProxy {
proxy := newSingleHostReverseProxyWithHostHeader(u)
enableSignature := false
if endpoint.Type == portainer.AgentOnDockerEnvironment {
enableSignature = true
}
transport := &proxyTransport{
enableSignature: enableSignature,
ResourceControlService: factory.ResourceControlService,
UserService: factory.UserService,
TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
ReverseTunnelService: factory.ReverseTunnelService,
ExtensionService: factory.ExtensionService,
dockerTransport: &http.Transport{},
endpointIdentifier: endpoint.ID,
endpointType: endpoint.Type,
endpointIdentifier: endpointID,
}
if enableSignature {
+2 -6
View File
@@ -8,21 +8,17 @@ import (
portainer "github.com/portainer/portainer/api"
)
func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endpoint) http.Handler {
func (factory *proxyFactory) newLocalProxy(path string, endpointID portainer.EndpointID) http.Handler {
proxy := &localProxy{}
transport := &proxyTransport{
enableSignature: false,
ResourceControlService: factory.ResourceControlService,
UserService: factory.UserService,
TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
ExtensionService: factory.ExtensionService,
dockerTransport: newSocketTransport(path),
ReverseTunnelService: factory.ReverseTunnelService,
endpointIdentifier: endpoint.ID,
endpointType: endpoint.Type,
endpointIdentifier: endpointID,
}
proxy.Transport = transport
return proxy
+3 -8
View File
@@ -3,29 +3,24 @@
package proxy
import (
"github.com/Microsoft/go-winio"
"net"
"net/http"
"github.com/Microsoft/go-winio"
portainer "github.com/portainer/portainer/api"
)
func (factory *proxyFactory) newLocalProxy(path string, endpoint *portainer.Endpoint) http.Handler {
func (factory *proxyFactory) newLocalProxy(path string, endpointID portainer.EndpointID) http.Handler {
proxy := &localProxy{}
transport := &proxyTransport{
enableSignature: false,
ResourceControlService: factory.ResourceControlService,
UserService: factory.UserService,
TeamMembershipService: factory.TeamMembershipService,
SettingsService: factory.SettingsService,
RegistryService: factory.RegistryService,
DockerHubService: factory.DockerHubService,
ReverseTunnelService: factory.ReverseTunnelService,
ExtensionService: factory.ExtensionService,
dockerTransport: newNamedPipeTransport(path),
endpointIdentifier: endpoint.ID,
endpointType: endpoint.Type,
endpointIdentifier: endpointID,
}
proxy.Transport = transport
return proxy
+19 -40
View File
@@ -1,7 +1,6 @@
package proxy
import (
"fmt"
"net/http"
"net/url"
"strconv"
@@ -22,7 +21,6 @@ type (
// Manager represents a service used to manage Docker proxies.
Manager struct {
proxyFactory *proxyFactory
reverseTunnelService portainer.ReverseTunnelService
proxies cmap.ConcurrentMap
extensionProxies cmap.ConcurrentMap
legacyExtensionProxies cmap.ConcurrentMap
@@ -31,14 +29,11 @@ type (
// ManagerParams represents the required parameters to create a new Manager instance.
ManagerParams struct {
ResourceControlService portainer.ResourceControlService
UserService portainer.UserService
TeamMembershipService portainer.TeamMembershipService
SettingsService portainer.SettingsService
RegistryService portainer.RegistryService
DockerHubService portainer.DockerHubService
SignatureService portainer.DigitalSignatureService
ReverseTunnelService portainer.ReverseTunnelService
ExtensionService portainer.ExtensionService
}
)
@@ -50,22 +45,18 @@ func NewManager(parameters *ManagerParams) *Manager {
legacyExtensionProxies: cmap.New(),
proxyFactory: &proxyFactory{
ResourceControlService: parameters.ResourceControlService,
UserService: parameters.UserService,
TeamMembershipService: parameters.TeamMembershipService,
SettingsService: parameters.SettingsService,
RegistryService: parameters.RegistryService,
DockerHubService: parameters.DockerHubService,
SignatureService: parameters.SignatureService,
ReverseTunnelService: parameters.ReverseTunnelService,
ExtensionService: parameters.ExtensionService,
},
reverseTunnelService: parameters.ReverseTunnelService,
}
}
// GetProxy returns the proxy associated to a key
func (manager *Manager) GetProxy(endpoint *portainer.Endpoint) http.Handler {
proxy, ok := manager.proxies.Get(string(endpoint.ID))
func (manager *Manager) GetProxy(key string) http.Handler {
proxy, ok := manager.proxies.Get(key)
if !ok {
return nil
}
@@ -85,8 +76,8 @@ func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (ht
}
// DeleteProxy deletes the proxy associated to a key
func (manager *Manager) DeleteProxy(endpoint *portainer.Endpoint) {
manager.proxies.Remove(string(endpoint.ID))
func (manager *Manager) DeleteProxy(key string) {
manager.proxies.Remove(key)
}
// GetExtensionProxy returns an extension proxy associated to an extension identifier
@@ -101,7 +92,7 @@ func (manager *Manager) GetExtensionProxy(extensionID portainer.ExtensionID) htt
// CreateExtensionProxy creates a new HTTP reverse proxy for an extension and
// registers it in the extension map associated to the specified extension identifier
func (manager *Manager) CreateExtensionProxy(extensionID portainer.ExtensionID) (http.Handler, error) {
address := "http://" + portainer.ExtensionServer + ":" + extensionPorts[extensionID]
address := "http://localhost:" + extensionPorts[extensionID]
extensionURL, err := url.Parse(address)
if err != nil {
@@ -145,40 +136,28 @@ func (manager *Manager) CreateLegacyExtensionProxy(key, extensionAPIURL string)
return proxy, nil
}
func (manager *Manager) createDockerProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
baseURL := endpoint.URL
if endpoint.Type == portainer.EdgeAgentEnvironment {
tunnel := manager.reverseTunnelService.GetTunnelDetails(endpoint.ID)
baseURL = fmt.Sprintf("http://localhost:%d", tunnel.Port)
func (manager *Manager) createDockerProxy(endpointURL *url.URL, tlsConfig *portainer.TLSConfiguration, endpointID portainer.EndpointID) (http.Handler, error) {
if endpointURL.Scheme == "tcp" {
if tlsConfig.TLS || tlsConfig.TLSSkipVerify {
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, tlsConfig, false, endpointID)
}
return manager.proxyFactory.newDockerHTTPProxy(endpointURL, false, endpointID), nil
}
return manager.proxyFactory.newLocalProxy(endpointURL.Path, endpointID), nil
}
endpointURL, err := url.Parse(baseURL)
func (manager *Manager) createProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
endpointURL, err := url.Parse(endpoint.URL)
if err != nil {
return nil, err
}
switch endpoint.Type {
case portainer.AgentOnDockerEnvironment:
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, endpoint)
case portainer.EdgeAgentEnvironment:
return manager.proxyFactory.newDockerHTTPProxy(endpointURL, endpoint), nil
}
if endpointURL.Scheme == "tcp" {
if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, endpoint)
}
return manager.proxyFactory.newDockerHTTPProxy(endpointURL, endpoint), nil
}
return manager.proxyFactory.newLocalProxy(endpointURL.Path, endpoint), nil
}
func (manager *Manager) createProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
if endpoint.Type == portainer.AzureEnvironment {
return manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, true, endpoint.ID)
case portainer.AzureEnvironment:
return newAzureProxy(&endpoint.AzureCredentials)
default:
return manager.createDockerProxy(endpointURL, &endpoint.TLSConfig, endpoint.ID)
}
return manager.createDockerProxy(endpoint)
}
+10 -49
View File
@@ -34,7 +34,7 @@ type (
}
// RestrictedRequestContext is a data structure containing information
// used in AuthenticatedAccess
// used in RestrictedAccess
RestrictedRequestContext struct {
IsAdmin bool
IsTeamLeader bool
@@ -64,40 +64,22 @@ func (bouncer *RequestBouncer) PublicAccess(h http.Handler) http.Handler {
return h
}
// AdminAccess defines a security check for API endpoints that require an authorization check.
// AuthorizedAccess defines a security check for API endpoints that require an authorization check.
// Authentication is required to access these endpoints.
// If the RBAC extension is enabled, authorizations are required to use these endpoints.
// If the RBAC extension is not enabled, the administrator role is required to use these endpoints.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to inside the API operation for extra authorization validation
// and resource filtering.
func (bouncer *RequestBouncer) AdminAccess(h http.Handler) http.Handler {
func (bouncer *RequestBouncer) AuthorizedAccess(h http.Handler) http.Handler {
h = bouncer.mwUpgradeToRestrictedRequest(h)
h = bouncer.mwCheckPortainerAuthorizations(h, true)
h = bouncer.mwCheckPortainerAuthorizations(h)
h = bouncer.mwAuthenticatedUser(h)
return h
}
// RestrictedAccess defines a security check for restricted API endpoints.
// Authentication is required to access these endpoints.
// If the RBAC extension is enabled, authorizations are required to use these endpoints.
// If the RBAC extension is not enabled, access is granted to any authenticated user.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to inside the API operation for extra authorization validation
// and resource filtering.
// that might be used later to authorize/filter access to resources inside an endpoint.
func (bouncer *RequestBouncer) RestrictedAccess(h http.Handler) http.Handler {
h = bouncer.mwUpgradeToRestrictedRequest(h)
h = bouncer.mwCheckPortainerAuthorizations(h, false)
h = bouncer.mwAuthenticatedUser(h)
return h
}
// AuthenticatedAccess defines a security check for restricted API endpoints.
// Authentication is required to access these endpoints.
// The request context will be enhanced with a RestrictedRequestContext object
// that might be used later to inside the API operation for extra authorization validation
// and resource filtering.
func (bouncer *RequestBouncer) AuthenticatedAccess(h http.Handler) http.Handler {
h = bouncer.mwUpgradeToRestrictedRequest(h)
h = bouncer.mwAuthenticatedUser(h)
return h
@@ -160,15 +142,10 @@ func (bouncer *RequestBouncer) checkEndpointOperationAuthorization(r *http.Reque
return err
}
user, err := bouncer.userService.User(tokenData.ID)
if err != nil {
return err
}
apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(),
Method: r.Method,
Authorizations: user.EndpointAuthorizations[endpoint.ID],
Authorizations: tokenData.EndpointAuthorizations[endpoint.ID],
}
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
@@ -209,13 +186,11 @@ func (bouncer *RequestBouncer) mwAuthenticatedUser(h http.Handler) http.Handler
// mwCheckPortainerAuthorizations will verify that the user has the required authorization to access
// a specific API endpoint. It will leverage the RBAC extension authorization validation if the extension
// is enabled.
// If the administratorOnly flag is specified and the RBAC extension is not enabled, this will prevent non-admin
// users from accessing the endpoint.
func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler, administratorOnly bool) http.Handler {
func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenData, err := RetrieveTokenData(r)
if err != nil {
httperror.WriteError(w, http.StatusForbidden, "Access denied", portainer.ErrUnauthorized)
httperror.WriteError(w, http.StatusForbidden, "Access denied", portainer.ErrResourceAccessDenied)
return
}
@@ -226,11 +201,6 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
extension, err := bouncer.extensionService.Extension(portainer.RBACExtension)
if err == portainer.ErrObjectNotFound {
if administratorOnly {
httperror.WriteError(w, http.StatusForbidden, "Access denied", portainer.ErrUnauthorized)
return
}
next.ServeHTTP(w, r)
return
} else if err != nil {
@@ -238,19 +208,10 @@ func (bouncer *RequestBouncer) mwCheckPortainerAuthorizations(next http.Handler,
return
}
user, err := bouncer.userService.User(tokenData.ID)
if err != nil && err == portainer.ErrObjectNotFound {
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return
} else if err != nil {
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err)
return
}
apiOperation := &portainer.APIOperationAuthorizationRequest{
Path: r.URL.String(),
Method: r.Method,
Authorizations: user.PortainerAuthorizations,
Authorizations: tokenData.PortainerAuthorizations,
}
bouncer.rbacExtensionClient.setLicenseKey(extension.License.LicenseKey)
@@ -320,7 +281,7 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han
httperror.WriteError(w, http.StatusUnauthorized, "Unauthorized", portainer.ErrUnauthorized)
return
} else if err != nil {
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve user details from the database", err)
httperror.WriteError(w, http.StatusInternalServerError, "Unable to retrieve users from the database", err)
return
}
} else {
+40 -74
View File
@@ -3,8 +3,6 @@ package http
import (
"time"
"github.com/portainer/portainer/api/http/handler/support"
"github.com/portainer/portainer/api/http/handler/roles"
"github.com/portainer/portainer/api"
@@ -41,72 +39,59 @@ import (
// Server implements the portainer.Server interface
type Server struct {
BindAddress string
AssetsPath string
AuthDisabled bool
EndpointManagement bool
Status *portainer.Status
ReverseTunnelService portainer.ReverseTunnelService
ExtensionManager portainer.ExtensionManager
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
JobScheduler portainer.JobScheduler
Snapshotter portainer.Snapshotter
RoleService portainer.RoleService
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
FileService portainer.FileService
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
ExtensionService portainer.ExtensionService
RegistryService portainer.RegistryService
ResourceControlService portainer.ResourceControlService
ScheduleService portainer.ScheduleService
SettingsService portainer.SettingsService
StackService portainer.StackService
SwarmStackManager portainer.SwarmStackManager
TagService portainer.TagService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
TemplateService portainer.TemplateService
UserService portainer.UserService
WebhookService portainer.WebhookService
Handler *handler.Handler
SSL bool
SSLCert string
SSLKey string
DockerClientFactory *docker.ClientFactory
JobService portainer.JobService
BindAddress string
AssetsPath string
TunnelServerFingerprint string
AuthDisabled bool
EndpointManagement bool
Status *portainer.Status
ExtensionManager portainer.ExtensionManager
ComposeStackManager portainer.ComposeStackManager
CryptoService portainer.CryptoService
SignatureService portainer.DigitalSignatureService
JobScheduler portainer.JobScheduler
Snapshotter portainer.Snapshotter
RoleService portainer.RoleService
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
FileService portainer.FileService
GitService portainer.GitService
JWTService portainer.JWTService
LDAPService portainer.LDAPService
ExtensionService portainer.ExtensionService
RegistryService portainer.RegistryService
ResourceControlService portainer.ResourceControlService
ScheduleService portainer.ScheduleService
SettingsService portainer.SettingsService
StackService portainer.StackService
SwarmStackManager portainer.SwarmStackManager
TagService portainer.TagService
TeamService portainer.TeamService
TeamMembershipService portainer.TeamMembershipService
TemplateService portainer.TemplateService
UserService portainer.UserService
WebhookService portainer.WebhookService
Handler *handler.Handler
SSL bool
SSLCert string
SSLKey string
DockerClientFactory *docker.ClientFactory
JobService portainer.JobService
}
// Start starts the HTTP server
func (server *Server) Start() error {
proxyManagerParameters := &proxy.ManagerParams{
ResourceControlService: server.ResourceControlService,
UserService: server.UserService,
TeamMembershipService: server.TeamMembershipService,
SettingsService: server.SettingsService,
RegistryService: server.RegistryService,
DockerHubService: server.DockerHubService,
SignatureService: server.SignatureService,
ReverseTunnelService: server.ReverseTunnelService,
ExtensionService: server.ExtensionService,
}
proxyManager := proxy.NewManager(proxyManagerParameters)
authorizationServiceParameters := &portainer.AuthorizationServiceParameters{
EndpointService: server.EndpointService,
EndpointGroupService: server.EndpointGroupService,
RegistryService: server.RegistryService,
RoleService: server.RoleService,
TeamMembershipService: server.TeamMembershipService,
UserService: server.UserService,
}
authorizationService := portainer.NewAuthorizationService(authorizationServiceParameters)
requestBouncerParameters := &security.RequestBouncerParams{
JWTService: server.JWTService,
UserService: server.UserService,
@@ -148,20 +133,15 @@ func (server *Server) Start() error {
endpointHandler.ProxyManager = proxyManager
endpointHandler.Snapshotter = server.Snapshotter
endpointHandler.JobService = server.JobService
endpointHandler.ReverseTunnelService = server.ReverseTunnelService
endpointHandler.SettingsService = server.SettingsService
endpointHandler.AuthorizationService = authorizationService
endpointHandler.TunnelServerFingerprint = server.TunnelServerFingerprint
var endpointGroupHandler = endpointgroups.NewHandler(requestBouncer)
endpointGroupHandler.EndpointGroupService = server.EndpointGroupService
endpointGroupHandler.EndpointService = server.EndpointService
endpointGroupHandler.AuthorizationService = authorizationService
var endpointProxyHandler = endpointproxy.NewHandler(requestBouncer)
endpointProxyHandler.EndpointService = server.EndpointService
endpointProxyHandler.ProxyManager = proxyManager
endpointProxyHandler.SettingsService = server.SettingsService
endpointProxyHandler.ReverseTunnelService = server.ReverseTunnelService
var fileHandler = file.NewHandler(filepath.Join(server.AssetsPath, "public"))
@@ -173,7 +153,6 @@ func (server *Server) Start() error {
extensionHandler.EndpointGroupService = server.EndpointGroupService
extensionHandler.EndpointService = server.EndpointService
extensionHandler.RegistryService = server.RegistryService
extensionHandler.AuthorizationService = authorizationService
var registryHandler = registries.NewHandler(requestBouncer)
registryHandler.RegistryService = server.RegistryService
@@ -191,7 +170,6 @@ func (server *Server) Start() error {
schedulesHandler.JobService = server.JobService
schedulesHandler.JobScheduler = server.JobScheduler
schedulesHandler.SettingsService = server.SettingsService
schedulesHandler.ReverseTunnelService = server.ReverseTunnelService
var settingsHandler = settings.NewHandler(requestBouncer)
settingsHandler.SettingsService = server.SettingsService
@@ -199,9 +177,6 @@ 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
@@ -213,7 +188,6 @@ func (server *Server) Start() error {
stackHandler.GitService = server.GitService
stackHandler.RegistryService = server.RegistryService
stackHandler.DockerHubService = server.DockerHubService
stackHandler.SettingsService = server.SettingsService
var tagHandler = tags.NewHandler(requestBouncer)
tagHandler.TagService = server.TagService
@@ -221,16 +195,11 @@ func (server *Server) Start() error {
var teamHandler = teams.NewHandler(requestBouncer)
teamHandler.TeamService = server.TeamService
teamHandler.TeamMembershipService = server.TeamMembershipService
teamHandler.AuthorizationService = authorizationService
var teamMembershipHandler = teammemberships.NewHandler(requestBouncer)
teamMembershipHandler.TeamMembershipService = server.TeamMembershipService
teamMembershipHandler.AuthorizationService = authorizationService
var statusHandler = status.NewHandler(requestBouncer, server.Status)
var supportHandler = support.NewHandler(requestBouncer)
var templatesHandler = templates.NewHandler(requestBouncer)
templatesHandler.TemplateService = server.TemplateService
templatesHandler.SettingsService = server.SettingsService
@@ -245,12 +214,10 @@ func (server *Server) Start() error {
userHandler.CryptoService = server.CryptoService
userHandler.ResourceControlService = server.ResourceControlService
userHandler.SettingsService = server.SettingsService
userHandler.AuthorizationService = authorizationService
var websocketHandler = websocket.NewHandler(requestBouncer)
websocketHandler.EndpointService = server.EndpointService
websocketHandler.SignatureService = server.SignatureService
websocketHandler.ReverseTunnelService = server.ReverseTunnelService
var webhookHandler = webhooks.NewHandler(requestBouncer)
webhookHandler.WebhookService = server.WebhookService
@@ -272,7 +239,6 @@ func (server *Server) Start() error {
SettingsHandler: settingsHandler,
StatusHandler: statusHandler,
StackHandler: stackHandler,
SupportHandler: supportHandler,
TagHandler: tagHandler,
TeamHandler: teamHandler,
TeamMembershipHandler: teamMembershipHandler,

Some files were not shown because too many files have changed in this diff Show More