mirror of
https://github.com/portainer/portainer.git
synced 2025-08-02 20:35:25 +02:00
feat(extensions): introduce RBAC extension (#2900)
This commit is contained in:
parent
27a0188949
commit
8057aa45c4
196 changed files with 3321 additions and 1316 deletions
|
@ -117,11 +117,60 @@ 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 {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to generate JWT token", err}
|
||||
|
|
|
@ -3,8 +3,8 @@ package auth
|
|||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
|
|
122
api/http/handler/auth/authorization.go
Normal file
122
api/http/handler/auth/authorization.go
Normal 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
|
||||
}
|
|
@ -30,6 +30,9 @@ type Handler struct {
|
|||
TeamService portainer.TeamService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
ExtensionService portainer.ExtensionService
|
||||
EndpointService portainer.EndpointService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
RoleService portainer.RoleService
|
||||
ProxyManager *proxy.Manager
|
||||
}
|
||||
|
||||
|
|
|
@ -25,9 +25,9 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/dockerhub",
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.dockerhubInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.dockerhubInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/dockerhub",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.dockerhubUpdate))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.dockerhubUpdate))).Methods(http.MethodPut)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -36,11 +36,11 @@ func (handler *Handler) endpointGroupCreate(w http.ResponseWriter, r *http.Reque
|
|||
}
|
||||
|
||||
endpointGroup := &portainer.EndpointGroup{
|
||||
Name: payload.Name,
|
||||
Description: payload.Description,
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Tags: payload.Tags,
|
||||
Name: payload.Name,
|
||||
Description: payload.Description,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Tags: payload.Tags,
|
||||
}
|
||||
|
||||
err = handler.EndpointGroupService.CreateEndpointGroup(endpointGroup)
|
||||
|
|
|
@ -14,6 +14,8 @@ type endpointGroupUpdatePayload struct {
|
|||
Description string
|
||||
AssociatedEndpoints []portainer.EndpointID
|
||||
Tags []string
|
||||
UserAccessPolicies portainer.UserAccessPolicies
|
||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||
}
|
||||
|
||||
func (payload *endpointGroupUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -52,20 +54,30 @@ func (handler *Handler) endpointGroupUpdate(w http.ResponseWriter, r *http.Reque
|
|||
endpointGroup.Tags = payload.Tags
|
||||
}
|
||||
|
||||
if payload.UserAccessPolicies != nil {
|
||||
endpointGroup.UserAccessPolicies = payload.UserAccessPolicies
|
||||
}
|
||||
|
||||
if payload.TeamAccessPolicies != nil {
|
||||
endpointGroup.TeamAccessPolicies = payload.TeamAccessPolicies
|
||||
}
|
||||
|
||||
err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, endpointGroup)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint group changes inside the database", err}
|
||||
}
|
||||
|
||||
endpoints, err := handler.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
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 payload.AssociatedEndpoints != nil {
|
||||
endpoints, err := handler.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to update endpoint", 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}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,63 +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"
|
||||
)
|
||||
|
||||
type endpointGroupUpdateAccessPayload struct {
|
||||
AuthorizedUsers []int
|
||||
AuthorizedTeams []int
|
||||
}
|
||||
|
||||
func (payload *endpointGroupUpdateAccessPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/endpoint_groups/:id/access
|
||||
func (handler *Handler) endpointGroupUpdateAccess(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}
|
||||
}
|
||||
|
||||
var payload endpointGroupUpdateAccessPayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", 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}
|
||||
}
|
||||
|
||||
if payload.AuthorizedUsers != nil {
|
||||
authorizedUserIDs := []portainer.UserID{}
|
||||
for _, value := range payload.AuthorizedUsers {
|
||||
authorizedUserIDs = append(authorizedUserIDs, portainer.UserID(value))
|
||||
}
|
||||
endpointGroup.AuthorizedUsers = authorizedUserIDs
|
||||
}
|
||||
|
||||
if payload.AuthorizedTeams != nil {
|
||||
authorizedTeamIDs := []portainer.TeamID{}
|
||||
for _, value := range payload.AuthorizedTeams {
|
||||
authorizedTeamIDs = append(authorizedTeamIDs, portainer.TeamID(value))
|
||||
}
|
||||
endpointGroup.AuthorizedTeams = authorizedTeamIDs
|
||||
}
|
||||
|
||||
err = handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, endpointGroup)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist endpoint group changes inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, endpointGroup)
|
||||
}
|
|
@ -22,17 +22,15 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/endpoint_groups",
|
||||
bouncer.AdministratorAccess(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.AdministratorAccess(httperror.LoggerHandler(h.endpointGroupInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/endpoint_groups/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointGroupUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/endpoint_groups/{id}/access",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointGroupUpdateAccess))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/endpoint_groups/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointGroupDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointGroupDelete))).Methods(http.MethodDelete)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -23,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}/extensions/storidge").Handler(
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToStoridgeAPI)))
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ func (handler *Handler) proxyRequestsToAzureAPI(w http.ResponseWriter, r *http.R
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
var proxy http.Handler
|
||||
|
|
|
@ -28,9 +28,9 @@ func (handler *Handler) proxyRequestsToDockerAPI(w http.ResponseWriter, r *http.
|
|||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Unable to query endpoint", errors.New("Endpoint is down")}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
var proxy http.Handler
|
||||
|
|
|
@ -25,9 +25,9 @@ func (handler *Handler) proxyRequestsToStoridgeAPI(w http.ResponseWriter, r *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
var storidgeExtension *portainer.EndpointExtension
|
||||
|
|
|
@ -172,19 +172,19 @@ func (handler *Handler) createAzureEndpoint(payload *endpointCreatePayload) (*po
|
|||
|
||||
endpointID := handler.EndpointService.GetNextIdentifier()
|
||||
endpoint := &portainer.Endpoint{
|
||||
ID: portainer.EndpointID(endpointID),
|
||||
Name: payload.Name,
|
||||
URL: "https://management.azure.com",
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
AzureCredentials: credentials,
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
ID: portainer.EndpointID(endpointID),
|
||||
Name: payload.Name,
|
||||
URL: "https://management.azure.com",
|
||||
Type: portainer.AzureEnvironment,
|
||||
GroupID: portainer.EndpointGroupID(payload.GroupID),
|
||||
PublicURL: payload.PublicURL,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
AzureCredentials: credentials,
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
}
|
||||
|
||||
err = handler.EndpointService.CreateEndpoint(endpoint)
|
||||
|
@ -224,12 +224,12 @@ func (handler *Handler) createUnsecuredEndpoint(payload *endpointCreatePayload)
|
|||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: false,
|
||||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
}
|
||||
|
||||
err := handler.snapshotAndPersistEndpoint(endpoint)
|
||||
|
@ -268,12 +268,12 @@ func (handler *Handler) createTLSSecuredEndpoint(payload *endpointCreatePayload)
|
|||
TLS: payload.TLS,
|
||||
TLSSkipVerify: payload.TLSSkipVerify,
|
||||
},
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
Extensions: []portainer.EndpointExtension{},
|
||||
Tags: payload.Tags,
|
||||
Status: portainer.EndpointStatusUp,
|
||||
Snapshots: []portainer.Snapshot{},
|
||||
}
|
||||
|
||||
filesystemError := handler.storeTLSFiles(endpoint, payload)
|
||||
|
|
|
@ -23,9 +23,9 @@ func (handler *Handler) endpointInspect(w http.ResponseWriter, r *http.Request)
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, false)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
hideFields(endpoint)
|
||||
|
|
|
@ -70,11 +70,6 @@ func (handler *Handler) endpointJob(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
}
|
||||
|
||||
switch method {
|
||||
case "file":
|
||||
return handler.executeJobFromFile(w, r, endpoint, nodeName)
|
||||
|
|
|
@ -24,6 +24,8 @@ type endpointUpdatePayload struct {
|
|||
AzureTenantID *string
|
||||
AzureAuthenticationKey *string
|
||||
Tags []string
|
||||
UserAccessPolicies portainer.UserAccessPolicies
|
||||
TeamAccessPolicies portainer.TeamAccessPolicies
|
||||
}
|
||||
|
||||
func (payload *endpointUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -74,6 +76,14 @@ func (handler *Handler) endpointUpdate(w http.ResponseWriter, r *http.Request) *
|
|||
endpoint.Tags = payload.Tags
|
||||
}
|
||||
|
||||
if payload.UserAccessPolicies != nil {
|
||||
endpoint.UserAccessPolicies = payload.UserAccessPolicies
|
||||
}
|
||||
|
||||
if payload.TeamAccessPolicies != nil {
|
||||
endpoint.TeamAccessPolicies = payload.TeamAccessPolicies
|
||||
}
|
||||
|
||||
if payload.Status != nil {
|
||||
switch *payload.Status {
|
||||
case 1:
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type endpointUpdateAccessPayload struct {
|
||||
AuthorizedUsers []int
|
||||
AuthorizedTeams []int
|
||||
}
|
||||
|
||||
func (payload *endpointUpdateAccessPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/endpoints/:id/access
|
||||
func (handler *Handler) endpointUpdateAccess(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
if !handler.authorizeEndpointManagement {
|
||||
return &httperror.HandlerError{http.StatusServiceUnavailable, "Endpoint management is disabled", ErrEndpointManagementDisabled}
|
||||
}
|
||||
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid endpoint identifier route variable", err}
|
||||
}
|
||||
|
||||
var payload endpointUpdateAccessPayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", 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 payload.AuthorizedUsers != nil {
|
||||
authorizedUserIDs := []portainer.UserID{}
|
||||
for _, value := range payload.AuthorizedUsers {
|
||||
authorizedUserIDs = append(authorizedUserIDs, portainer.UserID(value))
|
||||
}
|
||||
endpoint.AuthorizedUsers = authorizedUserIDs
|
||||
}
|
||||
|
||||
if payload.AuthorizedTeams != nil {
|
||||
authorizedTeamIDs := []portainer.TeamID{}
|
||||
for _, value := range payload.AuthorizedTeams {
|
||||
authorizedTeamIDs = append(authorizedTeamIDs, portainer.TeamID(value))
|
||||
}
|
||||
endpoint.AuthorizedTeams = authorizedTeamIDs
|
||||
}
|
||||
|
||||
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.JSON(w, endpoint)
|
||||
}
|
|
@ -37,32 +37,30 @@ type Handler struct {
|
|||
// NewHandler creates a handler to manage endpoint operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer, authorizeEndpointManagement bool) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
Router: mux.NewRouter(),
|
||||
authorizeEndpointManagement: authorizeEndpointManagement,
|
||||
requestBouncer: bouncer,
|
||||
}
|
||||
|
||||
h.Handle("/endpoints",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointCreate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/endpoints/snapshot",
|
||||
bouncer.AdministratorAccess(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.AdministratorAccess(httperror.LoggerHandler(h.endpointUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/endpoints/{id}/access",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointUpdateAccess))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/endpoints/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointDelete))).Methods(http.MethodDelete)
|
||||
h.Handle("/endpoints/{id}/extensions",
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointExtensionAdd))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointExtensionAdd))).Methods(http.MethodPost)
|
||||
h.Handle("/endpoints/{id}/extensions/{extensionType}",
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.endpointExtensionRemove))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointExtensionRemove))).Methods(http.MethodDelete)
|
||||
h.Handle("/endpoints/{id}/job",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointJob))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointJob))).Methods(http.MethodPost)
|
||||
h.Handle("/endpoints/{id}/snapshot",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.endpointSnapshot))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.endpointSnapshot))).Methods(http.MethodPost)
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -70,6 +70,13 @@ func (handler *Handler) extensionCreate(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
extension.Enabled = true
|
||||
|
||||
if extension.ID == portainer.RBACExtension {
|
||||
err = handler.upgradeRBACData()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "An error occured during database update", err}
|
||||
}
|
||||
}
|
||||
|
||||
err = handler.ExtensionService.Persist(extension)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist extension status inside the database", err}
|
||||
|
|
|
@ -12,8 +12,11 @@ import (
|
|||
// Handler is the HTTP handler used to handle extension operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
ExtensionService portainer.ExtensionService
|
||||
ExtensionManager portainer.ExtensionManager
|
||||
ExtensionService portainer.ExtensionService
|
||||
ExtensionManager portainer.ExtensionManager
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
EndpointService portainer.EndpointService
|
||||
RegistryService portainer.RegistryService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage extension operations.
|
||||
|
@ -23,15 +26,15 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
}
|
||||
|
||||
h.Handle("/extensions",
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionList))).Methods(http.MethodGet)
|
||||
h.Handle("/extensions",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/extensions/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.extensionInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/extensions/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.extensionDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionDelete))).Methods(http.MethodDelete)
|
||||
h.Handle("/extensions/{id}/update",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.extensionUpdate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.extensionUpdate))).Methods(http.MethodPost)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
59
api/http/handler/extensions/upgrade.go
Normal file
59
api/http/handler/extensions/upgrade.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package extensions
|
||||
|
||||
import portainer "github.com/portainer/portainer/api"
|
||||
|
||||
func updateUserAccessPolicyToReadOnlyRole(policies portainer.UserAccessPolicies, key portainer.UserID) {
|
||||
tmp := policies[key]
|
||||
tmp.RoleID = 4
|
||||
policies[key] = tmp
|
||||
}
|
||||
|
||||
func updateTeamAccessPolicyToReadOnlyRole(policies portainer.TeamAccessPolicies, key portainer.TeamID) {
|
||||
tmp := policies[key]
|
||||
tmp.RoleID = 4
|
||||
policies[key] = tmp
|
||||
}
|
||||
|
||||
func (handler *Handler) upgradeRBACData() error {
|
||||
endpointGroups, err := handler.EndpointGroupService.EndpointGroups()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpointGroup := range endpointGroups {
|
||||
for key := range endpointGroup.UserAccessPolicies {
|
||||
updateUserAccessPolicyToReadOnlyRole(endpointGroup.UserAccessPolicies, key)
|
||||
}
|
||||
|
||||
for key := range endpointGroup.TeamAccessPolicies {
|
||||
updateTeamAccessPolicyToReadOnlyRole(endpointGroup.TeamAccessPolicies, key)
|
||||
}
|
||||
|
||||
err := handler.EndpointGroupService.UpdateEndpointGroup(endpointGroup.ID, &endpointGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
endpoints, err := handler.EndpointService.Endpoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
for key := range endpoint.UserAccessPolicies {
|
||||
updateUserAccessPolicyToReadOnlyRole(endpoint.UserAccessPolicies, key)
|
||||
}
|
||||
|
||||
for key := range endpoint.TeamAccessPolicies {
|
||||
updateTeamAccessPolicyToReadOnlyRole(endpoint.TeamAccessPolicies, key)
|
||||
}
|
||||
|
||||
err := handler.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -4,6 +4,10 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/portainer/portainer/api/http/handler/schedules"
|
||||
|
||||
"github.com/portainer/portainer/api/http/handler/roles"
|
||||
|
||||
"github.com/portainer/portainer/api/http/handler/auth"
|
||||
"github.com/portainer/portainer/api/http/handler/dockerhub"
|
||||
"github.com/portainer/portainer/api/http/handler/endpointgroups"
|
||||
|
@ -14,7 +18,6 @@ import (
|
|||
"github.com/portainer/portainer/api/http/handler/motd"
|
||||
"github.com/portainer/portainer/api/http/handler/registries"
|
||||
"github.com/portainer/portainer/api/http/handler/resourcecontrols"
|
||||
"github.com/portainer/portainer/api/http/handler/schedules"
|
||||
"github.com/portainer/portainer/api/http/handler/settings"
|
||||
"github.com/portainer/portainer/api/http/handler/stacks"
|
||||
"github.com/portainer/portainer/api/http/handler/status"
|
||||
|
@ -30,8 +33,7 @@ import (
|
|||
|
||||
// Handler is a collection of all the service handlers.
|
||||
type Handler struct {
|
||||
AuthHandler *auth.Handler
|
||||
|
||||
AuthHandler *auth.Handler
|
||||
DockerHubHandler *dockerhub.Handler
|
||||
EndpointGroupHandler *endpointgroups.Handler
|
||||
EndpointHandler *endpoints.Handler
|
||||
|
@ -41,6 +43,8 @@ type Handler struct {
|
|||
ExtensionHandler *extensions.Handler
|
||||
RegistryHandler *registries.Handler
|
||||
ResourceControlHandler *resourcecontrols.Handler
|
||||
RoleHandler *roles.Handler
|
||||
SchedulesHanlder *schedules.Handler
|
||||
SettingsHandler *settings.Handler
|
||||
StackHandler *stacks.Handler
|
||||
StatusHandler *status.Handler
|
||||
|
@ -52,7 +56,6 @@ type Handler struct {
|
|||
UserHandler *users.Handler
|
||||
WebSocketHandler *websocket.Handler
|
||||
WebhookHandler *webhooks.Handler
|
||||
SchedulesHanlder *schedules.Handler
|
||||
}
|
||||
|
||||
// ServeHTTP delegates a request to the appropriate subhandler.
|
||||
|
@ -75,14 +78,18 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
case strings.HasPrefix(r.URL.Path, "/api/motd"):
|
||||
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/extensions"):
|
||||
http.StripPrefix("/api", h.ExtensionHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/motd"):
|
||||
http.StripPrefix("/api", h.MOTDHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/registries"):
|
||||
http.StripPrefix("/api", h.RegistryHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/resource_controls"):
|
||||
http.StripPrefix("/api", h.ResourceControlHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/roles"):
|
||||
http.StripPrefix("/api", h.RoleHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/schedules"):
|
||||
http.StripPrefix("/api", h.SchedulesHanlder).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/settings"):
|
||||
http.StripPrefix("/api", h.SettingsHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/stacks"):
|
||||
|
@ -105,8 +112,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api", h.WebSocketHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/webhooks"):
|
||||
http.StripPrefix("/api", h.WebhookHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/schedules"):
|
||||
http.StripPrefix("/api", h.SchedulesHanlder).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/"):
|
||||
h.FileHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/motd",
|
||||
bouncer.AuthenticatedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(http.HandlerFunc(h.motd))).Methods(http.MethodGet)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -33,19 +33,17 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
}
|
||||
|
||||
h.Handle("/registries",
|
||||
bouncer.AdministratorAccess(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.AdministratorAccess(httperror.LoggerHandler(h.registryUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/registries/{id}/access",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.registryUpdateAccess))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/registries/{id}/configure",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.registryConfigure))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryConfigure))).Methods(http.MethodPost)
|
||||
h.Handle("/registries/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.registryDelete))).Methods(http.MethodDelete)
|
||||
h.PathPrefix("/registries/{id}/v2").Handler(
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.proxyRequestsToRegistryAPI)))
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/portainer/api"
|
||||
|
@ -24,6 +26,15 @@ func (handler *Handler) proxyRequestsToRegistryAPI(w http.ResponseWriter, r *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", 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 {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.RegistryAccess(r, registry)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access registry", portainer.ErrEndpointAccessDenied}
|
||||
|
|
|
@ -53,14 +53,14 @@ func (handler *Handler) registryCreate(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
|
||||
registry := &portainer.Registry{
|
||||
Type: portainer.RegistryType(payload.Type),
|
||||
Name: payload.Name,
|
||||
URL: payload.URL,
|
||||
Authentication: payload.Authentication,
|
||||
Username: payload.Username,
|
||||
Password: payload.Password,
|
||||
AuthorizedUsers: []portainer.UserID{},
|
||||
AuthorizedTeams: []portainer.TeamID{},
|
||||
Type: portainer.RegistryType(payload.Type),
|
||||
Name: payload.Name,
|
||||
URL: payload.URL,
|
||||
Authentication: payload.Authentication,
|
||||
Username: payload.Username,
|
||||
Password: payload.Password,
|
||||
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||||
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||||
}
|
||||
|
||||
err = handler.RegistryService.CreateRegistry(registry)
|
||||
|
|
|
@ -11,11 +11,13 @@ 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 {
|
||||
|
@ -73,6 +75,14 @@ func (handler *Handler) registryUpdate(w http.ResponseWriter, r *http.Request) *
|
|||
registry.Password = ""
|
||||
}
|
||||
|
||||
if payload.UserAccessPolicies != nil {
|
||||
registry.UserAccessPolicies = payload.UserAccessPolicies
|
||||
}
|
||||
|
||||
if payload.TeamAccessPolicies != nil {
|
||||
registry.TeamAccessPolicies = payload.TeamAccessPolicies
|
||||
}
|
||||
|
||||
err = handler.RegistryService.UpdateRegistry(registry.ID, registry)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist registry changes inside the database", err}
|
||||
|
|
|
@ -1,63 +0,0 @@
|
|||
package registries
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
type registryUpdateAccessPayload struct {
|
||||
AuthorizedUsers []int
|
||||
AuthorizedTeams []int
|
||||
}
|
||||
|
||||
func (payload *registryUpdateAccessPayload) Validate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// PUT request on /api/registries/:id/access
|
||||
func (handler *Handler) registryUpdateAccess(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
registryID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid registry identifier route variable", err}
|
||||
}
|
||||
|
||||
var payload registryUpdateAccessPayload
|
||||
err = request.DecodeAndValidateJSONPayload(r, &payload)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
|
||||
}
|
||||
|
||||
registry, err := handler.RegistryService.Registry(portainer.RegistryID(registryID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a registry with the specified identifier inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a registry with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
if payload.AuthorizedUsers != nil {
|
||||
authorizedUserIDs := []portainer.UserID{}
|
||||
for _, value := range payload.AuthorizedUsers {
|
||||
authorizedUserIDs = append(authorizedUserIDs, portainer.UserID(value))
|
||||
}
|
||||
registry.AuthorizedUsers = authorizedUserIDs
|
||||
}
|
||||
|
||||
if payload.AuthorizedTeams != nil {
|
||||
authorizedTeamIDs := []portainer.TeamID{}
|
||||
for _, value := range payload.AuthorizedTeams {
|
||||
authorizedTeamIDs = append(authorizedTeamIDs, portainer.TeamID(value))
|
||||
}
|
||||
registry.AuthorizedTeams = authorizedTeamIDs
|
||||
}
|
||||
|
||||
err = handler.RegistryService.UpdateRegistry(registry.ID, registry)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist registry changes inside the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, registry)
|
||||
}
|
27
api/http/handler/roles/handler.go
Normal file
27
api/http/handler/roles/handler.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package roles
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// Handler is the HTTP handler used to handle role operations.
|
||||
type Handler struct {
|
||||
*mux.Router
|
||||
RoleService portainer.RoleService
|
||||
}
|
||||
|
||||
// NewHandler creates a handler to manage role operations.
|
||||
func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
||||
h := &Handler{
|
||||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/roles",
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.roleList))).Methods(http.MethodGet)
|
||||
|
||||
return h
|
||||
}
|
18
api/http/handler/roles/role_list.go
Normal file
18
api/http/handler/roles/role_list.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package roles
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/response"
|
||||
)
|
||||
|
||||
// GET request on /api/Role
|
||||
func (handler *Handler) roleList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
roles, err := handler.RoleService.Roles()
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve authorization sets from the database", err}
|
||||
}
|
||||
|
||||
return response.JSON(w, roles)
|
||||
}
|
|
@ -27,18 +27,18 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
}
|
||||
|
||||
h.Handle("/schedules",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleList))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleList))).Methods(http.MethodGet)
|
||||
h.Handle("/schedules",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleCreate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/schedules/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/schedules/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleUpdate))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/schedules/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleDelete))).Methods(http.MethodDelete)
|
||||
h.Handle("/schedules/{id}/file",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleFile))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleFile))).Methods(http.MethodGet)
|
||||
h.Handle("/schedules/{id}/tasks",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.scheduleTasks))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.scheduleTasks))).Methods(http.MethodGet)
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -30,13 +30,13 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/settings",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.settingsInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.settingsInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/settings",
|
||||
bouncer.AdministratorAccess(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.AdministratorAccess(httperror.LoggerHandler(h.settingsLDAPCheck))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.settingsLDAPCheck))).Methods(http.MethodPut)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -46,9 +46,9 @@ func (handler *Handler) stackCreate(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
switch portainer.StackType(stackType) {
|
||||
|
|
|
@ -4,12 +4,13 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
// DELETE request on /api/stacks/:id?external=<external>&endpointId=<endpointId>
|
||||
|
@ -38,22 +39,6 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", 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 {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
|
||||
// The EndpointID property is not available for these stacks, this API endpoint
|
||||
// can use the optional EndpointID query parameter to set a valid endpoint identifier to be
|
||||
|
@ -74,6 +59,27 @@ func (handler *Handler) stackDelete(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", 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 {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
||||
err = handler.deleteStack(stack, endpoint)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, err.Error(), err}
|
||||
|
@ -113,9 +119,9 @@ func (handler *Handler) deleteExternalStack(r *http.Request, w http.ResponseWrit
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
stack = &portainer.Stack{
|
||||
|
|
|
@ -30,6 +30,18 @@ func (handler *Handler) stackFile(w http.ResponseWriter, r *http.Request) *httpe
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
endpoint, err := handler.EndpointService.Endpoint(stack.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}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||
|
|
|
@ -25,6 +25,18 @@ func (handler *Handler) stackInspect(w http.ResponseWriter, r *http.Request) *ht
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
endpoint, err := handler.EndpointService.Endpoint(stack.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}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||
|
|
|
@ -44,6 +44,18 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
endpoint, err := handler.EndpointService.Endpoint(stack.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}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", err}
|
||||
|
@ -71,13 +83,6 @@ func (handler *Handler) stackMigrate(w http.ResponseWriter, r *http.Request) *ht
|
|||
stack.EndpointID = portainer.EndpointID(endpointID)
|
||||
}
|
||||
|
||||
endpoint, err := handler.EndpointService.Endpoint(stack.EndpointID)
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
} else if err != nil {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
}
|
||||
|
||||
targetEndpoint, err := handler.EndpointService.Endpoint(portainer.EndpointID(payload.EndpointID))
|
||||
if err == portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusNotFound, "Unable to find an endpoint with the specified identifier inside the database", err}
|
||||
|
|
|
@ -4,13 +4,14 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
"github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/proxy"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
)
|
||||
|
||||
type updateComposeStackPayload struct {
|
||||
|
@ -52,22 +53,6 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a stack with the specified identifier inside the database", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", 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 {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this is a work-around for stacks created with Portainer version >= 1.17.1
|
||||
// The EndpointID property is not available for these stacks, this API endpoint
|
||||
// can use the optional EndpointID query parameter to associate a valid endpoint identifier to the stack.
|
||||
|
@ -86,6 +71,27 @@ func (handler *Handler) stackUpdate(w http.ResponseWriter, r *http.Request) *htt
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
resourceControl, err := handler.ResourceControlService.ResourceControlByResourceID(stack.Name)
|
||||
if err != nil && err != portainer.ErrObjectNotFound {
|
||||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve a resource control associated to the stack", 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 {
|
||||
if !proxy.CanAccessStack(stack, resourceControl, securityContext.UserID, securityContext.UserMemberships) {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Access denied to resource", portainer.ErrResourceAccessDenied}
|
||||
}
|
||||
}
|
||||
|
||||
updateError := handler.updateAndDeployStack(r, stack, endpoint)
|
||||
if updateError != nil {
|
||||
return updateError
|
||||
|
|
|
@ -21,11 +21,11 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/tags",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.tagCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/tags",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.tagList))).Methods(http.MethodGet)
|
||||
h.Handle("/tags/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.tagDelete))).Methods(http.MethodDelete)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -23,13 +23,13 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/team_memberships",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipCreate))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipCreate))).Methods(http.MethodPost)
|
||||
h.Handle("/team_memberships",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipList))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipList))).Methods(http.MethodGet)
|
||||
h.Handle("/team_memberships/{id}",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipUpdate))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/team_memberships/{id}",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMembershipDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMembershipDelete))).Methods(http.MethodDelete)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -23,17 +23,17 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/teams",
|
||||
bouncer.AdministratorAccess(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.RestrictedAccess(httperror.LoggerHandler(h.teamInspect))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamInspect))).Methods(http.MethodGet)
|
||||
h.Handle("/teams/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.teamUpdate))).Methods(http.MethodPut)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamUpdate))).Methods(http.MethodPut)
|
||||
h.Handle("/teams/{id}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.teamDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamDelete))).Methods(http.MethodDelete)
|
||||
h.Handle("/teams/{id}/memberships",
|
||||
bouncer.RestrictedAccess(httperror.LoggerHandler(h.teamMemberships))).Methods(http.MethodGet)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.teamMemberships))).Methods(http.MethodGet)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -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.AdministratorAccess(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.AdministratorAccess(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.AdministratorAccess(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.AdministratorAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(h.templateManagementCheck(httperror.LoggerHandler(h.templateDelete)))).Methods(http.MethodDelete)
|
||||
return h
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,6 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/upload/tls/{certificate:(?:ca|cert|key)}",
|
||||
bouncer.AdministratorAccess(httperror.LoggerHandler(h.uploadTLS))).Methods(http.MethodPost)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.uploadTLS))).Methods(http.MethodPost)
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -31,19 +31,19 @@ func NewHandler(bouncer *security.RequestBouncer, rateLimiter *security.RateLimi
|
|||
Router: mux.NewRouter(),
|
||||
}
|
||||
h.Handle("/users",
|
||||
bouncer.RestrictedAccess(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.AdministratorAccess(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.AdministratorAccess(httperror.LoggerHandler(h.userDelete))).Methods(http.MethodDelete)
|
||||
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.userDelete))).Methods(http.MethodDelete)
|
||||
h.Handle("/users/{id}/memberships",
|
||||
bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.userMemberships))).Methods(http.MethodGet)
|
||||
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",
|
||||
|
|
|
@ -60,6 +60,23 @@ func (handler *Handler) userCreate(w http.ResponseWriter, r *http.Request) *http
|
|||
user = &portainer.User{
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -39,9 +39,9 @@ func (handler *Handler) websocketAttach(w http.ResponseWriter, r *http.Request)
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
params := &webSocketRequestParams{
|
||||
|
|
|
@ -46,9 +46,9 @@ func (handler *Handler) websocketExec(w http.ResponseWriter, r *http.Request) *h
|
|||
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find the endpoint associated to the stack inside the database", err}
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.EndpointAccess(r, endpoint)
|
||||
err = handler.requestBouncer.AuthorizedEndpointOperation(r, endpoint, true)
|
||||
if err != nil {
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", portainer.ErrEndpointAccessDenied}
|
||||
return &httperror.HandlerError{http.StatusForbidden, "Permission denied to access endpoint", err}
|
||||
}
|
||||
|
||||
params := &webSocketRequestParams{
|
||||
|
|
|
@ -25,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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue