diff --git a/controllers/hosts.go b/controllers/hosts.go index cd8f6c00..493580d6 100644 --- a/controllers/hosts.go +++ b/controllers/hosts.go @@ -10,11 +10,15 @@ import ( "github.com/gravitl/netmaker/models" ) +type hostNetworksUpdatePayload struct { + Networks []string `json:"networks"` +} + func hostHandlers(r *mux.Router) { r.HandleFunc("/api/hosts", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods("GET") r.HandleFunc("/api/hosts", logic.SecurityCheck(true, http.HandlerFunc(updateHost))).Methods("PUT") r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(deleteHost))).Methods("DELETE") - // r.HandleFunc("/api/hosts/{hostid}/{network}", logic.SecurityCheck(false, http.HandlerFunc(getHosts))).Methods("PUT") + r.HandleFunc("/api/hosts/{hostid}", logic.SecurityCheck(true, http.HandlerFunc(updateHostNetworks))).Methods("PUT") } // swagger:route GET /api/hosts hosts getHosts @@ -112,3 +116,38 @@ func deleteHost(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(currHost) } + +// swagger:route PUT /api/hosts hosts updateHostNetworks +// +// Given a list of networks, a host is updated accordingly. +// +// Schemes: https +// +// Security: +// oauth +// +// Responses: +// 200: updateHostNetworks +func updateHostNetworks(w http.ResponseWriter, r *http.Request) { + var payload hostNetworksUpdatePayload + err := json.NewDecoder(r.Body).Decode(&payload) + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to update host networks:", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + // confirm host exists + var params = mux.Vars(r) + hostid := params["hostid"] + currHost, err := logic.GetHost(hostid) + if err != nil { + logger.Log(0, r.Header.Get("user"), "failed to find host:", err.Error()) + logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) + return + } + + logger.Log(2, r.Header.Get("user"), "updated host", currHost.Name) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(payload) +} diff --git a/logic/hosts.go b/logic/hosts.go index 4f0774ca..44bed23e 100644 --- a/logic/hosts.go +++ b/logic/hosts.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/gravitl/netmaker/database" + "github.com/gravitl/netmaker/logger" "github.com/gravitl/netmaker/models" "golang.org/x/crypto/bcrypt" ) @@ -145,9 +146,39 @@ func RemoveHost(h *models.Host) error { return database.DeleteRecord(database.HOSTS_TABLE_NAME, h.ID.String()) } -// host.UpdatePass updates and saves host.HostPass -// Password saved on server needs to be the hashedPassword, whereas the raw password belongs to client -func UpdatePass(h *models.Host, pass string) error { - h.HostPass = pass - return UpsertHost(h) +// UpdateHostNetworks - updates a given host's networks +func UpdateHostNetworks(h *models.Host, nets []string) error { + if len(h.Nodes) > 0 { + for i := range h.Nodes { + n, err := GetNodeByID(h.Nodes[i]) + if err != nil { + return err + } + // loop through networks and remove need for updating existing networks + found := false + for j := range nets { + if len(nets[j]) > 0 && nets[j] == n.Network { + nets[j] = "" // mark as ignore + found = true + } + } + if !found { // remove the node/host from that network + if err = DeleteNodeByID(&n); err != nil { + return err + } + } + } + } else { + h.Nodes = []string{} + } + + for i := range nets { + // create a node for each non zero network remaining + if len(nets[i]) > 0 { + // TODO create a node with given hostid + logger.Log(0, "I will create a node here") + } + } + + return nil } diff --git a/models/api_host.go b/models/api_host.go new file mode 100644 index 00000000..5fae1f59 --- /dev/null +++ b/models/api_host.go @@ -0,0 +1,27 @@ +package models + +// APIHost - the host struct for API usage +type APIHost struct { + ID string `json:"id"` + Verbosity int `json:"verbosity"` + FirewallInUse string `json:"firewallinuse"` + Version string `json:"version"` + IPForwarding bool `json:"ipforwarding"` + DaemonInstalled bool `json:"daemoninstalled"` + HostPass string `json:"hostpass"` + Name string `json:"name"` + OS string `json:"os"` + Interface string `json:"interface"` + Debug bool `json:"debug"` + ListenPort int `json:"listenport"` + LocalAddress string `json:"localaddress"` + LocalRange string `json:"localrange"` + LocalListenPort int `json:"locallistenport"` + ProxyListenPort int `json:"proxy_listen_port"` + MTU int `json:"mtu" yaml:"mtu"` + Interfaces []Iface `json:"interfaces" yaml:"interfaces"` + PublicKey string `json:"publickey"` + MacAddress string `json:"macaddress"` + InternetGateway string `json:"internetgateway"` + Nodes []string `json:"nodes"` +} diff --git a/models/api_node.go b/models/api_node.go new file mode 100644 index 00000000..71ad3dee --- /dev/null +++ b/models/api_node.go @@ -0,0 +1,164 @@ +package models + +import ( + "net" + "time" + + "github.com/google/uuid" +) + +// ApiNode is a stripped down Node DTO that exposes only required fields to external systems +type ApiNode struct { + ID string `json:"id,omitempty" validate:"required,min=5,id_unique"` + HostID string `json:"hostid,omitempty" validate:"required,min=5,id_unique"` + Address string `json:"address" validate:"omitempty,ipv4"` + Address6 string `json:"address6" validate:"omitempty,ipv6"` + PostUp string `json:"postup"` + PostDown string `json:"postdown"` + AllowedIPs []string `json:"allowedips"` + PersistentKeepalive int32 `json:"persistentkeepalive"` + LastModified int64 `json:"lastmodified"` + ExpirationDateTime int64 `json:"expdatetime"` + LastCheckIn int64 `json:"lastcheckin"` + LastPeerUpdate int64 `json:"lastpeerupdate"` + Network string `json:"network"` + NetworkRange string `json:"networkrange"` + NetworkRange6 string `json:"networkrange6"` + IsRelayed bool `json:"isrelayed"` + IsRelay bool `json:"isrelay"` + IsEgressGateway bool `json:"isegressgateway"` + IsIngressGateway bool `json:"isingressgateway"` + EgressGatewayRanges []string `json:"egressgatewayranges"` + EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled"` + RelayAddrs []string `json:"relayaddrs"` + FailoverNode string `json:"failovernode"` + DNSOn bool `json:"dnson"` + IsLocal bool `json:"islocal"` + Server string `json:"server"` + InternetGateway string `json:"internetgateway"` + Connected bool `json:"connected"` + PendingDelete bool `json:"pendingdelete"` + // == PRO == + DefaultACL string `json:"defaultacl,omitempty" validate:"checkyesornoorunset"` + Failover bool `json:"failover"` +} + +// ApiNode.ConvertToServerNode - converts an api node to a server node +func (a *ApiNode) ConvertToServerNode(currentNode *Node) *Node { + convertedNode := Node{} + convertedNode.Network = a.Network + convertedNode.Server = a.Server + convertedNode.Action = currentNode.Action + convertedNode.Connected = a.Connected + convertedNode.AllowedIPs = a.AllowedIPs + convertedNode.ID, _ = uuid.Parse(a.ID) + convertedNode.HostID, _ = uuid.Parse(a.HostID) + convertedNode.PostUp = a.PostUp + convertedNode.PostDown = a.PostDown + convertedNode.IsLocal = a.IsLocal + convertedNode.IsRelay = a.IsRelay + convertedNode.IsRelayed = a.IsRelayed + convertedNode.PendingDelete = a.PendingDelete + convertedNode.Peers = currentNode.Peers + convertedNode.Failover = a.Failover + convertedNode.IsEgressGateway = a.IsEgressGateway + convertedNode.IsIngressGateway = a.IsIngressGateway + convertedNode.EgressGatewayRanges = a.EgressGatewayRanges + convertedNode.IngressGatewayRange = currentNode.IngressGatewayRange + convertedNode.IngressGatewayRange6 = currentNode.IngressGatewayRange6 + convertedNode.DNSOn = a.DNSOn + convertedNode.EgressGatewayRequest = currentNode.EgressGatewayRequest + convertedNode.EgressGatewayNatEnabled = currentNode.EgressGatewayNatEnabled + convertedNode.PersistentKeepalive = int(a.PersistentKeepalive) + convertedNode.RelayAddrs = a.RelayAddrs + convertedNode.DefaultACL = a.DefaultACL + convertedNode.OwnerID = currentNode.OwnerID + _, networkRange, err := net.ParseCIDR(a.NetworkRange) + if err == nil { + convertedNode.NetworkRange = *networkRange + } + _, networkRange6, err := net.ParseCIDR(a.NetworkRange6) + if err == nil { + convertedNode.NetworkRange6 = *networkRange6 + } + udpAddr, err := net.ResolveUDPAddr("udp", a.InternetGateway) + if err == nil { + convertedNode.InternetGateway = udpAddr + } + _, addr, err := net.ParseCIDR(a.Address) + if err == nil { + convertedNode.Address = *addr + } + _, addr6, err := net.ParseCIDR(a.Address6) + if err == nil { + convertedNode.Address = *addr6 + } + convertedNode.FailoverNode, _ = uuid.Parse(a.FailoverNode) + convertedNode.LastModified = time.Unix(a.LastModified, 0) + convertedNode.LastCheckIn = time.Unix(a.LastCheckIn, 0) + convertedNode.LastPeerUpdate = time.Unix(a.LastPeerUpdate, 0) + convertedNode.ExpirationDateTime = time.Unix(a.ExpirationDateTime, 0) + return &convertedNode +} + +// Node.ConvertToAPINode - converts a node to an API node +func (nm *Node) ConvertToAPINode() *ApiNode { + apiNode := ApiNode{} + apiNode.ID = nm.ID.String() + apiNode.HostID = nm.ID.String() + apiNode.Address = nm.Address.String() + if isEmptyAddr(apiNode.Address) { + apiNode.Address = "" + } + apiNode.Address6 = nm.Address6.String() + if isEmptyAddr(apiNode.Address6) { + apiNode.Address6 = "" + } + apiNode.PostDown = nm.PostDown + apiNode.PostUp = nm.PostUp + apiNode.AllowedIPs = nm.AllowedIPs + apiNode.PersistentKeepalive = int32(nm.PersistentKeepalive) + apiNode.LastModified = nm.LastModified.Unix() + apiNode.LastCheckIn = nm.LastCheckIn.Unix() + apiNode.LastPeerUpdate = nm.LastPeerUpdate.Unix() + apiNode.Network = nm.Network + apiNode.NetworkRange = nm.NetworkRange.String() + if isEmptyAddr(apiNode.NetworkRange) { + apiNode.NetworkRange = "" + } + apiNode.NetworkRange6 = nm.NetworkRange6.String() + if isEmptyAddr(apiNode.NetworkRange6) { + apiNode.NetworkRange6 = "" + } + apiNode.IsRelayed = nm.IsRelayed + apiNode.IsRelay = nm.IsRelay + apiNode.IsEgressGateway = nm.IsEgressGateway + apiNode.IsIngressGateway = nm.IsIngressGateway + apiNode.EgressGatewayRanges = nm.EgressGatewayRanges + apiNode.EgressGatewayNatEnabled = nm.EgressGatewayNatEnabled + apiNode.RelayAddrs = nm.RelayAddrs + apiNode.FailoverNode = nm.FailoverNode.String() + if isUUIDSet(apiNode.FailoverNode) { + apiNode.FailoverNode = "" + } + apiNode.DNSOn = nm.DNSOn + apiNode.IsLocal = nm.IsLocal + apiNode.Server = nm.Server + apiNode.InternetGateway = nm.InternetGateway.String() + if isEmptyAddr(apiNode.InternetGateway) { + apiNode.InternetGateway = "" + } + apiNode.Connected = nm.Connected + apiNode.PendingDelete = nm.PendingDelete + apiNode.DefaultACL = nm.DefaultACL + apiNode.Failover = nm.Failover + return &apiNode +} + +func isEmptyAddr(addr string) bool { + return addr == "" || addr == ":0" +} + +func isUUIDSet(uuid string) bool { + return uuid != "00000000-0000-0000-0000-000000000000" +} diff --git a/models/host.go b/models/host.go index 5f8235bc..8d2031ad 100644 --- a/models/host.go +++ b/models/host.go @@ -34,4 +34,10 @@ type Host struct { TrafficKeyPublic []byte `json:"traffickeypublic" yaml:"trafficekeypublic"` InternetGateway net.UDPAddr `json:"internetgateway" yaml:"internetgateway"` Nodes []string `json:"nodes" yaml:"nodes"` + Interfaces []Iface `json:"interfaces" yaml:"interfaces"` + EndpointIP net.IP `json:"endpointip" yaml:"endpointip"` + ProxyEnabled bool `json:"proxy_enabled" yaml:"proxy_enabled"` + IsDocker bool `json:"isdocker" yaml:"isdocker"` + IsK8S bool `json:"isk8s" yaml:"isk8s"` + IsStatic bool `json:"isstatic" yaml:"isstatic"` } diff --git a/models/node.go b/models/node.go index dd8ccf30..3caf8474 100644 --- a/models/node.go +++ b/models/node.go @@ -64,8 +64,6 @@ type CommonNode struct { InternetGateway *net.UDPAddr `json:"internetgateway" yaml:"internetgateway"` Server string `json:"server" yaml:"server"` Connected bool `json:"connected" yaml:"connected"` - Interfaces []Iface `json:"interfaces" yaml:"interfaces"` - EndpointIP net.IP `json:"endpointip" yaml:"endpointip"` Address net.IPNet `json:"address" yaml:"address"` Address6 net.IPNet `json:"address6" yaml:"address6"` PostUp string `json:"postup" yaml:"postup"` @@ -74,11 +72,9 @@ type CommonNode struct { IsLocal bool `json:"islocal" yaml:"islocal"` IsEgressGateway bool `json:"isegressgateway" yaml:"isegressgateway"` IsIngressGateway bool `json:"isingressgateway" yaml:"isingressgateway"` - IsStatic bool `json:"isstatic" yaml:"isstatic"` DNSOn bool `json:"dnson" yaml:"dnson"` PersistentKeepalive int `json:"persistentkeepalive" yaml:"persistentkeepalive"` Peers []wgtypes.PeerConfig `json:"peers" yaml:"peers"` - Proxy bool `json:"proxy" bson:"proxy" yaml:"proxy"` } // Node - a model of a network node @@ -91,15 +87,13 @@ type Node struct { ExpirationDateTime time.Time `json:"expdatetime" bson:"expdatetime" yaml:"expdatetime"` AllowedIPs []string `json:"allowedips" bson:"allowedips" yaml:"allowedips"` EgressGatewayRanges []string `json:"egressgatewayranges" bson:"egressgatewayranges" yaml:"egressgatewayranges"` - EgressGatewayNatEnabled string `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"` + EgressGatewayNatEnabled bool `json:"egressgatewaynatenabled" bson:"egressgatewaynatenabled" yaml:"egressgatewaynatenabled"` EgressGatewayRequest EgressGatewayRequest `json:"egressgatewayrequest" bson:"egressgatewayrequest" yaml:"egressgatewayrequest"` IngressGatewayRange string `json:"ingressgatewayrange" bson:"ingressgatewayrange" yaml:"ingressgatewayrange"` IngressGatewayRange6 string `json:"ingressgatewayrange6" bson:"ingressgatewayrange6" yaml:"ingressgatewayrange6"` IsRelayed bool `json:"isrelayed" bson:"isrelayed" yaml:"isrelayed"` IsRelay bool `json:"isrelay" bson:"isrelay" yaml:"isrelay"` RelayAddrs []string `json:"relayaddrs" bson:"relayaddrs" yaml:"relayaddrs"` - IsDocker bool `json:"isdocker" bson:"isdocker" yaml:"isdocker"` - IsK8S bool `json:"isk8s" bson:"isk8s" yaml:"isk8s"` // == PRO == DefaultACL string `json:"defaultacl,omitempty" bson:"defaultacl,omitempty" yaml:"defaultacl,omitempty" validate:"checkyesornoorunset"` OwnerID string `json:"ownerid,omitempty" bson:"ownerid,omitempty" yaml:"ownerid,omitempty"` @@ -423,9 +417,6 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable if newNode.IngressGatewayRange6 == "" { newNode.IngressGatewayRange6 = currentNode.IngressGatewayRange6 } - if newNode.IsStatic != currentNode.IsStatic { - newNode.IsStatic = currentNode.IsStatic - } if newNode.DNSOn != currentNode.DNSOn { newNode.DNSOn = currentNode.DNSOn } @@ -444,12 +435,6 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable if newNode.IsRelayed == currentNode.IsRelayed { newNode.IsRelayed = currentNode.IsRelayed } - if newNode.IsDocker == currentNode.IsDocker { - newNode.IsDocker = currentNode.IsDocker - } - if newNode.IsK8S != currentNode.IsK8S { - newNode.IsK8S = currentNode.IsK8S - } if newNode.Server == "" { newNode.Server = currentNode.Server } @@ -462,7 +447,6 @@ func (newNode *Node) Fill(currentNode *Node) { // TODO add new field for nftable if newNode.Failover != currentNode.Failover { newNode.Failover = currentNode.Failover } - newNode.Proxy = currentNode.Proxy } // StringWithCharset - returns random string inside defined charset @@ -532,6 +516,9 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) { host.InternetGateway = *gateway id, _ := uuid.Parse(ln.ID) host.Nodes = append(host.Nodes, id.String()) + host.Interfaces = ln.Interfaces + host.EndpointIP = net.ParseIP(ln.Endpoint) + // host.ProxyEnabled = ln.Proxy // this will always be false.. } id, _ := uuid.Parse(ln.ID) node.ID = id @@ -542,8 +529,6 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) { node.NetworkRange6 = *cidr node.Server = ln.Server node.Connected = parseBool(ln.Connected) - node.Interfaces = ln.Interfaces - node.EndpointIP = net.ParseIP(ln.Endpoint) _, cidr, _ = net.ParseCIDR(ln.Address) node.Address = *cidr _, cidr, _ = net.ParseCIDR(ln.Address6) @@ -554,10 +539,8 @@ func (ln *LegacyNode) ConvertToNewNode() (*Host, *Node) { node.IsLocal = parseBool(ln.IsLocal) node.IsEgressGateway = parseBool(ln.IsEgressGateway) node.IsIngressGateway = parseBool(ln.IsIngressGateway) - node.IsStatic = parseBool(ln.IsStatic) node.DNSOn = parseBool(ln.DNSOn) node.PersistentKeepalive = int(ln.PersistentKeepalive) - node.Proxy = ln.Proxy return &host, &node } @@ -570,14 +553,14 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode { l.Address = n.Address.String() l.Address6 = n.Address6.String() l.LocalAddress = h.LocalAddress.String() - l.Interfaces = n.Interfaces + l.Interfaces = h.Interfaces l.Name = h.Name l.NetworkSettings = *net l.ListenPort = int32(h.ListenPort) l.LocalListenPort = int32(h.LocalListenPort) l.ProxyListenPort = int32(h.ProxyListenPort) l.PublicKey = h.PublicKey.String() - l.Endpoint = n.EndpointIP.String() + l.Endpoint = h.EndpointIP.String() l.PostUp = n.PostUp l.PostDown = n.PostDown //l.AllowedIPs = @@ -603,7 +586,7 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode { //l.FailoverNode = n.FailoverNode //l.IngressGatewayRange = n.IngressGatewayRange //l.IngressGatewayRange6 = n.IngressGatewayRange6 - l.IsStatic = formatBool(n.IsStatic) + l.IsStatic = formatBool(h.IsStatic) l.UDPHolePunch = formatBool(true) l.DNSOn = formatBool(n.DNSOn) l.Action = n.Action @@ -620,7 +603,7 @@ func (n *Node) Legacy(h *Host, s *ServerConfig, net *Network) *LegacyNode { l.InternetGateway = h.InternetGateway.String() l.Connected = formatBool(n.Connected) //l.PendingDelete = formatBool(n.PendingDelete) - l.Proxy = n.Proxy + l.Proxy = h.ProxyEnabled l.DefaultACL = n.DefaultACL l.OwnerID = n.OwnerID //l.Failover = n.Failover