mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-27 02:24:30 +01:00
d0c6d88971
This PR simplifies Store and FileStore by keeping just the Get and Save account methods. The AccountManager operates mostly around a single account, so it makes sense to fetch the whole account object from the store.
417 lines
12 KiB
Go
417 lines
12 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/gorilla/mux"
|
|
"github.com/netbirdio/netbird/management/server"
|
|
"github.com/netbirdio/netbird/management/server/http/api"
|
|
"github.com/netbirdio/netbird/management/server/jwtclaims"
|
|
"github.com/netbirdio/netbird/route"
|
|
log "github.com/sirupsen/logrus"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"net/http"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// Routes is the routes handler of the account
|
|
type Routes struct {
|
|
jwtExtractor jwtclaims.ClaimsExtractor
|
|
accountManager server.AccountManager
|
|
authAudience string
|
|
}
|
|
|
|
// NewRoutes returns a new instance of Routes handler
|
|
func NewRoutes(accountManager server.AccountManager, authAudience string) *Routes {
|
|
return &Routes{
|
|
accountManager: accountManager,
|
|
authAudience: authAudience,
|
|
jwtExtractor: *jwtclaims.NewClaimsExtractor(nil),
|
|
}
|
|
}
|
|
|
|
// GetAllRoutesHandler returns the list of routes for the account
|
|
func (h *Routes) GetAllRoutesHandler(w http.ResponseWriter, r *http.Request) {
|
|
account, user, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
if err != nil {
|
|
log.Error(err)
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
routes, err := h.accountManager.ListRoutes(account.Id, user.Id)
|
|
if err != nil {
|
|
log.Error(err)
|
|
if e, ok := server.FromError(err); ok {
|
|
switch e.Type() {
|
|
case server.PermissionDenied:
|
|
http.Error(w, e.Error(), http.StatusForbidden)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
apiRoutes := make([]*api.Route, 0)
|
|
for _, r := range routes {
|
|
apiRoutes = append(apiRoutes, toRouteResponse(account, r))
|
|
}
|
|
|
|
writeJSONObject(w, apiRoutes)
|
|
}
|
|
|
|
// CreateRouteHandler handles route creation request
|
|
func (h *Routes) CreateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|
account, _, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var req api.PostApiRoutesJSONRequestBody
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
peerKey := req.Peer
|
|
if req.Peer != "" {
|
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
|
if err != nil {
|
|
log.Error(err)
|
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
|
return
|
|
}
|
|
peerKey = peer.Key
|
|
}
|
|
|
|
_, newPrefix, err := route.ParseNetwork(req.Network)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s", req.Network), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
|
http.Error(w, fmt.Sprintf("identifier should be between 1 and %d", route.MaxNetIDChar), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
newRoute, err := h.accountManager.CreateRoute(account.Id, newPrefix.String(), peerKey, req.Description, req.NetworkId, req.Masquerade, req.Metric, req.Enabled)
|
|
if err != nil {
|
|
log.Error(err)
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
resp := toRouteResponse(account, newRoute)
|
|
|
|
writeJSONObject(w, &resp)
|
|
}
|
|
|
|
// UpdateRouteHandler handles update to a route identified by a given ID
|
|
func (h *Routes) UpdateRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|
account, user, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
routeID := vars["id"]
|
|
if len(routeID) == 0 {
|
|
http.Error(w, "invalid route Id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
_, err = h.accountManager.GetRoute(account.Id, routeID, user.Id)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("couldn't find route for ID %s", routeID), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
var req api.PutApiRoutesIdJSONRequestBody
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
prefixType, newPrefix, err := route.ParseNetwork(req.Network)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("couldn't parse update prefix %s for route ID %s", req.Network, routeID), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
peerKey := req.Peer
|
|
if req.Peer != "" {
|
|
peer, err := h.accountManager.GetPeerByIP(account.Id, req.Peer)
|
|
if err != nil {
|
|
log.Error(err)
|
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
|
return
|
|
}
|
|
peerKey = peer.Key
|
|
}
|
|
|
|
if utf8.RuneCountInString(req.NetworkId) > route.MaxNetIDChar || req.NetworkId == "" {
|
|
http.Error(w, fmt.Sprintf("identifier should be between 1 and %d", route.MaxNetIDChar), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
newRoute := &route.Route{
|
|
ID: routeID,
|
|
Network: newPrefix,
|
|
NetID: req.NetworkId,
|
|
NetworkType: prefixType,
|
|
Masquerade: req.Masquerade,
|
|
Peer: peerKey,
|
|
Metric: req.Metric,
|
|
Description: req.Description,
|
|
Enabled: req.Enabled,
|
|
}
|
|
|
|
err = h.accountManager.SaveRoute(account.Id, newRoute)
|
|
if err != nil {
|
|
log.Errorf("failed updating route \"%s\" under account %s %v", routeID, account.Id, err)
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
resp := toRouteResponse(account, newRoute)
|
|
|
|
writeJSONObject(w, &resp)
|
|
}
|
|
|
|
// PatchRouteHandler handles patch updates to a route identified by a given ID
|
|
func (h *Routes) PatchRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|
account, user, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
vars := mux.Vars(r)
|
|
routeID := vars["id"]
|
|
if len(routeID) == 0 {
|
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
_, err = h.accountManager.GetRoute(account.Id, routeID, user.Id)
|
|
if err != nil {
|
|
log.Error(err)
|
|
http.Error(w, fmt.Sprintf("couldn't find route ID %s", routeID), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
var req api.PatchApiRoutesIdJSONRequestBody
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if len(req) == 0 {
|
|
http.Error(w, "no patch instruction received", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var operations []server.RouteUpdateOperation
|
|
|
|
for _, patch := range req {
|
|
switch patch.Path {
|
|
case api.RoutePatchOperationPathNetwork:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Network field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRouteNetwork,
|
|
Values: patch.Value,
|
|
})
|
|
case api.RoutePatchOperationPathDescription:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Description field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRouteDescription,
|
|
Values: patch.Value,
|
|
})
|
|
case api.RoutePatchOperationPathNetworkId:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Network Identifier field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRouteNetworkIdentifier,
|
|
Values: patch.Value,
|
|
})
|
|
case api.RoutePatchOperationPathPeer:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Peer field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
if len(patch.Value) > 1 {
|
|
http.Error(w, fmt.Sprintf("Value field only accepts 1 value, got %d", len(patch.Value)),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
peerValue := patch.Value
|
|
if patch.Value[0] != "" {
|
|
peer, err := h.accountManager.GetPeerByIP(account.Id, patch.Value[0])
|
|
if err != nil {
|
|
log.Error(err)
|
|
http.Redirect(w, r, "/", http.StatusUnprocessableEntity)
|
|
return
|
|
}
|
|
peerValue = []string{peer.Key}
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRoutePeer,
|
|
Values: peerValue,
|
|
})
|
|
case api.RoutePatchOperationPathMetric:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Metric field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRouteMetric,
|
|
Values: patch.Value,
|
|
})
|
|
case api.RoutePatchOperationPathMasquerade:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Masquerade field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRouteMasquerade,
|
|
Values: patch.Value,
|
|
})
|
|
case api.RoutePatchOperationPathEnabled:
|
|
if patch.Op != api.RoutePatchOperationOpReplace {
|
|
http.Error(w, fmt.Sprintf("Enabled field only accepts replace operation, got %s", patch.Op),
|
|
http.StatusBadRequest)
|
|
return
|
|
}
|
|
operations = append(operations, server.RouteUpdateOperation{
|
|
Type: server.UpdateRouteEnabled,
|
|
Values: patch.Value,
|
|
})
|
|
default:
|
|
http.Error(w, "invalid patch path", http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
route, err := h.accountManager.UpdateRoute(account.Id, routeID, operations)
|
|
|
|
if err != nil {
|
|
errStatus, ok := status.FromError(err)
|
|
if ok && errStatus.Code() == codes.Internal {
|
|
http.Error(w, errStatus.String(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if ok && errStatus.Code() == codes.NotFound {
|
|
http.Error(w, errStatus.String(), http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
if ok && errStatus.Code() == codes.InvalidArgument {
|
|
http.Error(w, errStatus.String(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
log.Errorf("failed updating route %s under account %s %v", routeID, account.Id, err)
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
resp := toRouteResponse(account, route)
|
|
|
|
writeJSONObject(w, &resp)
|
|
}
|
|
|
|
// DeleteRouteHandler handles route deletion request
|
|
func (h *Routes) DeleteRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|
account, _, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
routeID := mux.Vars(r)["id"]
|
|
if len(routeID) == 0 {
|
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
err = h.accountManager.DeleteRoute(account.Id, routeID)
|
|
if err != nil {
|
|
errStatus, ok := status.FromError(err)
|
|
if ok && errStatus.Code() == codes.NotFound {
|
|
http.Error(w, fmt.Sprintf("route %s not found under account %s", routeID, account.Id), http.StatusNotFound)
|
|
return
|
|
}
|
|
log.Errorf("failed delete route %s under account %s %v", routeID, account.Id, err)
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
writeJSONObject(w, "")
|
|
}
|
|
|
|
// GetRouteHandler handles a route Get request identified by ID
|
|
func (h *Routes) GetRouteHandler(w http.ResponseWriter, r *http.Request) {
|
|
account, user, err := getJWTAccount(h.accountManager, h.jwtExtractor, h.authAudience, r)
|
|
if err != nil {
|
|
http.Redirect(w, r, "/", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
routeID := mux.Vars(r)["id"]
|
|
if len(routeID) == 0 {
|
|
http.Error(w, "invalid route ID", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
foundRoute, err := h.accountManager.GetRoute(account.Id, routeID, user.Id)
|
|
if err != nil {
|
|
http.Error(w, "route not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
writeJSONObject(w, toRouteResponse(account, foundRoute))
|
|
}
|
|
|
|
func toRouteResponse(account *server.Account, serverRoute *route.Route) *api.Route {
|
|
var peerIP string
|
|
if serverRoute.Peer != "" {
|
|
peer, found := account.Peers[serverRoute.Peer]
|
|
if !found {
|
|
panic("peer ID not found")
|
|
}
|
|
peerIP = peer.IP.String()
|
|
}
|
|
|
|
return &api.Route{
|
|
Id: serverRoute.ID,
|
|
Description: serverRoute.Description,
|
|
NetworkId: serverRoute.NetID,
|
|
Enabled: serverRoute.Enabled,
|
|
Peer: peerIP,
|
|
Network: serverRoute.Network.String(),
|
|
NetworkType: serverRoute.NetworkType.String(),
|
|
Masquerade: serverRoute.Masquerade,
|
|
Metric: serverRoute.Metric,
|
|
}
|
|
}
|