mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
fix(api): add an authenticated access policy to the websocket endpoint (#1979)
* fix(api): add an authenticated access policy to the websocket endpoint * refactor(api): centralize EndpointAccess validation * feat(api): validate id query parameter for the /websocket/exec endpoint
This commit is contained in:
parent
f3ce5c25de
commit
da5a430b8c
14 changed files with 100 additions and 124 deletions
|
@ -14,9 +14,19 @@ type (
|
|||
jwtService portainer.JWTService
|
||||
userService portainer.UserService
|
||||
teamMembershipService portainer.TeamMembershipService
|
||||
endpointGroupService portainer.EndpointGroupService
|
||||
authDisabled bool
|
||||
}
|
||||
|
||||
// RequestBouncerParams represents the required parameters to create a new RequestBouncer instance.
|
||||
RequestBouncerParams struct {
|
||||
JWTService portainer.JWTService
|
||||
UserService portainer.UserService
|
||||
TeamMembershipService portainer.TeamMembershipService
|
||||
EndpointGroupService portainer.EndpointGroupService
|
||||
AuthDisabled bool
|
||||
}
|
||||
|
||||
// RestrictedRequestContext is a data structure containing information
|
||||
// used in RestrictedAccess
|
||||
RestrictedRequestContext struct {
|
||||
|
@ -28,12 +38,13 @@ type (
|
|||
)
|
||||
|
||||
// NewRequestBouncer initializes a new RequestBouncer
|
||||
func NewRequestBouncer(jwtService portainer.JWTService, userService portainer.UserService, teamMembershipService portainer.TeamMembershipService, authDisabled bool) *RequestBouncer {
|
||||
func NewRequestBouncer(parameters *RequestBouncerParams) *RequestBouncer {
|
||||
return &RequestBouncer{
|
||||
jwtService: jwtService,
|
||||
userService: userService,
|
||||
teamMembershipService: teamMembershipService,
|
||||
authDisabled: authDisabled,
|
||||
jwtService: parameters.JWTService,
|
||||
userService: parameters.UserService,
|
||||
teamMembershipService: parameters.TeamMembershipService,
|
||||
endpointGroupService: parameters.EndpointGroupService,
|
||||
authDisabled: parameters.AuthDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +81,36 @@ func (bouncer *RequestBouncer) AdministratorAccess(h http.Handler) http.Handler
|
|||
return h
|
||||
}
|
||||
|
||||
// EndpointAccess retrieves the JWT token from the request context and verifies
|
||||
// that the user can access the specified endpoint.
|
||||
// An error is returned when access is denied.
|
||||
func (bouncer *RequestBouncer) EndpointAccess(r *http.Request, endpoint *portainer.Endpoint) error {
|
||||
tokenData, err := RetrieveTokenData(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tokenData.Role == portainer.AdministratorRole {
|
||||
return nil
|
||||
}
|
||||
|
||||
memberships, err := bouncer.teamMembershipService.TeamMembershipsByUserID(tokenData.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group, err := bouncer.endpointGroupService.EndpointGroup(endpoint.GroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !authorizedEndpointAccess(endpoint, group, tokenData.ID, memberships) {
|
||||
return portainer.ErrEndpointAccessDenied
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// mwSecureHeaders provides secure headers middleware for handlers.
|
||||
func mwSecureHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -120,6 +161,10 @@ func (bouncer *RequestBouncer) mwCheckAuthentication(next http.Handler) http.Han
|
|||
if !bouncer.authDisabled {
|
||||
var token string
|
||||
|
||||
// Optionally, token might be set via the "token" query parameter.
|
||||
// For example, in websocket requests
|
||||
token = r.URL.Query().Get("token")
|
||||
|
||||
// Get token from the Authorization header
|
||||
tokens, ok := r.Header["Authorization"]
|
||||
if ok && len(tokens) >= 1 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue