mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
fix(performance): optimize performance for edge EE-3311 (#8040)
This commit is contained in:
parent
3d28a6f877
commit
dd0d1737b0
23 changed files with 577 additions and 164 deletions
|
@ -1,17 +1,23 @@
|
|||
package endpointedge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
httperror "github.com/portainer/libhttp/error"
|
||||
"github.com/portainer/libhttp/request"
|
||||
"github.com/portainer/libhttp/response"
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/http/middlewares"
|
||||
"github.com/portainer/portainer/api/internal/edge/cache"
|
||||
)
|
||||
|
||||
type stackStatusResponse struct {
|
||||
|
@ -64,9 +70,27 @@ type endpointEdgeStatusInspectResponse struct {
|
|||
// @failure 500 "Server error"
|
||||
// @router /endpoints/{id}/edge/status [get]
|
||||
func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
|
||||
endpoint, err := middlewares.FetchEndpoint(r)
|
||||
endpointID, err := request.RetrieveNumericRouteVariableValue(r, "id")
|
||||
if err != nil {
|
||||
return httperror.BadRequest("Unable to find an environment on request context", err)
|
||||
return httperror.BadRequest("Invalid environment identifier route variable", err)
|
||||
}
|
||||
|
||||
cachedResp := handler.respondFromCache(w, r, portainer.EndpointID(endpointID))
|
||||
if cachedResp {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := handler.DataStore.Endpoint().Heartbeat(portainer.EndpointID(endpointID)); !ok {
|
||||
return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", nil)
|
||||
}
|
||||
|
||||
endpoint, err := handler.DataStore.Endpoint().Endpoint(portainer.EndpointID(endpointID))
|
||||
if err != nil {
|
||||
if handler.DataStore.IsErrObjectNotFound(err) {
|
||||
return httperror.NotFound("Unable to find an environment with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
return httperror.InternalServerError("Unable to find an environment with the specified identifier inside the database", err)
|
||||
}
|
||||
|
||||
err = handler.requestBouncer.AuthorizedEdgeEndpointOperation(r, endpoint)
|
||||
|
@ -129,7 +153,7 @@ func (handler *Handler) endpointEdgeStatusInspect(w http.ResponseWriter, r *http
|
|||
}
|
||||
statusResponse.Stacks = edgeStacksStatus
|
||||
|
||||
return response.JSON(w, statusResponse)
|
||||
return cacheResponse(w, endpoint.ID, statusResponse)
|
||||
}
|
||||
|
||||
func parseAgentPlatform(r *http.Request) (portainer.EndpointType, error) {
|
||||
|
@ -191,17 +215,75 @@ func (handler *Handler) buildEdgeStacks(endpointID portainer.EndpointID) ([]stac
|
|||
|
||||
edgeStacksStatus := []stackStatusResponse{}
|
||||
for stackID := range relation.EdgeStacks {
|
||||
stack, err := handler.DataStore.EdgeStack().EdgeStack(stackID)
|
||||
if err != nil {
|
||||
version, ok := handler.DataStore.EdgeStack().EdgeStackVersion(stackID)
|
||||
if !ok {
|
||||
return nil, httperror.InternalServerError("Unable to retrieve edge stack from the database", err)
|
||||
}
|
||||
|
||||
stackStatus := stackStatusResponse{
|
||||
ID: stack.ID,
|
||||
Version: stack.Version,
|
||||
ID: stackID,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
edgeStacksStatus = append(edgeStacksStatus, stackStatus)
|
||||
}
|
||||
|
||||
return edgeStacksStatus, nil
|
||||
}
|
||||
|
||||
func cacheResponse(w http.ResponseWriter, endpointID portainer.EndpointID, statusResponse endpointEdgeStatusInspectResponse) *httperror.HandlerError {
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
httpErr := response.JSON(rr, statusResponse)
|
||||
if httpErr != nil {
|
||||
return httpErr
|
||||
}
|
||||
|
||||
h := fnv.New32a()
|
||||
h.Write(rr.Body.Bytes())
|
||||
etag := strconv.FormatUint(uint64(h.Sum32()), 16)
|
||||
|
||||
cache.Set(endpointID, []byte(etag))
|
||||
|
||||
resp := rr.Result()
|
||||
|
||||
for k, vs := range resp.Header {
|
||||
for _, v := range vs {
|
||||
w.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set("ETag", etag)
|
||||
io.Copy(w, resp.Body)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (handler *Handler) respondFromCache(w http.ResponseWriter, r *http.Request, endpointID portainer.EndpointID) bool {
|
||||
inmHeader := r.Header.Get("If-None-Match")
|
||||
etags := strings.Split(inmHeader, ",")
|
||||
|
||||
if len(inmHeader) == 0 || etags[0] == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
cachedETag, ok := cache.Get(endpointID)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, etag := range etags {
|
||||
if !bytes.Equal([]byte(etag), cachedETag) {
|
||||
continue
|
||||
}
|
||||
|
||||
handler.DataStore.Endpoint().UpdateHeartbeat(endpointID)
|
||||
|
||||
w.Header().Set("ETag", etag)
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -158,7 +158,7 @@ func TestMissingEdgeIdentifier(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpointID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpointID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ func TestWithEndpoints(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", test.endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", test.endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -231,7 +231,7 @@ func TestLastCheckInDateIncreases(t *testing.T) {
|
|||
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -279,7 +279,7 @@ func TestEmptyEdgeIdWithAgentPlatformHeader(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -348,7 +348,7 @@ func TestEdgeStackStatus(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
@ -418,7 +418,7 @@ func TestEdgeJobsResponse(t *testing.T) {
|
|||
|
||||
handler.ReverseTunnelService.AddEdgeJob(endpoint.ID, &edgeJob)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/%d/edge/status", endpoint.ID), nil)
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/endpoints/%d/edge/status", endpoint.ID), nil)
|
||||
if err != nil {
|
||||
t.Fatal("request error:", err)
|
||||
}
|
||||
|
|
|
@ -31,14 +31,16 @@ func NewHandler(bouncer *security.RequestBouncer, dataStore dataservices.DataSto
|
|||
ReverseTunnelService: reverseTunnelService,
|
||||
}
|
||||
|
||||
endpointRouter := h.PathPrefix("/{id}").Subrouter()
|
||||
h.Handle("/api/endpoints/{id}/edge/status", bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStatusInspect))).Methods(http.MethodGet)
|
||||
|
||||
endpointRouter := h.PathPrefix("/api/endpoints/{id}").Subrouter()
|
||||
endpointRouter.Use(middlewares.WithEndpoint(dataStore.Endpoint(), "id"))
|
||||
|
||||
endpointRouter.PathPrefix("/edge/status").Handler(
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStatusInspect))).Methods(http.MethodGet)
|
||||
endpointRouter.PathPrefix("/edge/stacks/{stackId}").Handler(
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeStackInspect))).Methods(http.MethodGet)
|
||||
|
||||
endpointRouter.PathPrefix("/edge/jobs/{jobID}/logs").Handler(
|
||||
bouncer.PublicAccess(httperror.LoggerHandler(h.endpointEdgeJobsLogs))).Methods(http.MethodPost)
|
||||
|
||||
return h
|
||||
}
|
||||
|
|
|
@ -28,16 +28,10 @@ func (handler *Handler) endpointCreateGlobalKey(w http.ResponseWriter, r *http.R
|
|||
|
||||
// Search for existing endpoints for the given edgeID
|
||||
|
||||
endpoints, err := handler.DataStore.Endpoint().Endpoints()
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable to retrieve the endpoints from the database", err)
|
||||
endpointID, ok := handler.DataStore.Endpoint().EndpointIDByEdgeID(edgeID)
|
||||
if ok {
|
||||
return response.JSON(w, endpointCreateGlobalKeyResponse{endpointID})
|
||||
}
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.EdgeID == edgeID {
|
||||
return response.JSON(w, endpointCreateGlobalKeyResponse{endpoint.ID})
|
||||
}
|
||||
}
|
||||
|
||||
return httperror.NotFound("Unable to find the endpoint in the database", err)
|
||||
return httperror.NotFound("Unable to find the endpoint in the database", nil)
|
||||
}
|
||||
|
|
|
@ -153,38 +153,39 @@ func (handler *Handler) filterEndpointsByQuery(filteredEndpoints []portainer.End
|
|||
}
|
||||
|
||||
func filterEndpointsByGroupIDs(endpoints []portainer.Endpoint, endpointGroupIDs []portainer.EndpointGroupID) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if slices.Contains(endpointGroupIDs, endpoint.GroupID) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func filterEndpointsBySearchCriteria(endpoints []portainer.Endpoint, endpointGroups []portainer.EndpointGroup, tagsMap map[portainer.TagID]string, searchCriteria string) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
endpointTags := convertTagIDsToTags(tagsMap, endpoint.TagIDs)
|
||||
if endpointMatchSearchCriteria(&endpoint, endpointTags, searchCriteria) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if endpointGroupMatchSearchCriteria(&endpoint, endpointGroups, tagsMap, searchCriteria) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func filterEndpointsByStatuses(endpoints []portainer.Endpoint, statuses []portainer.EndpointStatus, settings *portainer.Settings) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
status := endpoint.Status
|
||||
if endpointutils.IsEdgeEndpoint(&endpoint) {
|
||||
|
@ -205,11 +206,12 @@ func filterEndpointsByStatuses(endpoints []portainer.Endpoint, statuses []portai
|
|||
}
|
||||
|
||||
if slices.Contains(statuses, status) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, searchCriteria string) bool {
|
||||
|
@ -226,6 +228,7 @@ func endpointMatchSearchCriteria(endpoint *portainer.Endpoint, tags []string, se
|
|||
} else if endpoint.Status == portainer.EndpointStatusDown && searchCriteria == "down" {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
return true
|
||||
|
@ -241,6 +244,7 @@ func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGrou
|
|||
if strings.Contains(strings.ToLower(group.Name), searchCriteria) {
|
||||
return true
|
||||
}
|
||||
|
||||
tags := convertTagIDsToTags(tagsMap, group.TagIDs)
|
||||
for _, tag := range tags {
|
||||
if strings.Contains(strings.ToLower(tag), searchCriteria) {
|
||||
|
@ -254,30 +258,32 @@ func endpointGroupMatchSearchCriteria(endpoint *portainer.Endpoint, endpointGrou
|
|||
}
|
||||
|
||||
func filterEndpointsByTypes(endpoints []portainer.Endpoint, endpointTypes []portainer.EndpointType) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
typeSet := map[portainer.EndpointType]bool{}
|
||||
for _, endpointType := range endpointTypes {
|
||||
typeSet[portainer.EndpointType(endpointType)] = true
|
||||
}
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if typeSet[endpoint.Type] {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func filterEndpointsByEdgeDevice(endpoints []portainer.Endpoint, edgeDevice bool, untrusted bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if shouldReturnEdgeDevice(endpoint, edgeDevice, untrusted) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func shouldReturnEdgeDevice(endpoint portainer.Endpoint, edgeDeviceParam bool, untrustedParam bool) bool {
|
||||
|
@ -293,19 +299,21 @@ func shouldReturnEdgeDevice(endpoint portainer.Endpoint, edgeDeviceParam bool, u
|
|||
}
|
||||
|
||||
func convertTagIDsToTags(tagsMap map[portainer.TagID]string, tagIDs []portainer.TagID) []string {
|
||||
tags := make([]string, 0)
|
||||
tags := make([]string, 0, len(tagIDs))
|
||||
|
||||
for _, tagID := range tagIDs {
|
||||
tags = append(tags, tagsMap[tagID])
|
||||
}
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
func filteredEndpointsByTags(endpoints []portainer.Endpoint, tagIDs []portainer.TagID, endpointGroups []portainer.EndpointGroup, partialMatch bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
endpointGroup := getEndpointGroup(endpoint.GroupID, endpointGroups)
|
||||
endpointMatched := false
|
||||
|
||||
if partialMatch {
|
||||
endpointMatched = endpointPartialMatchTags(endpoint, endpointGroup, tagIDs)
|
||||
} else {
|
||||
|
@ -313,27 +321,33 @@ func filteredEndpointsByTags(endpoints []portainer.Endpoint, tagIDs []portainer.
|
|||
}
|
||||
|
||||
if endpointMatched {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func endpointPartialMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.EndpointGroup, tagIDs []portainer.TagID) bool {
|
||||
tagSet := make(map[portainer.TagID]bool)
|
||||
tagSet := make(map[portainer.TagID]bool, len(tagIDs))
|
||||
|
||||
for _, tagID := range tagIDs {
|
||||
tagSet[tagID] = true
|
||||
}
|
||||
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if tagSet[tagID] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, tagID := range endpointGroup.TagIDs {
|
||||
if tagSet[tagID] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -342,34 +356,37 @@ func endpointFullMatchTags(endpoint portainer.Endpoint, endpointGroup portainer.
|
|||
for _, tagID := range tagIDs {
|
||||
missingTags[tagID] = true
|
||||
}
|
||||
|
||||
for _, tagID := range endpoint.TagIDs {
|
||||
if missingTags[tagID] {
|
||||
delete(missingTags, tagID)
|
||||
}
|
||||
}
|
||||
|
||||
for _, tagID := range endpointGroup.TagIDs {
|
||||
if missingTags[tagID] {
|
||||
delete(missingTags, tagID)
|
||||
}
|
||||
}
|
||||
|
||||
return len(missingTags) == 0
|
||||
}
|
||||
|
||||
func filteredEndpointsByIds(endpoints []portainer.Endpoint, ids []portainer.EndpointID) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
idsSet := make(map[portainer.EndpointID]bool)
|
||||
idsSet := make(map[portainer.EndpointID]bool, len(ids))
|
||||
for _, id := range ids {
|
||||
idsSet[id] = true
|
||||
}
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if idsSet[endpoint.ID] {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
return endpoints[:n]
|
||||
|
||||
}
|
||||
|
||||
|
@ -378,25 +395,27 @@ func filterEndpointsByName(endpoints []portainer.Endpoint, name string) []portai
|
|||
return endpoints
|
||||
}
|
||||
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if endpoint.Name == name {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func filter(endpoints []portainer.Endpoint, predicate func(endpoint portainer.Endpoint) bool) []portainer.Endpoint {
|
||||
filteredEndpoints := make([]portainer.Endpoint, 0)
|
||||
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
if predicate(endpoint) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
return filteredEndpoints
|
||||
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
func getArrayQueryParameter(r *http.Request, parameter string) []string {
|
||||
|
|
|
@ -132,7 +132,7 @@ func Test_Filter_edgeDeviceFilter(t *testing.T) {
|
|||
func runTests(tests []filterTest, t *testing.T, handler *Handler, endpoints []portainer.Endpoint) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.title, func(t *testing.T) {
|
||||
runTest(t, test, handler, endpoints)
|
||||
runTest(t, test, handler, append([]portainer.Endpoint{}, endpoints...))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,6 +161,8 @@ type Handler struct {
|
|||
// ServeHTTP delegates a request to the appropriate subhandler.
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, "/api/endpoints") && strings.Contains(r.URL.Path, "/edge/"):
|
||||
h.EndpointEdgeHandler.ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/auth"):
|
||||
http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/backup"):
|
||||
|
@ -175,8 +177,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api", h.EdgeGroupsHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/edge_jobs"):
|
||||
http.StripPrefix("/api", h.EdgeJobsHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/edge_stacks"):
|
||||
http.StripPrefix("/api", h.EdgeStacksHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/edge_templates"):
|
||||
http.StripPrefix("/api", h.EdgeTemplatesHandler).ServeHTTP(w, r)
|
||||
case strings.HasPrefix(r.URL.Path, "/api/endpoint_groups"):
|
||||
|
@ -200,8 +200,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/agent/"):
|
||||
http.StripPrefix("/api/endpoints", h.EndpointProxyHandler).ServeHTTP(w, r)
|
||||
case strings.Contains(r.URL.Path, "/edge/"):
|
||||
http.StripPrefix("/api/endpoints", h.EndpointEdgeHandler).ServeHTTP(w, r)
|
||||
default:
|
||||
http.StripPrefix("/api", h.EndpointHandler).ServeHTTP(w, r)
|
||||
}
|
||||
|
|
|
@ -49,8 +49,7 @@ func NewRequestBouncer(dataStore dataservices.DataStore, jwtService dataservices
|
|||
// PublicAccess defines a security check for public API environments(endpoints).
|
||||
// No authentication is required to access these environments(endpoints).
|
||||
func (bouncer *RequestBouncer) PublicAccess(h http.Handler) http.Handler {
|
||||
h = mwSecureHeaders(h)
|
||||
return h
|
||||
return mwSecureHeaders(h)
|
||||
}
|
||||
|
||||
// AdminAccess defines a security check for API environments(endpoints) that require an authorization check.
|
||||
|
@ -375,8 +374,8 @@ func extractAPIKey(r *http.Request) (apikey string, ok bool) {
|
|||
// mwSecureHeaders provides secure headers middleware for handlers.
|
||||
func mwSecureHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("X-XSS-Protection", "1; mode=block")
|
||||
w.Header().Add("X-Content-Type-Options", "nosniff")
|
||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,26 +11,28 @@ func FilterUserTeams(teams []portainer.Team, context *RestrictedRequestContext)
|
|||
return teams
|
||||
}
|
||||
|
||||
teamsAccessableToUser := make([]portainer.Team, 0)
|
||||
n := 0
|
||||
for _, membership := range context.UserMemberships {
|
||||
for _, team := range teams {
|
||||
if team.ID == membership.TeamID {
|
||||
teamsAccessableToUser = append(teamsAccessableToUser, team)
|
||||
teams[n] = team
|
||||
n++
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return teamsAccessableToUser
|
||||
return teams[:n]
|
||||
}
|
||||
|
||||
// FilterLeaderTeams filters teams based on user role.
|
||||
// Team leaders only have access to team they lead.
|
||||
func FilterLeaderTeams(teams []portainer.Team, context *RestrictedRequestContext) []portainer.Team {
|
||||
filteredTeams := []portainer.Team{}
|
||||
n := 0
|
||||
|
||||
if !context.IsTeamLeader {
|
||||
return filteredTeams
|
||||
return teams[:n]
|
||||
}
|
||||
|
||||
leaderSet := map[portainer.TeamID]bool{}
|
||||
|
@ -42,11 +44,12 @@ func FilterLeaderTeams(teams []portainer.Team, context *RestrictedRequestContext
|
|||
|
||||
for _, team := range teams {
|
||||
if leaderSet[team.ID] {
|
||||
filteredTeams = append(filteredTeams, team)
|
||||
teams[n] = team
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredTeams
|
||||
return teams[:n]
|
||||
}
|
||||
|
||||
// FilterUsers filters users based on user role.
|
||||
|
@ -56,14 +59,15 @@ func FilterUsers(users []portainer.User, context *RestrictedRequestContext) []po
|
|||
return users
|
||||
}
|
||||
|
||||
nonAdmins := make([]portainer.User, 0)
|
||||
n := 0
|
||||
for _, user := range users {
|
||||
if user.Role != portainer.AdministratorRole {
|
||||
nonAdmins = append(nonAdmins, user)
|
||||
users[n] = user
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return nonAdmins
|
||||
return users[:n]
|
||||
}
|
||||
|
||||
// FilterRegistries filters registries based on user role and team memberships.
|
||||
|
@ -73,52 +77,53 @@ func FilterRegistries(registries []portainer.Registry, user *portainer.User, tea
|
|||
return registries
|
||||
}
|
||||
|
||||
filteredRegistries := []portainer.Registry{}
|
||||
n := 0
|
||||
for _, registry := range registries {
|
||||
if AuthorizedRegistryAccess(®istry, user, teamMemberships, endpointID) {
|
||||
filteredRegistries = append(filteredRegistries, registry)
|
||||
registries[n] = registry
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredRegistries
|
||||
return registries[:n]
|
||||
}
|
||||
|
||||
// FilterEndpoints filters environments(endpoints) based on user role and team memberships.
|
||||
// Non administrator only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
|
||||
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
|
||||
filteredEndpoints := endpoints
|
||||
if context.IsAdmin {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
if !context.IsAdmin {
|
||||
filteredEndpoints = make([]portainer.Endpoint, 0)
|
||||
n := 0
|
||||
for _, endpoint := range endpoints {
|
||||
endpointGroup := getAssociatedGroup(&endpoint, groups)
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointGroup := getAssociatedGroup(&endpoint, groups)
|
||||
|
||||
if AuthorizedEndpointAccess(&endpoint, endpointGroup, context.UserID, context.UserMemberships) {
|
||||
filteredEndpoints = append(filteredEndpoints, endpoint)
|
||||
}
|
||||
if AuthorizedEndpointAccess(&endpoint, endpointGroup, context.UserID, context.UserMemberships) {
|
||||
endpoints[n] = endpoint
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpoints
|
||||
return endpoints[:n]
|
||||
}
|
||||
|
||||
// FilterEndpointGroups filters environment(endpoint) groups based on user role and team memberships.
|
||||
// Non administrator users only have access to authorized environment(endpoint) groups.
|
||||
func FilterEndpointGroups(endpointGroups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.EndpointGroup {
|
||||
filteredEndpointGroups := endpointGroups
|
||||
if context.IsAdmin {
|
||||
return endpointGroups
|
||||
}
|
||||
|
||||
if !context.IsAdmin {
|
||||
filteredEndpointGroups = make([]portainer.EndpointGroup, 0)
|
||||
|
||||
for _, group := range endpointGroups {
|
||||
if authorizedEndpointGroupAccess(&group, context.UserID, context.UserMemberships) {
|
||||
filteredEndpointGroups = append(filteredEndpointGroups, group)
|
||||
}
|
||||
n := 0
|
||||
for _, group := range endpointGroups {
|
||||
if authorizedEndpointGroupAccess(&group, context.UserID, context.UserMemberships) {
|
||||
endpointGroups[n] = group
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
return filteredEndpointGroups
|
||||
return endpointGroups[:n]
|
||||
}
|
||||
|
||||
func getAssociatedGroup(endpoint *portainer.Endpoint, groups []portainer.EndpointGroup) *portainer.EndpointGroup {
|
||||
|
@ -127,5 +132,6 @@ func getAssociatedGroup(endpoint *portainer.Endpoint, groups []portainer.Endpoin
|
|||
return &group
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -341,8 +341,9 @@ func (server *Server) Start() error {
|
|||
|
||||
log.Info().Str("bind_address", server.BindAddressHTTPS).Msg("starting HTTPS server")
|
||||
httpsServer := &http.Server{
|
||||
Addr: server.BindAddressHTTPS,
|
||||
Handler: handler,
|
||||
Addr: server.BindAddressHTTPS,
|
||||
Handler: handler,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), // Disable HTTP/2
|
||||
}
|
||||
|
||||
httpsServer.TLSConfig = crypto.CreateServerTLSConfiguration()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue