1 import Base 2 import near-architecture@0.9.1.0/arc.bend as Arc 3 4 # Public metadata and effective ARM permission patterns only. No credential values. 5 type Resource is Data: 6 Resource{name: String, kind: String, group: String, location: String} 7 8 type DeclaredResource is Data: 9 DeclaredResource{name: String, kind: String, group: String} 10 11 type Permission is Data: 12 Permission{scope: String, actions: List<&2, String>, not_actions: List<&2, String>} 13 14 type Project is Data: 15 Project{name: String} 16 17 type Repository is Data: 18 Repository{name: String} 19 20 type Pipeline is Data: 21 Pipeline{name: String, repository: String, yaml: String} 22 23 type ServiceConnection is Data: 24 ServiceConnection{name: String, kind: String, scheme: String} 25 26 type Identity is Data: 27 Identity{profile: String, name: String} 28 29 def same_resource(a: Resource, b: Resource) -> Bool: 30 match a b: 31 case Resource{an, ak, ag, al} Resource{bn, bk, bg, bl}: 32 String.eq(an, bn) && String.eq(ak, bk) && String.eq(ag, bg) && String.eq(al, bl) 33 34 def contains_resource(+x: Resource, xs: List<&2, Resource>) -> Bool: 35 match xs: 36 case []: 37 False{} 38 case y <> rest: 39 same_resource(x, y) || contains_resource(x, rest) 40 41 def all_resources_in(xs: List<&2, Resource>, +ys: List<&2, Resource>) -> Bool: 42 match xs: 43 case []: 44 True{} 45 case x <> rest: 46 contains_resource(x, ys) && all_resources_in(rest, ys) 47 48 def resources_equal(+a: List<&2, Resource>, +b: List<&2, Resource>) -> Bool: 49 all_resources_in(a, b) && all_resources_in(b, a) 50 51 def declared_matches(d: DeclaredResource, r: Resource) -> Bool: 52 match d r: 53 case DeclaredResource{dn, dk, dg} Resource{rn, rk, rg, _}: 54 String.eq(dn, rn) && String.eq(dk, rk) && String.eq(dg, rg) 55 56 def resource_declared(+r: Resource, ds: List<&2, DeclaredResource>) -> Bool: 57 match ds: 58 case []: False{} 59 case d <> rest: declared_matches(d, r) || resource_declared(r, rest) 60 61 def declaration_exists(+d: DeclaredResource, rs: List<&2, Resource>) -> Bool: 62 match rs: 63 case []: False{} 64 case r <> rest: declared_matches(d, r) || declaration_exists(d, rest) 65 66 def all_observed_declared(rs: List<&2, Resource>, +ds: List<&2, DeclaredResource>) -> Bool: 67 match rs: 68 case []: True{} 69 case r <> rest: resource_declared(r, ds) && all_observed_declared(rest, ds) 70 71 def all_declared_observed(ds: List<&2, DeclaredResource>, +rs: List<&2, Resource>) -> Bool: 72 match ds: 73 case []: True{} 74 case d <> rest: declaration_exists(d, rs) && all_declared_observed(rest, rs) 75 76 def inventory_equal(+ds: List<&2, DeclaredResource>, +rs: List<&2, Resource>) -> Bool: 77 all_observed_declared(rs, ds) && all_declared_observed(ds, rs) 78 79 def declaration_names(ds: List<&2, DeclaredResource>) -> List<&2, String>: 80 match ds: 81 case []: [] 82 case d <> rest: 83 match d: 84 case DeclaredResource{name, _, _}: name <> declaration_names(rest) 85 86 def contains_name(+name: String, xs: List<&2, String>) -> Bool: 87 match xs: 88 case []: False{} 89 case x <> rest: String.eq(name, x) || contains_name(name, rest) 90 91 def all_names_in(xs: List<&2, String>, +ys: List<&2, String>) -> Bool: 92 match xs: 93 case []: True{} 94 case x <> rest: contains_name(x, ys) && all_names_in(rest, ys) 95 96 def scopes_equal(+declared: List<&2, String>, +observed: List<&2, String>) -> Bool: 97 all_names_in(declared, observed) && all_names_in(observed, declared) 98 99 # ARM patterns use '*' for zero or more characters; matching is case insensitive. 100 def glob_chars(+ps: List<&2, Char>, +vs: List<&2, Char>) -> Bool: 101 match ps: 102 case []: 103 match vs: 104 case []: True{} 105 case _ <> _: False{} 106 case p <> rest: 107 match p: 108 case '*': 109 match vs: 110 case []: glob_chars(rest, []) 111 case v <> tail: glob_chars(rest, v <> tail) || glob_chars('*' <> rest, tail) 112 case _: 113 match vs: 114 case []: False{} 115 case v <> tail: Char.is_eq(p, v) && glob_chars(rest, tail) 116 117 def glob(pattern: String, value: String) -> Bool: 118 glob_chars(String.to_list(String.to_lower(pattern)), String.to_list(String.to_lower(value))) 119 120 # A wildcard pattern is covered only by the same pattern or '*'. This conservative 121 # rule avoids claiming that overlapping wildcards grant exactly equal permissions. 122 def covers(+want: String, +actual: String) -> Bool: 123 String.eq(String.to_lower(want), String.to_lower(actual)) || 124 String.eq(want, "*") || 125 (Bool.not(String.contains(actual, "*")) && glob(want, actual)) 126 127 def covered(+action: String, patterns: List<&2, String>) -> Bool: 128 match patterns: 129 case []: 130 False{} 131 case p <> rest: 132 covers(p, action) || covered(action, rest) 133 134 def every_covered(xs: List<&2, String>, +by: List<&2, String>) -> Bool: 135 match xs: 136 case []: 137 True{} 138 case x <> rest: 139 covered(x, by) && every_covered(rest, by) 140 141 def actions_if(hit: Bool, acts: List<&2, String>, rest: List<&2, String>) -> List<&2, String>: 142 match hit: 143 case True{}: List.append(&2, String, acts, rest) 144 case False{}: rest 145 146 def scope_actions(+scope: String, xs: List<&2, Permission>) -> List<&2, String>: 147 match xs: 148 case []: 149 [] 150 case p <> rest: 151 match p: 152 case Permission{s, acts, _}: 153 actions_if(String.eq(scope, s), acts, scope_actions(scope, rest)) 154 155 def no_exclusions(+scope: String, xs: List<&2, Permission>) -> Bool: 156 match xs: 157 case []: 158 True{} 159 case p <> rest: 160 match p: 161 case Permission{s, _, exclusions}: 162 (Bool.not(String.eq(scope, s)) || List.is_empty(&2, String, exclusions)) && no_exclusions(scope, rest) 163 164 def permissions_equal(+scope: String, +want: List<&2, String>, +actual: List<&2, Permission>) -> Bool: 165 +got : List<&2, String> = scope_actions(scope, actual) 166 no_exclusions(scope, actual) && every_covered(got, want) && every_covered(want, got) 167 168 def in_domain(name: String, domain: String) -> Bool: 169 String.ends_with(String.to_lower(name), "@" ++ String.to_lower(domain)) 170 171 def identities_in_domain(xs: List<&2, Identity>, +domain: String) -> Bool: 172 match xs: 173 case []: 174 True{} 175 case x <> rest: 176 match x: 177 case Identity{_, name}: 178 in_domain(name, domain) && identities_in_domain(rest, domain) 179 180 def identity_names(xs: List<&2, Identity>) -> List<&2, String>: 181 match xs: 182 case []: [] 183 case x <> rest: 184 match x: 185 case Identity{_, name}: name <> identity_names(rest) 186 187 # Permission minimality: on every resource group, `person` has exactly what the agreement grants 188 # there (nothing granted means no access at all). 189 def access_minimal(groups: List<&2, String>, +a: Arc.Agreement, +person: String, +actual: List<&2, Permission>) -> Bool: 190 match groups: 191 case []: True{} 192 case +g <> rest: permissions_equal(g, Arc.granted(a, person, g), actual) && access_minimal(rest, a, person, actual)