diff --git a/hscontrol/acls.go b/hscontrol/acls.go index 449c7ffd..919b5add 100644 --- a/hscontrol/acls.go +++ b/hscontrol/acls.go @@ -119,9 +119,82 @@ func (h *Headscale) LoadACLPolicyFromBytes(acl []byte, format string) error { h.aclPolicy = &policy + machines, err := h.ListMachines() + if err != nil { + return err + } + + if h.aclPolicy.ForcedTags != nil { + forcedTagsByIp := make(map[netip.Addr][]string) + + for tag, _ := range h.aclPolicy.ForcedTags { + var expandedHosts []string + + err := expandNestedTagsToHosts(h.aclPolicy.ForcedTags, tag, &expandedHosts, 0) + if err != nil { + return err + } + + for _, expandedHost := range expandedHosts { + ipForExpandedHost := h.aclPolicy.Hosts[expandedHost].Addr() + + forcedTags, _ := forcedTagsByIp[ipForExpandedHost] + + forcedTagsByIp[ipForExpandedHost] = append(forcedTags, tag) + } + } + + for _, machine := range machines { + machine, err := h.GetMachineByID(machine.ID) + if err != nil { + return err + } + + machine.ForcedTags = []string{} + + for _, ip := range machine.IPAddresses { + forcedTags, ok := forcedTagsByIp[ip] + + if ok { + log.Info(). + Str("machine", machine.String()). + Strs("forcedTags", forcedTags). + Msg("Setting forced tags") + + machine.ForcedTags = forcedTags + } + } + + if err := h.db.Save(machine).Error; err != nil { + return fmt.Errorf("failed to update tags for machine in the database: %w", err) + } + } + + h.setLastStateChangeToNow() + } + return h.UpdateACLRules() } +func expandNestedTagsToHosts(forcedTags ForcedTags, tag string, into *[]string, depth int) error { + if depth > 5 { + log.Error(). + Msgf("Recursed too deeply trying to expand %s, expanded %v so far", tag, *into) + + return fmt.Errorf("Recursed too deeply") + } + + for _, hostOrTag := range forcedTags[tag] { + if !strings.HasPrefix(hostOrTag, "tag:") { + *into = append(*into, hostOrTag) + } else { + expandNestedTagsToHosts(forcedTags, hostOrTag, into, depth + 1) + } + } + + return nil +} + func (h *Headscale) UpdateACLRules() error { machines, err := h.ListMachines() if err != nil { diff --git a/hscontrol/acls_types.go b/hscontrol/acls_types.go index 0e553515..71458d4c 100644 --- a/hscontrol/acls_types.go +++ b/hscontrol/acls_types.go @@ -14,6 +14,7 @@ type ACLPolicy struct { Groups Groups `json:"groups" yaml:"groups"` Hosts Hosts `json:"hosts" yaml:"hosts"` TagOwners TagOwners `json:"tagOwners" yaml:"tagOwners"` + ForcedTags ForcedTags `json:"forcedTags" yaml:"forcedTags"` ACLs []ACL `json:"acls" yaml:"acls"` Tests []ACLTest `json:"tests" yaml:"tests"` AutoApprovers AutoApprovers `json:"autoApprovers" yaml:"autoApprovers"` @@ -28,6 +29,9 @@ type ACL struct { Destinations []string `json:"dst" yaml:"dst"` } +// ForcedTags specifies which tags are applied to which hosts by the server +type ForcedTags map[string][]string + // Groups references a series of alias in the ACL rules. type Groups map[string][]string