mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-10-28 10:51:44 +01:00 
			
		
		
		
	Merge pull request #320 from restanrm/feat-improve-acls-usage
Improvements on the ACLs and bug fixing
This commit is contained in:
		
						commit
						69cdfbb56f
					
				@ -48,6 +48,7 @@ linters-settings:
 | 
				
			|||||||
      - ip
 | 
					      - ip
 | 
				
			||||||
      - ok
 | 
					      - ok
 | 
				
			||||||
      - c
 | 
					      - c
 | 
				
			||||||
 | 
					      - tt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  gocritic:
 | 
					  gocritic:
 | 
				
			||||||
    disabled-checks:
 | 
					    disabled-checks:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@ -2,6 +2,24 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
**TBD (TBD):**
 | 
					**TBD (TBD):**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**0.14.0 (2022-xx-xx):**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**UPCOMING BREAKING**:
 | 
				
			||||||
 | 
					From the **next** version (`0.15.0`), all machines will be able to communicate regardless of
 | 
				
			||||||
 | 
					if they are in the same namespace. This means that the behaviour currently limited to ACLs
 | 
				
			||||||
 | 
					will become default. From version `0.15.0`, all limitation of communications must be done
 | 
				
			||||||
 | 
					with ACLs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This is a part of aligning `headscale`'s behaviour with Tailscale's upstream behaviour.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**BREAKING**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- ACLs have been rewritten to align with the bevaviour Tailscale Control Panel provides. **NOTE:** This is only active if you use ACLs
 | 
				
			||||||
 | 
					  - Namespaces are now treated as Users
 | 
				
			||||||
 | 
					  - All machines can communicate with all machines by default
 | 
				
			||||||
 | 
					  - Tags should now work correctly and adding a host to Headscale should now reload the rules.
 | 
				
			||||||
 | 
					  - The documentation have a [fictional example](docs/acls.md) that should cover some use cases of the ACLs features
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**0.13.0 (2022-02-18):**
 | 
					**0.13.0 (2022-02-18):**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Features**:
 | 
					**Features**:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										233
									
								
								acls.go
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								acls.go
									
									
									
									
									
								
							@ -20,7 +20,6 @@ const (
 | 
				
			|||||||
	errInvalidUserSection = Error("invalid user section")
 | 
						errInvalidUserSection = Error("invalid user section")
 | 
				
			||||||
	errInvalidGroup       = Error("invalid group")
 | 
						errInvalidGroup       = Error("invalid group")
 | 
				
			||||||
	errInvalidTag         = Error("invalid tag")
 | 
						errInvalidTag         = Error("invalid tag")
 | 
				
			||||||
	errInvalidNamespace   = Error("invalid namespace")
 | 
					 | 
				
			||||||
	errInvalidPortFormat  = Error("invalid port format")
 | 
						errInvalidPortFormat  = Error("invalid port format")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,13 +68,17 @@ func (h *Headscale) LoadACLPolicy(path string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	h.aclPolicy = &policy
 | 
						h.aclPolicy = &policy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h.UpdateACLRules()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) UpdateACLRules() error {
 | 
				
			||||||
	rules, err := h.generateACLRules()
 | 
						rules, err := h.generateACLRules()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h.aclRules = rules
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	log.Trace().Interface("ACL", rules).Msg("ACL rules generated")
 | 
						log.Trace().Interface("ACL", rules).Msg("ACL rules generated")
 | 
				
			||||||
 | 
						h.aclRules = rules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -83,16 +86,23 @@ func (h *Headscale) LoadACLPolicy(path string) error {
 | 
				
			|||||||
func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
 | 
					func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
 | 
				
			||||||
	rules := []tailcfg.FilterRule{}
 | 
						rules := []tailcfg.FilterRule{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if h.aclPolicy == nil {
 | 
				
			||||||
 | 
							return nil, errEmptyPolicy
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						machines, err := h.ListAllMachines()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for index, acl := range h.aclPolicy.ACLs {
 | 
						for index, acl := range h.aclPolicy.ACLs {
 | 
				
			||||||
		if acl.Action != "accept" {
 | 
							if acl.Action != "accept" {
 | 
				
			||||||
			return nil, errInvalidAction
 | 
								return nil, errInvalidAction
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		filterRule := tailcfg.FilterRule{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		srcIPs := []string{}
 | 
							srcIPs := []string{}
 | 
				
			||||||
		for innerIndex, user := range acl.Users {
 | 
							for innerIndex, user := range acl.Users {
 | 
				
			||||||
			srcs, err := h.generateACLPolicySrcIP(user)
 | 
								srcs, err := h.generateACLPolicySrcIP(machines, *h.aclPolicy, user)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Msgf("Error parsing ACL %d, User %d", index, innerIndex)
 | 
										Msgf("Error parsing ACL %d, User %d", index, innerIndex)
 | 
				
			||||||
@ -101,11 +111,10 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
			srcIPs = append(srcIPs, srcs...)
 | 
								srcIPs = append(srcIPs, srcs...)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		filterRule.SrcIPs = srcIPs
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		destPorts := []tailcfg.NetPortRange{}
 | 
							destPorts := []tailcfg.NetPortRange{}
 | 
				
			||||||
		for innerIndex, ports := range acl.Ports {
 | 
							for innerIndex, ports := range acl.Ports {
 | 
				
			||||||
			dests, err := h.generateACLPolicyDestPorts(ports)
 | 
								dests, err := h.generateACLPolicyDestPorts(machines, *h.aclPolicy, ports)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil {
 | 
				
			||||||
				log.Error().
 | 
									log.Error().
 | 
				
			||||||
					Msgf("Error parsing ACL %d, Port %d", index, innerIndex)
 | 
										Msgf("Error parsing ACL %d, Port %d", index, innerIndex)
 | 
				
			||||||
@ -124,11 +133,17 @@ func (h *Headscale) generateACLRules() ([]tailcfg.FilterRule, error) {
 | 
				
			|||||||
	return rules, nil
 | 
						return rules, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) generateACLPolicySrcIP(u string) ([]string, error) {
 | 
					func (h *Headscale) generateACLPolicySrcIP(
 | 
				
			||||||
	return h.expandAlias(u)
 | 
						machines []Machine,
 | 
				
			||||||
 | 
						aclPolicy ACLPolicy,
 | 
				
			||||||
 | 
						u string,
 | 
				
			||||||
 | 
					) ([]string, error) {
 | 
				
			||||||
 | 
						return expandAlias(machines, aclPolicy, u)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) generateACLPolicyDestPorts(
 | 
					func (h *Headscale) generateACLPolicyDestPorts(
 | 
				
			||||||
 | 
						machines []Machine,
 | 
				
			||||||
 | 
						aclPolicy ACLPolicy,
 | 
				
			||||||
	d string,
 | 
						d string,
 | 
				
			||||||
) ([]tailcfg.NetPortRange, error) {
 | 
					) ([]tailcfg.NetPortRange, error) {
 | 
				
			||||||
	tokens := strings.Split(d, ":")
 | 
						tokens := strings.Split(d, ":")
 | 
				
			||||||
@ -149,11 +164,11 @@ func (h *Headscale) generateACLPolicyDestPorts(
 | 
				
			|||||||
		alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
 | 
							alias = fmt.Sprintf("%s:%s", tokens[0], tokens[1])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	expanded, err := h.expandAlias(alias)
 | 
						expanded, err := expandAlias(machines, aclPolicy, alias)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ports, err := h.expandPorts(tokens[len(tokens)-1])
 | 
						ports, err := expandPorts(tokens[len(tokens)-1])
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -172,21 +187,28 @@ func (h *Headscale) generateACLPolicyDestPorts(
 | 
				
			|||||||
	return dests, nil
 | 
						return dests, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) expandAlias(alias string) ([]string, error) {
 | 
					// expandalias has an input of either
 | 
				
			||||||
 | 
					// - a namespace
 | 
				
			||||||
 | 
					// - a group
 | 
				
			||||||
 | 
					// - a tag
 | 
				
			||||||
 | 
					// and transform these in IPAddresses.
 | 
				
			||||||
 | 
					func expandAlias(
 | 
				
			||||||
 | 
						machines []Machine,
 | 
				
			||||||
 | 
						aclPolicy ACLPolicy,
 | 
				
			||||||
 | 
						alias string,
 | 
				
			||||||
 | 
					) ([]string, error) {
 | 
				
			||||||
 | 
						ips := []string{}
 | 
				
			||||||
	if alias == "*" {
 | 
						if alias == "*" {
 | 
				
			||||||
		return []string{"*"}, nil
 | 
							return []string{"*"}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if strings.HasPrefix(alias, "group:") {
 | 
						if strings.HasPrefix(alias, "group:") {
 | 
				
			||||||
		if _, ok := h.aclPolicy.Groups[alias]; !ok {
 | 
							namespaces, err := expandGroup(aclPolicy, alias)
 | 
				
			||||||
			return nil, errInvalidGroup
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return ips, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ips := []string{}
 | 
							for _, n := range namespaces {
 | 
				
			||||||
		for _, n := range h.aclPolicy.Groups[alias] {
 | 
								nodes := filterMachinesByNamespace(machines, n)
 | 
				
			||||||
			nodes, err := h.ListMachinesInNamespace(n)
 | 
					 | 
				
			||||||
			if err != nil {
 | 
					 | 
				
			||||||
				return nil, errInvalidNamespace
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			for _, node := range nodes {
 | 
								for _, node := range nodes {
 | 
				
			||||||
				ips = append(ips, node.IPAddresses.ToStringSlice()...)
 | 
									ips = append(ips, node.IPAddresses.ToStringSlice()...)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -196,35 +218,23 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if strings.HasPrefix(alias, "tag:") {
 | 
						if strings.HasPrefix(alias, "tag:") {
 | 
				
			||||||
		if _, ok := h.aclPolicy.TagOwners[alias]; !ok {
 | 
							owners, err := expandTagOwners(aclPolicy, alias)
 | 
				
			||||||
			return nil, errInvalidTag
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return ips, err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							for _, namespace := range owners {
 | 
				
			||||||
		// This will have HORRIBLE performance.
 | 
								machines := filterMachinesByNamespace(machines, namespace)
 | 
				
			||||||
		// We need to change the data model to better store tags
 | 
								for _, machine := range machines {
 | 
				
			||||||
		machines := []Machine{}
 | 
									if len(machine.HostInfo) == 0 {
 | 
				
			||||||
		if err := h.db.Where("registered").Find(&machines).Error; err != nil {
 | 
										continue
 | 
				
			||||||
			return nil, err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		ips := []string{}
 | 
					 | 
				
			||||||
		for _, machine := range machines {
 | 
					 | 
				
			||||||
			hostinfo := tailcfg.Hostinfo{}
 | 
					 | 
				
			||||||
			if len(machine.HostInfo) != 0 {
 | 
					 | 
				
			||||||
				hi, err := machine.HostInfo.MarshalJSON()
 | 
					 | 
				
			||||||
				if err != nil {
 | 
					 | 
				
			||||||
					return nil, err
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				err = json.Unmarshal(hi, &hostinfo)
 | 
									hi, err := machine.GetHostInfo()
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					return nil, err
 | 
										return ips, err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
									for _, t := range hi.RequestTags {
 | 
				
			||||||
				// FIXME: Check TagOwners allows this
 | 
										if alias == t {
 | 
				
			||||||
				for _, t := range hostinfo.RequestTags {
 | 
					 | 
				
			||||||
					if alias[4:] == t {
 | 
					 | 
				
			||||||
						ips = append(ips, machine.IPAddresses.ToStringSlice()...)
 | 
											ips = append(ips, machine.IPAddresses.ToStringSlice()...)
 | 
				
			||||||
 | 
					 | 
				
			||||||
						break
 | 
					 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -233,38 +243,82 @@ func (h *Headscale) expandAlias(alias string) ([]string, error) {
 | 
				
			|||||||
		return ips, nil
 | 
							return ips, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	n, err := h.GetNamespace(alias)
 | 
						// if alias is a namespace
 | 
				
			||||||
	if err == nil {
 | 
						nodes := filterMachinesByNamespace(machines, alias)
 | 
				
			||||||
		nodes, err := h.ListMachinesInNamespace(n.Name)
 | 
						nodes, err := excludeCorrectlyTaggedNodes(aclPolicy, nodes, alias)
 | 
				
			||||||
		if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
			return nil, err
 | 
							return ips, err
 | 
				
			||||||
		}
 | 
						}
 | 
				
			||||||
		ips := []string{}
 | 
						for _, n := range nodes {
 | 
				
			||||||
		for _, n := range nodes {
 | 
							ips = append(ips, n.IPAddresses.ToStringSlice()...)
 | 
				
			||||||
			ips = append(ips, n.IPAddresses.ToStringSlice()...)
 | 
						}
 | 
				
			||||||
		}
 | 
						if len(ips) > 0 {
 | 
				
			||||||
 | 
					 | 
				
			||||||
		return ips, nil
 | 
							return ips, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if h, ok := h.aclPolicy.Hosts[alias]; ok {
 | 
						// if alias is an host
 | 
				
			||||||
 | 
						if h, ok := aclPolicy.Hosts[alias]; ok {
 | 
				
			||||||
		return []string{h.String()}, nil
 | 
							return []string{h.String()}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if alias is an IP
 | 
				
			||||||
	ip, err := netaddr.ParseIP(alias)
 | 
						ip, err := netaddr.ParseIP(alias)
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
		return []string{ip.String()}, nil
 | 
							return []string{ip.String()}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// if alias is an CIDR
 | 
				
			||||||
	cidr, err := netaddr.ParseIPPrefix(alias)
 | 
						cidr, err := netaddr.ParseIPPrefix(alias)
 | 
				
			||||||
	if err == nil {
 | 
						if err == nil {
 | 
				
			||||||
		return []string{cidr.String()}, nil
 | 
							return []string{cidr.String()}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil, errInvalidUserSection
 | 
						return ips, errInvalidUserSection
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
 | 
					// excludeCorrectlyTaggedNodes will remove from the list of input nodes the ones
 | 
				
			||||||
 | 
					// that are correctly tagged since they should not be listed as being in the namespace
 | 
				
			||||||
 | 
					// we assume in this function that we only have nodes from 1 namespace.
 | 
				
			||||||
 | 
					func excludeCorrectlyTaggedNodes(
 | 
				
			||||||
 | 
						aclPolicy ACLPolicy,
 | 
				
			||||||
 | 
						nodes []Machine,
 | 
				
			||||||
 | 
						namespace string,
 | 
				
			||||||
 | 
					) ([]Machine, error) {
 | 
				
			||||||
 | 
						out := []Machine{}
 | 
				
			||||||
 | 
						tags := []string{}
 | 
				
			||||||
 | 
						for tag, ns := range aclPolicy.TagOwners {
 | 
				
			||||||
 | 
							if containsString(ns, namespace) {
 | 
				
			||||||
 | 
								tags = append(tags, tag)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// for each machine if tag is in tags list, don't append it.
 | 
				
			||||||
 | 
						for _, machine := range nodes {
 | 
				
			||||||
 | 
							if len(machine.HostInfo) == 0 {
 | 
				
			||||||
 | 
								out = append(out, machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							hi, err := machine.GetHostInfo()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return out, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							found := false
 | 
				
			||||||
 | 
							for _, t := range hi.RequestTags {
 | 
				
			||||||
 | 
								if containsString(tags, t) {
 | 
				
			||||||
 | 
									found = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !found {
 | 
				
			||||||
 | 
								out = append(out, machine)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return out, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
 | 
				
			||||||
	if portsStr == "*" {
 | 
						if portsStr == "*" {
 | 
				
			||||||
		return &[]tailcfg.PortRange{
 | 
							return &[]tailcfg.PortRange{
 | 
				
			||||||
			{First: portRangeBegin, Last: portRangeEnd},
 | 
								{First: portRangeBegin, Last: portRangeEnd},
 | 
				
			||||||
@ -306,3 +360,64 @@ func (h *Headscale) expandPorts(portsStr string) (*[]tailcfg.PortRange, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return &ports, nil
 | 
						return &ports, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func filterMachinesByNamespace(machines []Machine, namespace string) []Machine {
 | 
				
			||||||
 | 
						out := []Machine{}
 | 
				
			||||||
 | 
						for _, machine := range machines {
 | 
				
			||||||
 | 
							if machine.Namespace.Name == namespace {
 | 
				
			||||||
 | 
								out = append(out, machine)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return out
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// expandTagOwners will return a list of namespace. An owner can be either a namespace or a group
 | 
				
			||||||
 | 
					// a group cannot be composed of groups.
 | 
				
			||||||
 | 
					func expandTagOwners(aclPolicy ACLPolicy, tag string) ([]string, error) {
 | 
				
			||||||
 | 
						var owners []string
 | 
				
			||||||
 | 
						ows, ok := aclPolicy.TagOwners[tag]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return []string{}, fmt.Errorf(
 | 
				
			||||||
 | 
								"%w. %v isn't owned by a TagOwner. Please add one first. https://tailscale.com/kb/1018/acls/#tag-owners",
 | 
				
			||||||
 | 
								errInvalidTag,
 | 
				
			||||||
 | 
								tag,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, owner := range ows {
 | 
				
			||||||
 | 
							if strings.HasPrefix(owner, "group:") {
 | 
				
			||||||
 | 
								gs, err := expandGroup(aclPolicy, owner)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return []string{}, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								owners = append(owners, gs...)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								owners = append(owners, owner)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return owners, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// expandGroup will return the list of namespace inside the group
 | 
				
			||||||
 | 
					// after some validation.
 | 
				
			||||||
 | 
					func expandGroup(aclPolicy ACLPolicy, group string) ([]string, error) {
 | 
				
			||||||
 | 
						groups, ok := aclPolicy.Groups[group]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return []string{}, fmt.Errorf(
 | 
				
			||||||
 | 
								"group %v isn't registered. %w",
 | 
				
			||||||
 | 
								group,
 | 
				
			||||||
 | 
								errInvalidGroup,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, g := range groups {
 | 
				
			||||||
 | 
							if strings.HasPrefix(g, "group:") {
 | 
				
			||||||
 | 
								return []string{}, fmt.Errorf(
 | 
				
			||||||
 | 
									"%w. A group cannot be composed of groups. https://tailscale.com/kb/1018/acls/#groups",
 | 
				
			||||||
 | 
									errInvalidGroup,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return groups, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										968
									
								
								acls_test.go
									
									
									
									
									
								
							
							
						
						
									
										968
									
								
								acls_test.go
									
									
									
									
									
								
							@ -1,7 +1,14 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
 | 
						"gorm.io/datatypes"
 | 
				
			||||||
 | 
						"inet.af/netaddr"
 | 
				
			||||||
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestWrongPath(c *check.C) {
 | 
					func (s *Suite) TestWrongPath(c *check.C) {
 | 
				
			||||||
@ -52,6 +59,245 @@ func (s *Suite) TestBasicRule(c *check.C) {
 | 
				
			|||||||
	c.Assert(rules, check.NotNil)
 | 
						c.Assert(rules, check.NotNil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO(kradalby): Make tests values safe, independent and descriptive.
 | 
				
			||||||
 | 
					func (s *Suite) TestInvalidAction(c *check.C) {
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "invalidAction", Users: []string{"*"}, Ports: []string{"*:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(errors.Is(err, errInvalidAction), check.Equals, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Suite) TestInvalidGroupInGroup(c *check.C) {
 | 
				
			||||||
 | 
						// this ACL is wrong because the group in users sections doesn't exist
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							Groups: Groups{
 | 
				
			||||||
 | 
								"group:test":  []string{"foo"},
 | 
				
			||||||
 | 
								"group:error": []string{"foo", "group:test"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"group:error"}, Ports: []string{"*:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(errors.Is(err, errInvalidGroup), check.Equals, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Suite) TestInvalidTagOwners(c *check.C) {
 | 
				
			||||||
 | 
						// this ACL is wrong because no tagOwners own the requested tag for the server
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"tag:foo"}, Ports: []string{"*:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err := app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(errors.Is(err, errInvalidTag), check.Equals, true)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// this test should validate that we can expand a group in a TagOWner section and
 | 
				
			||||||
 | 
					// match properly the IP's of the related hosts. The owner is valid and the tag is also valid.
 | 
				
			||||||
 | 
					// the tag is matched in the Users section.
 | 
				
			||||||
 | 
					func (s *Suite) TestValidExpandTagOwnersInUsers(c *check.C) {
 | 
				
			||||||
 | 
						namespace, err := app.CreateNamespace("user1")
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = app.GetMachine("user1", "testmachine")
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
						hostInfo := []byte(
 | 
				
			||||||
 | 
							"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						machine := Machine{
 | 
				
			||||||
 | 
							ID:             0,
 | 
				
			||||||
 | 
							MachineKey:     "foo",
 | 
				
			||||||
 | 
							NodeKey:        "bar",
 | 
				
			||||||
 | 
							DiscoKey:       "faa",
 | 
				
			||||||
 | 
							Name:           "testmachine",
 | 
				
			||||||
 | 
							IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
 | 
							Registered:     true,
 | 
				
			||||||
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
 | 
							HostInfo:       datatypes.JSON(hostInfo),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							Groups:    Groups{"group:test": []string{"user1", "user2"}},
 | 
				
			||||||
 | 
							TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}},
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"tag:test"}, Ports: []string{"*:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].SrcIPs, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].SrcIPs[0], check.Equals, "100.64.0.1")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// this test should validate that we can expand a group in a TagOWner section and
 | 
				
			||||||
 | 
					// match properly the IP's of the related hosts. The owner is valid and the tag is also valid.
 | 
				
			||||||
 | 
					// the tag is matched in the Ports section.
 | 
				
			||||||
 | 
					func (s *Suite) TestValidExpandTagOwnersInPorts(c *check.C) {
 | 
				
			||||||
 | 
						namespace, err := app.CreateNamespace("user1")
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = app.GetMachine("user1", "testmachine")
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
						hostInfo := []byte(
 | 
				
			||||||
 | 
							"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:test\"]}",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						machine := Machine{
 | 
				
			||||||
 | 
							ID:             1,
 | 
				
			||||||
 | 
							MachineKey:     "12345",
 | 
				
			||||||
 | 
							NodeKey:        "bar",
 | 
				
			||||||
 | 
							DiscoKey:       "faa",
 | 
				
			||||||
 | 
							Name:           "testmachine",
 | 
				
			||||||
 | 
							IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
 | 
							Registered:     true,
 | 
				
			||||||
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
 | 
							HostInfo:       datatypes.JSON(hostInfo),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							Groups:    Groups{"group:test": []string{"user1", "user2"}},
 | 
				
			||||||
 | 
							TagOwners: TagOwners{"tag:test": []string{"user3", "group:test"}},
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"*"}, Ports: []string{"tag:test:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[0].IP, check.Equals, "100.64.0.1")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// need a test with:
 | 
				
			||||||
 | 
					// tag on a host that isn't owned by a tag owners. So the namespace
 | 
				
			||||||
 | 
					// of the host should be valid.
 | 
				
			||||||
 | 
					func (s *Suite) TestInvalidTagValidNamespace(c *check.C) {
 | 
				
			||||||
 | 
						namespace, err := app.CreateNamespace("user1")
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = app.GetMachine("user1", "testmachine")
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
						hostInfo := []byte(
 | 
				
			||||||
 | 
							"{\"OS\":\"centos\",\"Hostname\":\"testmachine\",\"RequestTags\":[\"tag:foo\"]}",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						machine := Machine{
 | 
				
			||||||
 | 
							ID:             1,
 | 
				
			||||||
 | 
							MachineKey:     "12345",
 | 
				
			||||||
 | 
							NodeKey:        "bar",
 | 
				
			||||||
 | 
							DiscoKey:       "faa",
 | 
				
			||||||
 | 
							Name:           "testmachine",
 | 
				
			||||||
 | 
							IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
 | 
							Registered:     true,
 | 
				
			||||||
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
 | 
							HostInfo:       datatypes.JSON(hostInfo),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							TagOwners: TagOwners{"tag:test": []string{"user1"}},
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"user1"}, Ports: []string{"*:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].SrcIPs, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].SrcIPs[0], check.Equals, "100.64.0.1")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// tag on a host is owned by a tag owner, the tag is valid.
 | 
				
			||||||
 | 
					// an ACL rule is matching the tag to a namespace. It should not be valid since the
 | 
				
			||||||
 | 
					// host should be tied to the tag now.
 | 
				
			||||||
 | 
					func (s *Suite) TestValidTagInvalidNamespace(c *check.C) {
 | 
				
			||||||
 | 
						namespace, err := app.CreateNamespace("user1")
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = app.GetMachine("user1", "webserver")
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
						hostInfo := []byte(
 | 
				
			||||||
 | 
							"{\"OS\":\"centos\",\"Hostname\":\"webserver\",\"RequestTags\":[\"tag:webapp\"]}",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						machine := Machine{
 | 
				
			||||||
 | 
							ID:             1,
 | 
				
			||||||
 | 
							MachineKey:     "12345",
 | 
				
			||||||
 | 
							NodeKey:        "bar",
 | 
				
			||||||
 | 
							DiscoKey:       "faa",
 | 
				
			||||||
 | 
							Name:           "webserver",
 | 
				
			||||||
 | 
							IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
 | 
							Registered:     true,
 | 
				
			||||||
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
 | 
							HostInfo:       datatypes.JSON(hostInfo),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
						_, err = app.GetMachine("user1", "user")
 | 
				
			||||||
 | 
						hostInfo = []byte("{\"OS\":\"debian\",\"Hostname\":\"user\"}")
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
						machine = Machine{
 | 
				
			||||||
 | 
							ID:             2,
 | 
				
			||||||
 | 
							MachineKey:     "56789",
 | 
				
			||||||
 | 
							NodeKey:        "bar2",
 | 
				
			||||||
 | 
							DiscoKey:       "faab",
 | 
				
			||||||
 | 
							Name:           "user",
 | 
				
			||||||
 | 
							IPAddresses:    MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
 | 
							NamespaceID:    namespace.ID,
 | 
				
			||||||
 | 
							Registered:     true,
 | 
				
			||||||
 | 
							RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
 | 
							AuthKeyID:      uint(pak.ID),
 | 
				
			||||||
 | 
							HostInfo:       datatypes.JSON(hostInfo),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						app.db.Save(&machine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							TagOwners: TagOwners{"tag:webapp": []string{"user1"}},
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									Action: "accept",
 | 
				
			||||||
 | 
									Users:  []string{"user1"},
 | 
				
			||||||
 | 
									Ports:  []string{"tag:webapp:80,443"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].SrcIPs, check.HasLen, 1)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].SrcIPs[0], check.Equals, "100.64.0.2")
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts, check.HasLen, 2)
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[0].Ports.First, check.Equals, uint16(80))
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[0].Ports.Last, check.Equals, uint16(80))
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[0].IP, check.Equals, "100.64.0.1")
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[1].Ports.First, check.Equals, uint16(443))
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[1].Ports.Last, check.Equals, uint16(443))
 | 
				
			||||||
 | 
						c.Assert(app.aclRules[0].DstPorts[1].IP, check.Equals, "100.64.0.1")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestPortRange(c *check.C) {
 | 
					func (s *Suite) TestPortRange(c *check.C) {
 | 
				
			||||||
	err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_range.hujson")
 | 
						err := app.LoadACLPolicy("./tests/acls/acl_policy_basic_range.hujson")
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
@ -94,7 +340,7 @@ func (s *Suite) TestPortNamespace(c *check.C) {
 | 
				
			|||||||
	ips, _ := app.getAvailableIPs()
 | 
						ips, _ := app.getAvailableIPs()
 | 
				
			||||||
	machine := Machine{
 | 
						machine := Machine{
 | 
				
			||||||
		ID:             0,
 | 
							ID:             0,
 | 
				
			||||||
		MachineKey:     "foo",
 | 
							MachineKey:     "12345",
 | 
				
			||||||
		NodeKey:        "bar",
 | 
							NodeKey:        "bar",
 | 
				
			||||||
		DiscoKey:       "faa",
 | 
							DiscoKey:       "faa",
 | 
				
			||||||
		Name:           "testmachine",
 | 
							Name:           "testmachine",
 | 
				
			||||||
@ -165,3 +411,723 @@ func (s *Suite) TestPortGroup(c *check.C) {
 | 
				
			|||||||
	c.Assert(len(ips), check.Equals, 1)
 | 
						c.Assert(len(ips), check.Equals, 1)
 | 
				
			||||||
	c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String())
 | 
						c.Assert(rules[0].SrcIPs[0], check.Equals, ips[0].String())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_expandGroup(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							aclPolicy ACLPolicy
 | 
				
			||||||
 | 
							group     string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							want    []string
 | 
				
			||||||
 | 
							wantErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple test",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups: Groups{
 | 
				
			||||||
 | 
											"group:test": []string{"user1", "user2", "user3"},
 | 
				
			||||||
 | 
											"group:foo":  []string{"user2", "user3"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									group: "group:test",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"user1", "user2", "user3"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "InexistantGroup",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups: Groups{
 | 
				
			||||||
 | 
											"group:test": []string{"user1", "user2", "user3"},
 | 
				
			||||||
 | 
											"group:foo":  []string{"user2", "user3"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									group: "group:undefined",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{},
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := expandGroup(test.args.aclPolicy, test.args.group)
 | 
				
			||||||
 | 
								if (err != nil) != test.wantErr {
 | 
				
			||||||
 | 
									t.Errorf("expandGroup() error = %v, wantErr %v", err, test.wantErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, test.want) {
 | 
				
			||||||
 | 
									t.Errorf("expandGroup() = %v, want %v", got, test.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_expandTagOwners(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							aclPolicy ACLPolicy
 | 
				
			||||||
 | 
							tag       string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							want    []string
 | 
				
			||||||
 | 
							wantErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple tag expansion",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:test": []string{"user1"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									tag: "tag:test",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"user1"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "expand with tag and group",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups:    Groups{"group:foo": []string{"user1", "user2"}},
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:test": []string{"group:foo"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									tag: "tag:test",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"user1", "user2"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "expand with namespace and group",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups:    Groups{"group:foo": []string{"user1", "user2"}},
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:test": []string{"group:foo", "user3"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									tag: "tag:test",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"user1", "user2", "user3"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid tag",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:foo": []string{"group:foo", "user1"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									tag: "tag:test",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{},
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "invalid group",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups:    Groups{"group:bar": []string{"user1", "user2"}},
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:test": []string{"group:foo", "user2"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									tag: "tag:test",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{},
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := expandTagOwners(test.args.aclPolicy, test.args.tag)
 | 
				
			||||||
 | 
								if (err != nil) != test.wantErr {
 | 
				
			||||||
 | 
									t.Errorf("expandTagOwners() error = %v, wantErr %v", err, test.wantErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, test.want) {
 | 
				
			||||||
 | 
									t.Errorf("expandTagOwners() = %v, want %v", got, test.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_expandPorts(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							portsStr string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							want    *[]tailcfg.PortRange
 | 
				
			||||||
 | 
							wantErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "wildcard",
 | 
				
			||||||
 | 
								args: args{portsStr: "*"},
 | 
				
			||||||
 | 
								want: &[]tailcfg.PortRange{
 | 
				
			||||||
 | 
									{First: portRangeBegin, Last: portRangeEnd},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "two ports",
 | 
				
			||||||
 | 
								args: args{portsStr: "80,443"},
 | 
				
			||||||
 | 
								want: &[]tailcfg.PortRange{
 | 
				
			||||||
 | 
									{First: 80, Last: 80},
 | 
				
			||||||
 | 
									{First: 443, Last: 443},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "a range and a port",
 | 
				
			||||||
 | 
								args: args{portsStr: "80-1024,443"},
 | 
				
			||||||
 | 
								want: &[]tailcfg.PortRange{
 | 
				
			||||||
 | 
									{First: 80, Last: 1024},
 | 
				
			||||||
 | 
									{First: 443, Last: 443},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "out of bounds",
 | 
				
			||||||
 | 
								args:    args{portsStr: "854038"},
 | 
				
			||||||
 | 
								want:    nil,
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "wrong port",
 | 
				
			||||||
 | 
								args:    args{portsStr: "85a38"},
 | 
				
			||||||
 | 
								want:    nil,
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "wrong port in first",
 | 
				
			||||||
 | 
								args:    args{portsStr: "a-80"},
 | 
				
			||||||
 | 
								want:    nil,
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "wrong port in last",
 | 
				
			||||||
 | 
								args:    args{portsStr: "80-85a38"},
 | 
				
			||||||
 | 
								want:    nil,
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:    "wrong port format",
 | 
				
			||||||
 | 
								args:    args{portsStr: "80-85a38-3"},
 | 
				
			||||||
 | 
								want:    nil,
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := expandPorts(test.args.portsStr)
 | 
				
			||||||
 | 
								if (err != nil) != test.wantErr {
 | 
				
			||||||
 | 
									t.Errorf("expandPorts() error = %v, wantErr %v", err, test.wantErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, test.want) {
 | 
				
			||||||
 | 
									t.Errorf("expandPorts() = %v, want %v", got, test.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_listMachinesInNamespace(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							machines  []Machine
 | 
				
			||||||
 | 
							namespace string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
							args args
 | 
				
			||||||
 | 
							want []Machine
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "1 machine in namespace",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{Namespace: Namespace{Name: "joe"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									namespace: "joe",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: []Machine{
 | 
				
			||||||
 | 
									{Namespace: Namespace{Name: "joe"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "3 machines, 2 in namespace",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{ID: 1, Namespace: Namespace{Name: "joe"}},
 | 
				
			||||||
 | 
										{ID: 2, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
										{ID: 3, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									namespace: "marc",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: []Machine{
 | 
				
			||||||
 | 
									{ID: 2, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
									{ID: 3, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "5 machines, 0 in namespace",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{ID: 1, Namespace: Namespace{Name: "joe"}},
 | 
				
			||||||
 | 
										{ID: 2, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
										{ID: 3, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
										{ID: 4, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
										{ID: 5, Namespace: Namespace{Name: "marc"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									namespace: "mickael",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: []Machine{},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								if got := filterMachinesByNamespace(test.args.machines, test.args.namespace); !reflect.DeepEqual(
 | 
				
			||||||
 | 
									got,
 | 
				
			||||||
 | 
									test.want,
 | 
				
			||||||
 | 
								) {
 | 
				
			||||||
 | 
									t.Errorf("listMachinesInNamespace() = %v, want %v", got, test.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// nolint
 | 
				
			||||||
 | 
					func Test_expandAlias(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							machines  []Machine
 | 
				
			||||||
 | 
							aclPolicy ACLPolicy
 | 
				
			||||||
 | 
							alias     string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							want    []string
 | 
				
			||||||
 | 
							wantErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "wildcard",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias: "*",
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")}},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.78.84.227"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"*"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple group",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias: "group:accountant",
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "wrong group",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias: "group:hr",
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{},
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple ipaddress",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias:     "10.0.0.3",
 | 
				
			||||||
 | 
									machines:  []Machine{},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"10.0.0.3"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "private network",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias:    "homeNetwork",
 | 
				
			||||||
 | 
									machines: []Machine{},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Hosts: Hosts{
 | 
				
			||||||
 | 
											"homeNetwork": netaddr.MustParseIPPrefix("192.168.1.0/24"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"192.168.1.0/24"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple host",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias:     "10.0.0.1",
 | 
				
			||||||
 | 
									machines:  []Machine{},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"10.0.0.1"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple CIDR",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias:     "10.0.0.0/16",
 | 
				
			||||||
 | 
									machines:  []Machine{},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"10.0.0.0/16"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "simple tag",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias: "tag:hr-webserver",
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:hr-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:hr-webserver": []string{"joe"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"100.64.0.1", "100.64.0.2"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "No tag defined",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias: "tag:hr-webserver",
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										Groups: Groups{"group:accountant": []string{"joe", "marc"}},
 | 
				
			||||||
 | 
										TagOwners: TagOwners{
 | 
				
			||||||
 | 
											"tag:accountant-webserver": []string{"group:accountant"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{},
 | 
				
			||||||
 | 
								wantErr: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "list host in namespace without correctly tagged servers",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									alias: "joe",
 | 
				
			||||||
 | 
									machines: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want:    []string{"100.64.0.4"},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := expandAlias(
 | 
				
			||||||
 | 
									test.args.machines,
 | 
				
			||||||
 | 
									test.args.aclPolicy,
 | 
				
			||||||
 | 
									test.args.alias,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if (err != nil) != test.wantErr {
 | 
				
			||||||
 | 
									t.Errorf("expandAlias() error = %v, wantErr %v", err, test.wantErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, test.want) {
 | 
				
			||||||
 | 
									t.Errorf("expandAlias() = %v, want %v", got, test.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_excludeCorrectlyTaggedNodes(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							aclPolicy ACLPolicy
 | 
				
			||||||
 | 
							nodes     []Machine
 | 
				
			||||||
 | 
							namespace string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							want    []Machine
 | 
				
			||||||
 | 
							wantErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "exclude nodes with valid tags",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									nodes: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"foo\",\"RequestTags\":[\"tag:accountant-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									namespace: "joe",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: []Machine{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.4")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "joe"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "all nodes have invalid tags, don't exclude them",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									aclPolicy: ACLPolicy{
 | 
				
			||||||
 | 
										TagOwners: TagOwners{"tag:accountant-webserver": []string{"joe"}},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									nodes: []Machine{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
											HostInfo: []byte(
 | 
				
			||||||
 | 
												"{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}",
 | 
				
			||||||
 | 
											),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									namespace: "joe",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: []Machine{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
											netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										HostInfo: []byte(
 | 
				
			||||||
 | 
											"{\"OS\":\"centos\",\"Hostname\":\"hr-web1\",\"RequestTags\":[\"tag:hr-webserver\"]}",
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
											netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										HostInfo: []byte(
 | 
				
			||||||
 | 
											"{\"OS\":\"centos\",\"Hostname\":\"hr-web2\",\"RequestTags\":[\"tag:hr-webserver\"]}",
 | 
				
			||||||
 | 
										),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
											netaddr.MustParseIP("100.64.0.4"),
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								wantErr: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, test := range tests {
 | 
				
			||||||
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := excludeCorrectlyTaggedNodes(
 | 
				
			||||||
 | 
									test.args.aclPolicy,
 | 
				
			||||||
 | 
									test.args.nodes,
 | 
				
			||||||
 | 
									test.args.namespace,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if (err != nil) != test.wantErr {
 | 
				
			||||||
 | 
									t.Errorf(
 | 
				
			||||||
 | 
										"excludeCorrectlyTaggedNodes() error = %v, wantErr %v",
 | 
				
			||||||
 | 
										err,
 | 
				
			||||||
 | 
										test.wantErr,
 | 
				
			||||||
 | 
									)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, test.want) {
 | 
				
			||||||
 | 
									t.Errorf("excludeCorrectlyTaggedNodes() = %v, want %v", got, test.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										22
									
								
								api.go
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								api.go
									
									
									
									
									
								
							@ -261,7 +261,16 @@ func (h *Headscale) getMapResponse(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	var respBody []byte
 | 
						var respBody []byte
 | 
				
			||||||
	if req.Compress == "zstd" {
 | 
						if req.Compress == "zstd" {
 | 
				
			||||||
		src, _ := json.Marshal(resp)
 | 
							src, err := json.Marshal(resp)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Str("func", "getMapResponse").
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Failed to marshal response for the client")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		encoder, _ := zstd.NewWriter(nil)
 | 
							encoder, _ := zstd.NewWriter(nil)
 | 
				
			||||||
		srcCompressed := encoder.EncodeAll(src, nil)
 | 
							srcCompressed := encoder.EncodeAll(src, nil)
 | 
				
			||||||
@ -290,7 +299,16 @@ func (h *Headscale) getMapKeepAliveResponse(
 | 
				
			|||||||
	var respBody []byte
 | 
						var respBody []byte
 | 
				
			||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	if mapRequest.Compress == "zstd" {
 | 
						if mapRequest.Compress == "zstd" {
 | 
				
			||||||
		src, _ := json.Marshal(mapResponse)
 | 
							src, err := json.Marshal(mapResponse)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Str("func", "getMapKeepAliveResponse").
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Failed to marshal keepalive response for the client")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		encoder, _ := zstd.NewWriter(nil)
 | 
							encoder, _ := zstd.NewWriter(nil)
 | 
				
			||||||
		srcCompressed := encoder.EncodeAll(src, nil)
 | 
							srcCompressed := encoder.EncodeAll(src, nil)
 | 
				
			||||||
		respBody = h.privateKey.SealTo(machineKey, srcCompressed)
 | 
							respBody = h.privateKey.SealTo(machineKey, srcCompressed)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								dns.go
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								dns.go
									
									
									
									
									
								
							@ -163,7 +163,15 @@ func getMapResponseDNSConfig(
 | 
				
			|||||||
		dnsConfig = dnsConfigOrig.Clone()
 | 
							dnsConfig = dnsConfigOrig.Clone()
 | 
				
			||||||
		dnsConfig.Domains = append(
 | 
							dnsConfig.Domains = append(
 | 
				
			||||||
			dnsConfig.Domains,
 | 
								dnsConfig.Domains,
 | 
				
			||||||
			fmt.Sprintf("%s.%s", machine.Namespace.Name, baseDomain),
 | 
								fmt.Sprintf(
 | 
				
			||||||
 | 
									"%s.%s",
 | 
				
			||||||
 | 
									strings.ReplaceAll(
 | 
				
			||||||
 | 
										machine.Namespace.Name,
 | 
				
			||||||
 | 
										"@",
 | 
				
			||||||
 | 
										".",
 | 
				
			||||||
 | 
									), // Replace @ with . for valid domain for machine
 | 
				
			||||||
 | 
									baseDomain,
 | 
				
			||||||
 | 
								),
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		namespaceSet := set.New(set.ThreadSafe)
 | 
							namespaceSet := set.New(set.ThreadSafe)
 | 
				
			||||||
@ -171,8 +179,14 @@ func getMapResponseDNSConfig(
 | 
				
			|||||||
		for _, p := range peers {
 | 
							for _, p := range peers {
 | 
				
			||||||
			namespaceSet.Add(p.Namespace)
 | 
								namespaceSet.Add(p.Namespace)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		for _, namespace := range namespaceSet.List() {
 | 
							for _, ns := range namespaceSet.List() {
 | 
				
			||||||
			dnsRoute := fmt.Sprintf("%s.%s", namespace.(Namespace).Name, baseDomain)
 | 
								namespace, ok := ns.(Namespace)
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									dnsConfig = dnsConfigOrig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								dnsRoute := fmt.Sprintf("%v.%v", namespace.Name, baseDomain)
 | 
				
			||||||
			dnsConfig.Routes[dnsRoute] = nil
 | 
								dnsConfig.Routes[dnsRoute] = nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
				
			|||||||
@ -39,6 +39,14 @@ use namespaces (which are the equivalent to user/logins in Tailscale.com).
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
 | 
					Please check https://tailscale.com/kb/1018/acls/, and `./tests/acls/` in this repo for working examples.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					When using ACL's the Namespace borders are no longer applied. All machines
 | 
				
			||||||
 | 
					whichever the Namespace have the ability to communicate with other hosts as
 | 
				
			||||||
 | 
					long as the ACL's permits this exchange.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The [ACLs](acls.md) document should help understand a fictional case of setting
 | 
				
			||||||
 | 
					up ACLs in a small company. All concepts presented in this document could be
 | 
				
			||||||
 | 
					applied outside of business oriented usage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Apple devices
 | 
					### Apple devices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
 | 
					An endpoint with information on how to connect your Apple devices (currently macOS only) is available at `/apple` on your running instance.
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										141
									
								
								docs/acls.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								docs/acls.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					# ACLs use case example
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Let's build an example use case for a small business (It may be the place where
 | 
				
			||||||
 | 
					ACL's are the most useful).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We have a small company with a boss, an admin, two developers and an intern.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The boss should have access to all servers but not to the users hosts. Admin
 | 
				
			||||||
 | 
					should also have access to all hosts except that their permissions should be
 | 
				
			||||||
 | 
					limited to maintaining the hosts (for example purposes). The developers can do
 | 
				
			||||||
 | 
					anything they want on dev hosts, but only watch on productions hosts. Intern
 | 
				
			||||||
 | 
					can only interact with the development servers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each user have at least a device connected to the network and we have some
 | 
				
			||||||
 | 
					servers.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- database.prod
 | 
				
			||||||
 | 
					- database.dev
 | 
				
			||||||
 | 
					- app-server1.prod
 | 
				
			||||||
 | 
					- app-server1.dev
 | 
				
			||||||
 | 
					- billing.internal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Setup of the network
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Let's create the namespaces. Each user should have his own namespace. The users
 | 
				
			||||||
 | 
					here are represented as namespaces.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					headscale namespaces create boss
 | 
				
			||||||
 | 
					headscale namespaces create admin1
 | 
				
			||||||
 | 
					headscale namespaces create dev1
 | 
				
			||||||
 | 
					headscale namespaces create dev2
 | 
				
			||||||
 | 
					headscale namespaces create intern1
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					We don't need to create namespaces for the servers because the servers will be
 | 
				
			||||||
 | 
					tagged. When registering the servers we will need to add the flag
 | 
				
			||||||
 | 
					`--advertised-tags=tag:<tag1>,tag:<tag2>`, and the user (namespace) that is
 | 
				
			||||||
 | 
					registering the server should be allowed to do it. Since anyone can add tags to
 | 
				
			||||||
 | 
					a server they can register, the check of the tags is done on headscale server
 | 
				
			||||||
 | 
					and only valid tags are applied. A tag is valid if the namespace that is
 | 
				
			||||||
 | 
					registering it is allowed to do it.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Here are the ACL's to implement the same permissions as above:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  // groups are collections of users having a common scope. A user can be in multiple groups
 | 
				
			||||||
 | 
					  // groups cannot be composed of groups
 | 
				
			||||||
 | 
					  "groups": {
 | 
				
			||||||
 | 
					    "group:boss": ["boss"],
 | 
				
			||||||
 | 
					    "group:dev": ["dev1", "dev2"],
 | 
				
			||||||
 | 
					    "group:admin": ["admin1"],
 | 
				
			||||||
 | 
					    "group:intern": ["intern1"]
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  // tagOwners in tailscale is an association between a TAG and the people allowed to set this TAG on a server.
 | 
				
			||||||
 | 
					  // This is documented [here](https://tailscale.com/kb/1068/acl-tags#defining-a-tag)
 | 
				
			||||||
 | 
					  // and explained [here](https://tailscale.com/blog/rbac-like-it-was-meant-to-be/)
 | 
				
			||||||
 | 
					  "tagOwners": {
 | 
				
			||||||
 | 
					    // the administrators can add servers in production
 | 
				
			||||||
 | 
					    "tag:prod-databases": ["group:admin"],
 | 
				
			||||||
 | 
					    "tag:prod-app-servers": ["group:admin"],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // the boss can tag any server as internal
 | 
				
			||||||
 | 
					    "tag:internal": ["group:boss"],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // dev can add servers for dev purposes as well as admins
 | 
				
			||||||
 | 
					    "tag:dev-databases": ["group:admin", "group:dev"],
 | 
				
			||||||
 | 
					    "tag:dev-app-servers": ["group:admin", "group:dev"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // interns cannot add servers
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "acls": [
 | 
				
			||||||
 | 
					    // boss have access to all servers
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "action": "accept",
 | 
				
			||||||
 | 
					      "users": ["group:boss"],
 | 
				
			||||||
 | 
					      "ports": [
 | 
				
			||||||
 | 
					        "tag:prod-databases:*",
 | 
				
			||||||
 | 
					        "tag:prod-app-servers:*",
 | 
				
			||||||
 | 
					        "tag:internal:*",
 | 
				
			||||||
 | 
					        "tag:dev-databases:*",
 | 
				
			||||||
 | 
					        "tag:dev-app-servers:*"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // admin have only access to administrative ports of the servers
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "action": "accept",
 | 
				
			||||||
 | 
					      "users": ["group:admin"],
 | 
				
			||||||
 | 
					      "ports": [
 | 
				
			||||||
 | 
					        "tag:prod-databases:22",
 | 
				
			||||||
 | 
					        "tag:prod-app-servers:22",
 | 
				
			||||||
 | 
					        "tag:internal:22",
 | 
				
			||||||
 | 
					        "tag:dev-databases:22",
 | 
				
			||||||
 | 
					        "tag:dev-app-servers:22"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // developers have access to databases servers and application servers on all ports
 | 
				
			||||||
 | 
					    // they can only view the applications servers in prod and have no access to databases servers in production
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "action": "accept",
 | 
				
			||||||
 | 
					      "users": ["group:dev"],
 | 
				
			||||||
 | 
					      "ports": [
 | 
				
			||||||
 | 
					        "tag:dev-databases:*",
 | 
				
			||||||
 | 
					        "tag:dev-app-servers:*",
 | 
				
			||||||
 | 
					        "tag:prod-app-servers:80,443"
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // servers should be able to talk to database. Database should not be able to initiate connections to
 | 
				
			||||||
 | 
					    // applications servers
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "action": "accept",
 | 
				
			||||||
 | 
					      "users": ["tag:dev-app-servers"],
 | 
				
			||||||
 | 
					      "ports": ["tag:dev-databases:5432"]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "action": "accept",
 | 
				
			||||||
 | 
					      "users": ["tag:prod-app-servers"],
 | 
				
			||||||
 | 
					      "ports": ["tag:prod-databases:5432"]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // interns have access to dev-app-servers only in reading mode
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "action": "accept",
 | 
				
			||||||
 | 
					      "users": ["group:intern"],
 | 
				
			||||||
 | 
					      "ports": ["tag:dev-app-servers:80,443"]
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We still have to allow internal namespaces communications since nothing guarantees that each user have
 | 
				
			||||||
 | 
					    // their own namespaces.
 | 
				
			||||||
 | 
					    { "action": "accept", "users": ["boss"], "ports": ["boss:*"] },
 | 
				
			||||||
 | 
					    { "action": "accept", "users": ["dev1"], "ports": ["dev1:*"] },
 | 
				
			||||||
 | 
					    { "action": "accept", "users": ["dev2"], "ports": ["dev2:*"] },
 | 
				
			||||||
 | 
					    { "action": "accept", "users": ["admin1"], "ports": ["admin1:*"] },
 | 
				
			||||||
 | 
					    { "action": "accept", "users": ["intern1"], "ports": ["intern1:*"] }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										193
									
								
								machine.go
									
									
									
									
									
								
							
							
						
						
									
										193
									
								
								machine.go
									
									
									
									
									
								
							@ -119,6 +119,118 @@ func (machine Machine) isExpired() bool {
 | 
				
			|||||||
	return time.Now().UTC().After(*machine.Expiry)
 | 
						return time.Now().UTC().After(*machine.Expiry)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (h *Headscale) ListAllMachines() ([]Machine, error) {
 | 
				
			||||||
 | 
						machines := []Machine{}
 | 
				
			||||||
 | 
						if err := h.db.Preload("AuthKey").
 | 
				
			||||||
 | 
							Preload("AuthKey.Namespace").
 | 
				
			||||||
 | 
							Preload("Namespace").
 | 
				
			||||||
 | 
							Where("registered").
 | 
				
			||||||
 | 
							Find(&machines).Error; err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return machines, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func containsAddresses(inputs []string, addrs []string) bool {
 | 
				
			||||||
 | 
						for _, addr := range addrs {
 | 
				
			||||||
 | 
							if containsString(inputs, addr) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// matchSourceAndDestinationWithRule.
 | 
				
			||||||
 | 
					func matchSourceAndDestinationWithRule(
 | 
				
			||||||
 | 
						ruleSources []string,
 | 
				
			||||||
 | 
						ruleDestinations []string,
 | 
				
			||||||
 | 
						source []string,
 | 
				
			||||||
 | 
						destination []string,
 | 
				
			||||||
 | 
					) bool {
 | 
				
			||||||
 | 
						return containsAddresses(ruleSources, source) &&
 | 
				
			||||||
 | 
							containsAddresses(ruleDestinations, destination)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getFilteredByACLPeerss should return the list of peers authorized to be accessed from machine.
 | 
				
			||||||
 | 
					func getFilteredByACLPeers(
 | 
				
			||||||
 | 
						machines []Machine,
 | 
				
			||||||
 | 
						rules []tailcfg.FilterRule,
 | 
				
			||||||
 | 
						machine *Machine,
 | 
				
			||||||
 | 
					) Machines {
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Caller().
 | 
				
			||||||
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
 | 
							Msg("Finding peers filtered by ACLs")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						peers := make(map[uint64]Machine)
 | 
				
			||||||
 | 
						// Aclfilter peers here. We are itering through machines in all namespaces and search through the computed aclRules
 | 
				
			||||||
 | 
						// for match between rule SrcIPs and DstPorts. If the rule is a match we allow the machine to be viewable.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FIXME: On official control plane if a rule allow user A to talk to user B but NO rule allows user B to talk to
 | 
				
			||||||
 | 
						// user A. The behaviour is the following
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// On official tailscale control plane:
 | 
				
			||||||
 | 
						//   on first `tailscale status`` on node A we can see node B. The `tailscale status` command on node B doesn't show node A
 | 
				
			||||||
 | 
						//   We can successfully establish a communication from A to B. When it's done, if we run the `tailscale status` command
 | 
				
			||||||
 | 
						//   on node B again we can now see node A. It's not possible to establish a communication from node B to node A.
 | 
				
			||||||
 | 
						// On this implementation of the feature
 | 
				
			||||||
 | 
						//   on any `tailscale status` command on node A we can see node B. The `tailscale status` command on node B DOES show A.
 | 
				
			||||||
 | 
						//
 | 
				
			||||||
 | 
						// I couldn't find a way to not clutter the output of `tailscale status` with all nodes that we could be talking to.
 | 
				
			||||||
 | 
						// In order to do this we would need to be able to identify that node A want to talk to node B but that Node B doesn't know
 | 
				
			||||||
 | 
						// how to talk to node A and then add the peering resource.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, peer := range machines {
 | 
				
			||||||
 | 
							if peer.ID == machine.ID {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for _, rule := range rules {
 | 
				
			||||||
 | 
								var dst []string
 | 
				
			||||||
 | 
								for _, d := range rule.DstPorts {
 | 
				
			||||||
 | 
									dst = append(dst, d.IP)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if matchSourceAndDestinationWithRule(
 | 
				
			||||||
 | 
									rule.SrcIPs,
 | 
				
			||||||
 | 
									dst,
 | 
				
			||||||
 | 
									machine.IPAddresses.ToStringSlice(),
 | 
				
			||||||
 | 
									peer.IPAddresses.ToStringSlice(),
 | 
				
			||||||
 | 
								) || // match source and destination
 | 
				
			||||||
 | 
									matchSourceAndDestinationWithRule(
 | 
				
			||||||
 | 
										rule.SrcIPs,
 | 
				
			||||||
 | 
										dst,
 | 
				
			||||||
 | 
										machine.IPAddresses.ToStringSlice(),
 | 
				
			||||||
 | 
										[]string{"*"},
 | 
				
			||||||
 | 
									) || // match source and all destination
 | 
				
			||||||
 | 
									matchSourceAndDestinationWithRule(
 | 
				
			||||||
 | 
										rule.SrcIPs,
 | 
				
			||||||
 | 
										dst,
 | 
				
			||||||
 | 
										peer.IPAddresses.ToStringSlice(),
 | 
				
			||||||
 | 
										machine.IPAddresses.ToStringSlice(),
 | 
				
			||||||
 | 
									) { // match return path
 | 
				
			||||||
 | 
									peers[peer.ID] = peer
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						authorizedPeers := make([]Machine, 0, len(peers))
 | 
				
			||||||
 | 
						for _, m := range peers {
 | 
				
			||||||
 | 
							authorizedPeers = append(authorizedPeers, m)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort.Slice(
 | 
				
			||||||
 | 
							authorizedPeers,
 | 
				
			||||||
 | 
							func(i, j int) bool { return authorizedPeers[i].ID < authorizedPeers[j].ID },
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						log.Trace().
 | 
				
			||||||
 | 
							Caller().
 | 
				
			||||||
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
 | 
							Msgf("Found some machines: %v", machines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return authorizedPeers
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getDirectPeers(machine *Machine) (Machines, error) {
 | 
					func (h *Headscale) getDirectPeers(machine *Machine) (Machines, error) {
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
		Caller().
 | 
							Caller().
 | 
				
			||||||
@ -206,39 +318,54 @@ func (h *Headscale) getSharedTo(machine *Machine) (Machines, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
 | 
					func (h *Headscale) getPeers(machine *Machine) (Machines, error) {
 | 
				
			||||||
	direct, err := h.getDirectPeers(machine)
 | 
						var peers Machines
 | 
				
			||||||
	if err != nil {
 | 
						var err error
 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Cannot fetch peers")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return Machines{}, err
 | 
						// If ACLs rules are defined, filter visible host list with the ACLs
 | 
				
			||||||
 | 
						// else use the classic namespace scope
 | 
				
			||||||
 | 
						if h.aclPolicy != nil {
 | 
				
			||||||
 | 
							var machines []Machine
 | 
				
			||||||
 | 
							machines, err = h.ListAllMachines()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().Err(err).Msg("Error retrieving list of machines")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return Machines{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							peers = getFilteredByACLPeers(machines, h.aclRules, machine)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							direct, err := h.getDirectPeers(machine)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Cannot fetch peers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return Machines{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							shared, err := h.getShared(machine)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Cannot fetch peers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return Machines{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sharedTo, err := h.getSharedTo(machine)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Cannot fetch peers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return Machines{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							peers = append(direct, shared...)
 | 
				
			||||||
 | 
							peers = append(peers, sharedTo...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	shared, err := h.getShared(machine)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Cannot fetch peers")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return Machines{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sharedTo, err := h.getSharedTo(machine)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Error().
 | 
					 | 
				
			||||||
			Caller().
 | 
					 | 
				
			||||||
			Err(err).
 | 
					 | 
				
			||||||
			Msg("Cannot fetch peers")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return Machines{}, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	peers := append(direct, shared...)
 | 
					 | 
				
			||||||
	peers = append(peers, sharedTo...)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
 | 
						sort.Slice(peers, func(i, j int) bool { return peers[i].ID < peers[j].ID })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Trace().
 | 
						log.Trace().
 | 
				
			||||||
@ -597,7 +724,11 @@ func (machine Machine) toNode(
 | 
				
			|||||||
		hostname = fmt.Sprintf(
 | 
							hostname = fmt.Sprintf(
 | 
				
			||||||
			"%s.%s.%s",
 | 
								"%s.%s.%s",
 | 
				
			||||||
			machine.Name,
 | 
								machine.Name,
 | 
				
			||||||
			machine.Namespace.Name,
 | 
								strings.ReplaceAll(
 | 
				
			||||||
 | 
									machine.Namespace.Name,
 | 
				
			||||||
 | 
									"@",
 | 
				
			||||||
 | 
									".",
 | 
				
			||||||
 | 
								), // Replace @ with . for valid domain for machine
 | 
				
			||||||
			baseDomain,
 | 
								baseDomain,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										262
									
								
								machine_test.go
									
									
									
									
									
								
							
							
						
						
									
										262
									
								
								machine_test.go
									
									
									
									
									
								
							@ -1,11 +1,15 @@
 | 
				
			|||||||
package headscale
 | 
					package headscale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gopkg.in/check.v1"
 | 
						"gopkg.in/check.v1"
 | 
				
			||||||
	"inet.af/netaddr"
 | 
						"inet.af/netaddr"
 | 
				
			||||||
 | 
						"tailscale.com/tailcfg"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestGetMachine(c *check.C) {
 | 
					func (s *Suite) TestGetMachine(c *check.C) {
 | 
				
			||||||
@ -154,6 +158,89 @@ func (s *Suite) TestGetDirectPeers(c *check.C) {
 | 
				
			|||||||
	c.Assert(peersOfMachine0[8].Name, check.Equals, "testmachine10")
 | 
						c.Assert(peersOfMachine0[8].Name, check.Equals, "testmachine10")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (s *Suite) TestGetACLFilteredPeers(c *check.C) {
 | 
				
			||||||
 | 
						type base struct {
 | 
				
			||||||
 | 
							namespace *Namespace
 | 
				
			||||||
 | 
							key       *PreAuthKey
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stor := make([]base, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, name := range []string{"test", "admin"} {
 | 
				
			||||||
 | 
							namespace, err := app.CreateNamespace(name)
 | 
				
			||||||
 | 
							c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
							pak, err := app.CreatePreAuthKey(namespace.Name, false, false, nil)
 | 
				
			||||||
 | 
							c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
							stor = append(stor, base{namespace, pak})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := app.GetMachineByID(0)
 | 
				
			||||||
 | 
						c.Assert(err, check.NotNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for index := 0; index <= 10; index++ {
 | 
				
			||||||
 | 
							machine := Machine{
 | 
				
			||||||
 | 
								ID:         uint64(index),
 | 
				
			||||||
 | 
								MachineKey: "foo" + strconv.Itoa(index),
 | 
				
			||||||
 | 
								NodeKey:    "bar" + strconv.Itoa(index),
 | 
				
			||||||
 | 
								DiscoKey:   "faa" + strconv.Itoa(index),
 | 
				
			||||||
 | 
								IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
									netaddr.MustParseIP(fmt.Sprintf("100.64.0.%v", strconv.Itoa(index+1))),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Name:           "testmachine" + strconv.Itoa(index),
 | 
				
			||||||
 | 
								NamespaceID:    stor[index%2].namespace.ID,
 | 
				
			||||||
 | 
								Registered:     true,
 | 
				
			||||||
 | 
								RegisterMethod: RegisterMethodAuthKey,
 | 
				
			||||||
 | 
								AuthKeyID:      uint(stor[index%2].key.ID),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							app.db.Save(&machine)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						app.aclPolicy = &ACLPolicy{
 | 
				
			||||||
 | 
							Groups: map[string][]string{
 | 
				
			||||||
 | 
								"group:test": {"admin"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Hosts:     map[string]netaddr.IPPrefix{},
 | 
				
			||||||
 | 
							TagOwners: map[string][]string{},
 | 
				
			||||||
 | 
							ACLs: []ACL{
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"admin"}, Ports: []string{"*:*"}},
 | 
				
			||||||
 | 
								{Action: "accept", Users: []string{"test"}, Ports: []string{"test:*"}},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Tests: []ACLTest{},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err = app.UpdateACLRules()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						adminMachine, err := app.GetMachineByID(1)
 | 
				
			||||||
 | 
						c.Logf("Machine(%v), namespace: %v", adminMachine.Name, adminMachine.Namespace)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						testMachine, err := app.GetMachineByID(2)
 | 
				
			||||||
 | 
						c.Logf("Machine(%v), namespace: %v", testMachine.Name, testMachine.Namespace)
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = testMachine.GetHostInfo()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						machines, err := app.ListAllMachines()
 | 
				
			||||||
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						peersOfTestMachine := getFilteredByACLPeers(machines, app.aclRules, testMachine)
 | 
				
			||||||
 | 
						peersOfAdminMachine := getFilteredByACLPeers(machines, app.aclRules, adminMachine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Log(peersOfTestMachine)
 | 
				
			||||||
 | 
						c.Assert(len(peersOfTestMachine), check.Equals, 4)
 | 
				
			||||||
 | 
						c.Assert(peersOfTestMachine[0].Name, check.Equals, "testmachine4")
 | 
				
			||||||
 | 
						c.Assert(peersOfTestMachine[1].Name, check.Equals, "testmachine6")
 | 
				
			||||||
 | 
						c.Assert(peersOfTestMachine[3].Name, check.Equals, "testmachine10")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						c.Log(peersOfAdminMachine)
 | 
				
			||||||
 | 
						c.Assert(len(peersOfAdminMachine), check.Equals, 9)
 | 
				
			||||||
 | 
						c.Assert(peersOfAdminMachine[0].Name, check.Equals, "testmachine2")
 | 
				
			||||||
 | 
						c.Assert(peersOfAdminMachine[2].Name, check.Equals, "testmachine4")
 | 
				
			||||||
 | 
						c.Assert(peersOfAdminMachine[5].Name, check.Equals, "testmachine7")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Suite) TestExpireMachine(c *check.C) {
 | 
					func (s *Suite) TestExpireMachine(c *check.C) {
 | 
				
			||||||
	namespace, err := app.CreateNamespace("test")
 | 
						namespace, err := app.CreateNamespace("test")
 | 
				
			||||||
	c.Assert(err, check.IsNil)
 | 
						c.Assert(err, check.IsNil)
 | 
				
			||||||
@ -208,3 +295,178 @@ func (s *Suite) TestSerdeAddressStrignSlice(c *check.C) {
 | 
				
			|||||||
		c.Assert(deserialized[i], check.Equals, input[i])
 | 
							c.Assert(deserialized[i], check.Equals, input[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_getFilteredByACLPeers(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							machines []Machine
 | 
				
			||||||
 | 
							rules    []tailcfg.FilterRule
 | 
				
			||||||
 | 
							machine  *Machine
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
							args args
 | 
				
			||||||
 | 
							want Machines
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "all hosts can talk to each other",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									machines: []Machine{ // list of all machines in the database
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 1,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 2,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 3,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											SrcIPs: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
 | 
				
			||||||
 | 
											DstPorts: []tailcfg.NetPortRange{
 | 
				
			||||||
 | 
												{IP: "*"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									machine: &Machine{ // current machine
 | 
				
			||||||
 | 
										ID:          1,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "joe"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: Machines{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ID:          2,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "marc"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ID:          3,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "One host can talk to another, but not all hosts",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									machines: []Machine{ // list of all machines in the database
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 1,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 2,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 3,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											SrcIPs: []string{"100.64.0.1", "100.64.0.2", "100.64.0.3"},
 | 
				
			||||||
 | 
											DstPorts: []tailcfg.NetPortRange{
 | 
				
			||||||
 | 
												{IP: "100.64.0.2"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									machine: &Machine{ // current machine
 | 
				
			||||||
 | 
										ID:          1,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.1")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "joe"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: Machines{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ID:          2,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "marc"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "host cannot directly talk to destination, but return path is authorized",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									machines: []Machine{ // list of all machines in the database
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 1,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.1"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "joe"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 2,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.2"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "marc"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ID: 3,
 | 
				
			||||||
 | 
											IPAddresses: MachineAddresses{
 | 
				
			||||||
 | 
												netaddr.MustParseIP("100.64.0.3"),
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
											Namespace: Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									rules: []tailcfg.FilterRule{ // list of all ACLRules registered
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											SrcIPs: []string{"100.64.0.3"},
 | 
				
			||||||
 | 
											DstPorts: []tailcfg.NetPortRange{
 | 
				
			||||||
 | 
												{IP: "100.64.0.2"},
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									machine: &Machine{ // current machine
 | 
				
			||||||
 | 
										ID:          1,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.2")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "marc"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								want: Machines{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										ID:          3,
 | 
				
			||||||
 | 
										IPAddresses: MachineAddresses{netaddr.MustParseIP("100.64.0.3")},
 | 
				
			||||||
 | 
										Namespace:   Namespace{Name: "mickael"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got := getFilteredByACLPeers(
 | 
				
			||||||
 | 
									tt.args.machines,
 | 
				
			||||||
 | 
									tt.args.rules,
 | 
				
			||||||
 | 
									tt.args.machine,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, tt.want) {
 | 
				
			||||||
 | 
									t.Errorf("getFilteredByACLPeers() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								poll.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								poll.go
									
									
									
									
									
								
							@ -85,12 +85,26 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 | 
				
			|||||||
		Str("machine", machine.Name).
 | 
							Str("machine", machine.Name).
 | 
				
			||||||
		Msg("Found machine in database")
 | 
							Msg("Found machine in database")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	hostinfo, _ := json.Marshal(req.Hostinfo)
 | 
						hostinfo, err := json.Marshal(req.Hostinfo)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	machine.Name = req.Hostinfo.Hostname
 | 
						machine.Name = req.Hostinfo.Hostname
 | 
				
			||||||
	machine.HostInfo = datatypes.JSON(hostinfo)
 | 
						machine.HostInfo = datatypes.JSON(hostinfo)
 | 
				
			||||||
	machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
 | 
						machine.DiscoKey = DiscoPublicKeyStripPrefix(req.DiscoKey)
 | 
				
			||||||
	now := time.Now().UTC()
 | 
						now := time.Now().UTC()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// update ACLRules with peer informations (to update server tags if necessary)
 | 
				
			||||||
 | 
						if h.aclPolicy != nil {
 | 
				
			||||||
 | 
							err = h.UpdateACLRules()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Str("func", "handleAuthKey").
 | 
				
			||||||
 | 
									Str("machine", machine.Name).
 | 
				
			||||||
 | 
									Err(err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	// From Tailscale client:
 | 
						// From Tailscale client:
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// ReadOnly is whether the client just wants to fetch the MapResponse,
 | 
						// ReadOnly is whether the client just wants to fetch the MapResponse,
 | 
				
			||||||
@ -100,7 +114,17 @@ func (h *Headscale) PollNetMapHandler(ctx *gin.Context) {
 | 
				
			|||||||
	// The intended use is for clients to discover the DERP map at start-up
 | 
						// The intended use is for clients to discover the DERP map at start-up
 | 
				
			||||||
	// before their first real endpoint update.
 | 
						// before their first real endpoint update.
 | 
				
			||||||
	if !req.ReadOnly {
 | 
						if !req.ReadOnly {
 | 
				
			||||||
		endpoints, _ := json.Marshal(req.Endpoints)
 | 
							endpoints, err := json.Marshal(req.Endpoints)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Error().
 | 
				
			||||||
 | 
									Caller().
 | 
				
			||||||
 | 
									Str("func", "PollNetMapHandler").
 | 
				
			||||||
 | 
									Err(err).
 | 
				
			||||||
 | 
									Msg("Failed to mashal requested endpoints for the client")
 | 
				
			||||||
 | 
								ctx.String(http.StatusInternalServerError, ":(")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		machine.Endpoints = datatypes.JSON(endpoints)
 | 
							machine.Endpoints = datatypes.JSON(endpoints)
 | 
				
			||||||
		machine.LastSeen = &now
 | 
							machine.LastSeen = &now
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								utils.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								utils.go
									
									
									
									
									
								
							@ -212,6 +212,16 @@ func (h *Headscale) getUsedIPs() ([]netaddr.IP, error) {
 | 
				
			|||||||
	return ips, nil
 | 
						return ips, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func containsString(ss []string, s string) bool {
 | 
				
			||||||
 | 
						for _, v := range ss {
 | 
				
			||||||
 | 
							if v == s {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func containsIPs(ips []netaddr.IP, ip netaddr.IP) bool {
 | 
					func containsIPs(ips []netaddr.IP, ip netaddr.IP) bool {
 | 
				
			||||||
	for _, v := range ips {
 | 
						for _, v := range ips {
 | 
				
			||||||
		if v == ip {
 | 
							if v == ip {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user