package controller import ( "encoding/json" "errors" "fmt" "net" "net/http" "strings" "github.com/google/uuid" "github.com/gorilla/mux" "golang.org/x/exp/slog" "github.com/gravitl/netmaker/database" "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/logic" "github.com/gravitl/netmaker/logic/acls" "github.com/gravitl/netmaker/models" "github.com/gravitl/netmaker/mq" "github.com/gravitl/netmaker/servercfg" ) func networkHandlers(r *mux.Router) { r.HandleFunc("/api/networks", logic.SecurityCheck(true, http.HandlerFunc(getNetworks))).Methods(http.MethodGet) r.HandleFunc("/api/networks", logic.SecurityCheck(true, checkFreeTierLimits(limitChoiceNetworks, http.HandlerFunc(createNetwork)))).Methods(http.MethodPost) r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(getNetwork))).Methods(http.MethodGet) r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(deleteNetwork))).Methods(http.MethodDelete) r.HandleFunc("/api/networks/{networkname}", logic.SecurityCheck(true, http.HandlerFunc(updateNetwork))).Methods(http.MethodPut) // ACLs r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACL))).Methods(http.MethodPut) r.HandleFunc("/api/networks/{networkname}/acls/v2", logic.SecurityCheck(true, http.HandlerFunc(updateNetworkACLv2))).Methods(http.MethodPut) r.HandleFunc("/api/networks/{networkname}/acls", logic.SecurityCheck(true, http.HandlerFunc(getNetworkACL))).Methods(http.MethodGet) } // swagger:route GET /api/networks networks getNetworks // // Lists all networks. // // Schemes: https // // Security: // oauth // // Responses: // 200: getNetworksSliceResponse func getNetworks(w http.ResponseWriter, r *http.Request) { var err error allnetworks, err := logic.GetNetworks() if err != nil && !database.IsEmptyRecord(err) { slog.Error("failed to fetch networks", "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "fetched networks.") logic.SortNetworks(allnetworks[:]) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(allnetworks) } // swagger:route GET /api/networks/{networkname} networks getNetwork // // Get a network. // // Schemes: https // // Security: // oauth // // Responses: // 200: networkBodyResponse func getNetwork(w http.ResponseWriter, r *http.Request) { // set header. w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) netname := params["networkname"] network, err := logic.GetNetwork(netname) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch network [%s] info: %v", netname, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "fetched network", netname) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(network) } // swagger:route PUT /api/networks/{networkname}/acls networks updateNetworkACL // // Update a network ACL (Access Control List). // // Schemes: https // // Security: // oauth // // Responses: // 200: aclContainerResponse func updateNetworkACL(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) netname := params["networkname"] var networkACLChange acls.ACLContainer networkACLChange, err := networkACLChange.Get(acls.ContainerID(netname)) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = json.NewDecoder(r.Body).Decode(&networkACLChange) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } newNetACL, err := networkACLChange.Save(acls.ContainerID(netname)) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname) // send peer updates go func() { if err = mq.PublishPeerUpdate(false); err != nil { logger.Log(0, "failed to publish peer update after ACL update on network:", netname) } }() w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(newNetACL) } // swagger:route PUT /api/networks/{networkname}/acls/v2 networks updateNetworkACL // // Update a network ACL (Access Control List). // // Schemes: https // // Security: // oauth // // Responses: // 200: aclContainerResponse func updateNetworkACLv2(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) netname := params["networkname"] var networkACLChange acls.ACLContainer networkACLChange, err := networkACLChange.Get(acls.ContainerID(netname)) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = json.NewDecoder(r.Body).Decode(&networkACLChange) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } // clone req body to use as return data successful update retData := make(acls.ACLContainer) data, err := json.Marshal(networkACLChange) if err != nil { slog.Error("failed to marshal networkACLChange whiles cloning", "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = json.Unmarshal(data, &retData) if err != nil { slog.Error("failed to unmarshal networkACLChange whiles cloning", "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } allNodes, err := logic.GetAllNodes() if err != nil { slog.Error("failed to fetch all nodes", "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } networkNodes := make([]models.Node, 0) for _, node := range allNodes { if node.Network == netname { networkNodes = append(networkNodes, node) } } networkNodesIdMap := make(map[string]models.Node) for _, node := range networkNodes { networkNodesIdMap[node.ID.String()] = node } networkClients, err := logic.GetNetworkExtClients(netname) if err != nil { slog.Error("failed to fetch network clients", "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } networkClientsMap := make(map[string]models.ExtClient) for _, client := range networkClients { networkClientsMap[client.ClientID] = client } // keep track of ingress gateways to disconnect from their clients // this is required because PublishPeerUpdate only somehow does not stop communication // between blocked clients and their ingress assocClientsToDisconnectPerHost := make(map[uuid.UUID][]models.ExtClient) // update client acls and then, remove client acls from req data to pass to existing functions for id, acl := range networkACLChange { // for node acls if _, ok := networkNodesIdMap[string(id)]; ok { nodeId := string(id) // check acl update, then remove client entries for id2 := range acl { if _, ok := networkNodesIdMap[string(id2)]; !ok { // update client acl clientId := string(id2) if client, ok := networkClientsMap[clientId]; ok { if client.DeniedACLs == nil { client.DeniedACLs = make(map[string]struct{}) } if acl[acls.AclID(clientId)] == acls.NotAllowed { client.DeniedACLs[nodeId] = struct{}{} } else { delete(client.DeniedACLs, string(nodeId)) } networkClientsMap[clientId] = client } } } } else { // for client acls clientId := string(id) for id2 := range acl { if _, ok := networkNodesIdMap[string(id2)]; !ok { // update client acl clientId2 := string(id2) if client, ok := networkClientsMap[clientId]; ok { if client.DeniedACLs == nil { client.DeniedACLs = make(map[string]struct{}) } { // TODO: review this when client-to-client acls are supported // if acl[acls.AclID(clientId2)] == acls.NotAllowed { // client.DeniedACLs[clientId2] = struct{}{} // } else { // delete(client.DeniedACLs, clientId2) // } delete(client.DeniedACLs, clientId2) } networkClientsMap[clientId] = client } } else { nodeId2 := string(id2) if networkClientsMap[clientId].IngressGatewayID == nodeId2 && acl[acls.AclID(nodeId2)] == acls.NotAllowed { assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID] = append(assocClientsToDisconnectPerHost[networkNodesIdMap[nodeId2].HostID], networkClientsMap[clientId]) } } } } } // update each client in db for pro servers if servercfg.IsPro { for _, client := range networkClientsMap { client := client err := logic.DeleteExtClient(client.Network, client.ClientID) if err != nil { slog.Error("failed to delete client during update", "client", client.ClientID, "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } err = logic.SaveExtClient(&client) if err != nil { slog.Error("failed to save client during update", "client", client.ClientID, "error", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } } } _, err = networkACLChange.Save(acls.ContainerID(netname)) if err != nil { logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to update ACLs for network [%s]: %v", netname, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } logger.Log(1, r.Header.Get("user"), "updated ACLs for network", netname) // send peer updates go func() { if err = mq.PublishPeerUpdate(false); err != nil { logger.Log(0, "failed to publish peer update after ACL update on network:", netname) } // update ingress gateways of associated clients hosts, err := logic.GetAllHosts() if err != nil { slog.Error("failed to fetch hosts after network ACL update. skipping publish extclients ACL", "network", netname) return } hostsMap := make(map[uuid.UUID]models.Host) for _, host := range hosts { hostsMap[host.ID] = host } for hostId, clients := range assocClientsToDisconnectPerHost { if host, ok := hostsMap[hostId]; ok { if err = mq.PublishSingleHostPeerUpdate(&host, allNodes, nil, clients, false); err != nil { slog.Error("failed to publish peer update to ingress after ACL update on network", "network", netname, "host", hostId) } } } }() w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(networkACLChange) } // swagger:route GET /api/networks/{networkname}/acls networks getNetworkACL // // Get a network ACL (Access Control List). // // Schemes: https // // Security: // oauth // // Responses: // 200: aclContainerResponse func getNetworkACL(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) netname := params["networkname"] var networkACL acls.ACLContainer networkACL, err := networkACL.Get(acls.ContainerID(netname)) if err != nil { if database.IsEmptyRecord(err) { networkACL = acls.ACLContainer{} w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(networkACL) return } logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to fetch ACLs for network [%s]: %v", netname, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(2, r.Header.Get("user"), "fetched acl for network", netname) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(networkACL) } // swagger:route DELETE /api/networks/{networkname} networks deleteNetwork // // Delete a network. Will not delete if there are any nodes that belong to the network. // // Schemes: https // // Security: // oauth // // Responses: // 200: successResponse func deleteNetwork(w http.ResponseWriter, r *http.Request) { // Set header w.Header().Set("Content-Type", "application/json") var params = mux.Vars(r) network := params["networkname"] err := logic.DeleteNetwork(network) if err != nil { errtype := "badrequest" if strings.Contains(err.Error(), "Node check failed") { errtype = "forbidden" } logger.Log(0, r.Header.Get("user"), fmt.Sprintf("failed to delete network [%s]: %v", network, err)) logic.ReturnErrorResponse(w, r, logic.FormatError(err, errtype)) return } logger.Log(1, r.Header.Get("user"), "deleted network", network) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode("success") } // swagger:route POST /api/networks networks createNetwork // // Create a network. // // Schemes: https // // Security: // oauth // // Responses: // 200: networkBodyResponse func createNetwork(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var network models.Network // we decode our body request params err := json.NewDecoder(r.Body).Decode(&network) if err != nil { logger.Log(0, r.Header.Get("user"), "error decoding request body: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if len(network.NetID) > 32 { err := errors.New("network name shouldn't exceed 32 characters") logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if network.AddressRange == "" && network.AddressRange6 == "" { err := errors.New("IPv4 or IPv6 CIDR required") logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } // validate address ranges: must be private if network.AddressRange != "" { _, ipNet, err := net.ParseCIDR(network.AddressRange) if err != nil { logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if !ipNet.IP.IsPrivate() { err := errors.New("address range must be private") logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } } if network.AddressRange6 != "" { _, ipNet, err := net.ParseCIDR(network.AddressRange6) if err != nil { logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } if !ipNet.IP.IsPrivate() { err := errors.New("address range must be private") logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } } network, err = logic.CreateNetwork(network) if err != nil { logger.Log(0, r.Header.Get("user"), "failed to create network: ", err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } go func() { defaultHosts := logic.GetDefaultHosts() for i := range defaultHosts { currHost := &defaultHosts[i] newNode, err := logic.UpdateHostNetwork(currHost, network.NetID, true) if err != nil { logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error()) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } logger.Log(1, "added new node", newNode.ID.String(), "to host", currHost.Name) if err = mq.HostUpdate(&models.HostUpdate{ Action: models.JoinHostToNetwork, Host: *currHost, Node: *newNode, }); err != nil { logger.Log(0, r.Header.Get("user"), "failed to add host to network:", currHost.ID.String(), network.NetID, err.Error()) } // make host failover logic.CreateFailOver(*newNode) // make host remote access gateway logic.CreateIngressGateway(network.NetID, newNode.ID.String(), models.IngressRequest{}) } }() logger.Log(1, r.Header.Get("user"), "created network", network.NetID) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(network) } // swagger:route PUT /api/networks/{networkname} networks updateNetwork // // Update pro settings for a network. // // Schemes: https // // Security: // oauth // // Responses: // 200: networkBodyResponse func updateNetwork(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var payload models.Network // we decode our body request params err := json.NewDecoder(r.Body).Decode(&payload) if err != nil { slog.Info("error decoding request body", "user", r.Header.Get("user"), "err", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } netOld1, err := logic.GetNetwork(payload.NetID) if err != nil { slog.Info("error fetching network", "user", r.Header.Get("user"), "err", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } // partial update netOld2 := netOld1 _, _, _, err = logic.UpdateNetwork(&netOld1, &netOld2) if err != nil { slog.Info("failed to update network", "user", r.Header.Get("user"), "err", err) logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest")) return } slog.Info("updated network", "network", payload.NetID, "user", r.Header.Get("user")) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(payload) }