mirror of
https://github.com/h44z/wg-portal.git
synced 2026-06-07 00:56:22 +00:00
Merge commit from fork
* sec: do not expose traffic stats to all users, harden origin check in websocket endpoint * add tests to validate new logic
This commit is contained in:
@@ -3,6 +3,7 @@ package handlers
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -19,23 +20,28 @@ type WebsocketEventBus interface {
|
||||
Unsubscribe(topic string, fn any) error
|
||||
}
|
||||
|
||||
type WebsocketPeerService interface {
|
||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||
}
|
||||
|
||||
type WebsocketEndpoint struct {
|
||||
authenticator Authenticator
|
||||
bus WebsocketEventBus
|
||||
peerService WebsocketPeerService
|
||||
|
||||
upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
func NewWebsocketEndpoint(cfg *config.Config, auth Authenticator, bus WebsocketEventBus) *WebsocketEndpoint {
|
||||
func NewWebsocketEndpoint(cfg *config.Config, auth Authenticator, bus WebsocketEventBus, peerService WebsocketPeerService) *WebsocketEndpoint {
|
||||
return &WebsocketEndpoint{
|
||||
authenticator: auth,
|
||||
bus: bus,
|
||||
peerService: peerService,
|
||||
upgrader: websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
origin := r.Header.Get("Origin")
|
||||
return strings.HasPrefix(origin, cfg.Web.ExternalUrl)
|
||||
return matchOrigin(cfg.Web.ExternalUrl, r.Header.Get("Origin"))
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -57,6 +63,8 @@ type wsMessage struct {
|
||||
|
||||
func (e WebsocketEndpoint) handleWebsocket() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
userInfo := domain.GetUserInfo(r.Context())
|
||||
|
||||
conn, err := e.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -74,9 +82,29 @@ func (e WebsocketEndpoint) handleWebsocket() http.HandlerFunc {
|
||||
}
|
||||
|
||||
peerStatsHandler := func(status domain.TrafficDelta) {
|
||||
if !userInfo.IsAdmin {
|
||||
// lookup peer user-info to validate ownership
|
||||
peer, err := e.peerService.GetPeer(ctx, domain.PeerIdentifier(status.EntityId))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if peer.UserIdentifier == "" {
|
||||
return // if peer is not assigned to any user, dont send stats
|
||||
}
|
||||
|
||||
if peer.UserIdentifier != userInfo.Id {
|
||||
return // only expose stats for own peers
|
||||
}
|
||||
}
|
||||
|
||||
_ = writeJSON(wsMessage{Type: "peer_stats", Data: status})
|
||||
}
|
||||
interfaceStatsHandler := func(status domain.TrafficDelta) {
|
||||
if !userInfo.IsAdmin {
|
||||
return // interface stats will only be exposed to admins
|
||||
}
|
||||
|
||||
_ = writeJSON(wsMessage{Type: "interface_stats", Data: status})
|
||||
}
|
||||
|
||||
@@ -98,3 +126,18 @@ func (e WebsocketEndpoint) handleWebsocket() http.HandlerFunc {
|
||||
<-ctx.Done()
|
||||
}
|
||||
}
|
||||
|
||||
func matchOrigin(externalBaseUrl, origin string) bool {
|
||||
originURL, err := url.Parse(origin)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
externalURL, err := url.Parse(externalBaseUrl)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return originURL.Scheme == externalURL.Scheme &&
|
||||
strings.EqualFold(originURL.Host, externalURL.Host)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user