|
1 | 1 | package v2 |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "cmp" |
4 | 5 | "encoding/json" |
5 | 6 | "errors" |
6 | 7 | "fmt" |
@@ -560,12 +561,6 @@ func (pm *PolicyManager) NodeCanHaveTag(node types.NodeView, tag string) bool { |
560 | 561 | return false |
561 | 562 | } |
562 | 563 |
|
563 | | - // Tagged nodes can only keep tags they already have via client requests. |
564 | | - // (Admin API bypasses this function entirely and can modify any tags.) |
565 | | - if node.IsTagged() { |
566 | | - return node.HasTag(tag) |
567 | | - } |
568 | | - |
569 | 564 | // Check if node's owner can assign this tag via the pre-resolved tagOwnerMap. |
570 | 565 | // The tagOwnerMap contains IP sets built from resolving TagOwners entries |
571 | 566 | // (usernames/groups) to their nodes' IPs, so checking if the node's IP |
@@ -930,28 +925,116 @@ func (pm *PolicyManager) invalidateGlobalPolicyCache(newNodes views.Slice[types. |
930 | 925 | } |
931 | 926 | } |
932 | 927 |
|
| 928 | +// flattenTags flattens the TagOwners by resolving nested tags and detecting cycles. |
| 929 | +// It will return a Owners list where all the Tag types have been resolved to their underlying Owners. |
| 930 | +func flattenTags(tagOwners TagOwners, tag Tag, visiting map[Tag]bool, chain []Tag) (Owners, error) { |
| 931 | + if visiting[tag] { |
| 932 | + cycleStart := 0 |
| 933 | + |
| 934 | + for i, t := range chain { |
| 935 | + if t == tag { |
| 936 | + cycleStart = i |
| 937 | + break |
| 938 | + } |
| 939 | + } |
| 940 | + |
| 941 | + cycleTags := make([]string, len(chain[cycleStart:])) |
| 942 | + for i, t := range chain[cycleStart:] { |
| 943 | + cycleTags[i] = string(t) |
| 944 | + } |
| 945 | + |
| 946 | + slices.Sort(cycleTags) |
| 947 | + |
| 948 | + return nil, fmt.Errorf("%w: %s", ErrCircularReference, strings.Join(cycleTags, " -> ")) |
| 949 | + } |
| 950 | + |
| 951 | + visiting[tag] = true |
| 952 | + |
| 953 | + chain = append(chain, tag) |
| 954 | + defer delete(visiting, tag) |
| 955 | + |
| 956 | + var result Owners |
| 957 | + |
| 958 | + for _, owner := range tagOwners[tag] { |
| 959 | + switch o := owner.(type) { |
| 960 | + case *Tag: |
| 961 | + if _, ok := tagOwners[*o]; !ok { |
| 962 | + return nil, fmt.Errorf("tag %q %w %q", tag, ErrUndefinedTagReference, *o) |
| 963 | + } |
| 964 | + |
| 965 | + nested, err := flattenTags(tagOwners, *o, visiting, chain) |
| 966 | + if err != nil { |
| 967 | + return nil, err |
| 968 | + } |
| 969 | + |
| 970 | + result = append(result, nested...) |
| 971 | + default: |
| 972 | + result = append(result, owner) |
| 973 | + } |
| 974 | + } |
| 975 | + |
| 976 | + return result, nil |
| 977 | +} |
| 978 | + |
| 979 | +// flattenTagOwners flattens all TagOwners by resolving nested tags and detecting cycles. |
| 980 | +// It will return a new TagOwners map where all the Tag types have been resolved to their underlying Owners. |
| 981 | +func flattenTagOwners(tagOwners TagOwners) (TagOwners, error) { |
| 982 | + ret := make(TagOwners) |
| 983 | + |
| 984 | + for tag := range tagOwners { |
| 985 | + flattened, err := flattenTags(tagOwners, tag, make(map[Tag]bool), nil) |
| 986 | + if err != nil { |
| 987 | + return nil, err |
| 988 | + } |
| 989 | + |
| 990 | + slices.SortFunc(flattened, func(a, b Owner) int { |
| 991 | + return cmp.Compare(a.String(), b.String()) |
| 992 | + }) |
| 993 | + ret[tag] = slices.CompactFunc(flattened, func(a, b Owner) bool { |
| 994 | + return a.String() == b.String() |
| 995 | + }) |
| 996 | + } |
| 997 | + |
| 998 | + return ret, nil |
| 999 | +} |
| 1000 | + |
933 | 1001 | // resolveTagOwners resolves the TagOwners to a map of Tag to netipx.IPSet. |
934 | 1002 | // The resulting map can be used to quickly look up the IPSet for a given Tag. |
935 | 1003 | // It is intended for internal use in a PolicyManager. |
936 | 1004 | func resolveTagOwners(p *Policy, users types.Users, nodes views.Slice[types.NodeView]) (map[Tag]*netipx.IPSet, error) { |
| 1005 | + if p == nil { |
| 1006 | + return make(map[Tag]*netipx.IPSet), nil |
| 1007 | + } |
| 1008 | + |
| 1009 | + if len(p.TagOwners) == 0 { |
| 1010 | + return make(map[Tag]*netipx.IPSet), nil |
| 1011 | + } |
| 1012 | + |
937 | 1013 | ret := make(map[Tag]*netipx.IPSet) |
938 | 1014 |
|
939 | | - if p == nil { |
940 | | - return ret, nil |
| 1015 | + tagOwners, err := flattenTagOwners(p.TagOwners) |
| 1016 | + if err != nil { |
| 1017 | + return nil, err |
941 | 1018 | } |
942 | 1019 |
|
943 | | - for tag, owners := range p.TagOwners { |
| 1020 | + for tag, owners := range tagOwners { |
944 | 1021 | var ips netipx.IPSetBuilder |
945 | 1022 |
|
946 | 1023 | for _, owner := range owners { |
947 | | - o, ok := owner.(Alias) |
948 | | - if !ok { |
949 | | - // Should never happen |
| 1024 | + switch o := owner.(type) { |
| 1025 | + case *Tag: |
| 1026 | + // After flattening, Tag types should not appear in the owners list. |
| 1027 | + // If they do, skip them as they represent already-resolved references. |
| 1028 | + |
| 1029 | + case Alias: |
| 1030 | + // If it does not resolve, that means the tag is not associated with any IP addresses. |
| 1031 | + resolved, _ := o.Resolve(p, users, nodes) |
| 1032 | + ips.AddSet(resolved) |
| 1033 | + |
| 1034 | + default: |
| 1035 | + // Should never happen - after flattening, all owners should be Alias types |
950 | 1036 | return nil, fmt.Errorf("%w: %v", ErrInvalidTagOwner, owner) |
951 | 1037 | } |
952 | | - // If it does not resolve, that means the tag is not associated with any IP addresses. |
953 | | - resolved, _ := o.Resolve(p, users, nodes) |
954 | | - ips.AddSet(resolved) |
955 | 1038 | } |
956 | 1039 |
|
957 | 1040 | ipSet, err := ips.IPSet() |
|
0 commit comments