go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/delegation/config_test.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package delegation 16 17 import ( 18 "context" 19 "testing" 20 21 "google.golang.org/protobuf/encoding/prototext" 22 23 "go.chromium.org/luci/auth/identity" 24 "go.chromium.org/luci/server/auth" 25 "go.chromium.org/luci/server/auth/authtest" 26 admin "go.chromium.org/luci/tokenserver/api/admin/v1" 27 "go.chromium.org/luci/tokenserver/appengine/impl/utils/identityset" 28 "go.chromium.org/luci/tokenserver/appengine/impl/utils/policy" 29 30 . "github.com/smartystreets/goconvey/convey" 31 . "go.chromium.org/luci/common/testing/assertions" 32 ) 33 34 func TestIsAuthorizedRequestor(t *testing.T) { 35 t.Parallel() 36 37 ctx := auth.WithState(context.Background(), &authtest.FakeState{ 38 Identity: "user:some-user@example.com", 39 }) 40 41 Convey("IsAuthorizedRequestor works", t, func() { 42 cfg, err := loadConfig(ctx, ` 43 rules { 44 name: "rule 1" 45 requestor: "user:some-user@example.com" 46 47 target_service: "service:some-service" 48 allowed_to_impersonate: "group:some-group" 49 allowed_audience: "REQUESTOR" 50 max_validity_duration: 86400 51 } 52 53 rules { 54 name: "rule 2" 55 requestor: "user:some-another-user@example.com" 56 requestor: "group:some-group" 57 58 target_service: "service:some-service" 59 allowed_to_impersonate: "group:some-group" 60 allowed_audience: "REQUESTOR" 61 max_validity_duration: 86400 62 } 63 `) 64 So(err, ShouldBeNil) 65 So(cfg, ShouldNotBeNil) 66 67 res, err := cfg.IsAuthorizedRequestor(ctx, identity.Identity("user:some-user@example.com")) 68 So(err, ShouldBeNil) 69 So(res, ShouldBeTrue) 70 71 ctx = auth.WithState(context.Background(), &authtest.FakeState{ 72 Identity: "user:some-another-user@example.com", 73 }) 74 res, err = cfg.IsAuthorizedRequestor(ctx, identity.Identity("user:some-another-user@example.com")) 75 So(err, ShouldBeNil) 76 So(res, ShouldBeTrue) 77 78 ctx = auth.WithState(context.Background(), &authtest.FakeState{ 79 Identity: "user:unknown-user@example.com", 80 }) 81 res, err = cfg.IsAuthorizedRequestor(ctx, identity.Identity("user:unknown-user@example.com")) 82 So(err, ShouldBeNil) 83 So(res, ShouldBeFalse) 84 85 ctx = auth.WithState(context.Background(), &authtest.FakeState{ 86 Identity: "user:via-group@example.com", 87 IdentityGroups: []string{"some-group"}, 88 }) 89 res, err = cfg.IsAuthorizedRequestor(ctx, identity.Identity("user:via-group@example.com")) 90 So(err, ShouldBeNil) 91 So(res, ShouldBeTrue) 92 }) 93 } 94 95 func TestFindMatchingRule(t *testing.T) { 96 t.Parallel() 97 98 ctx := auth.WithState(context.Background(), &authtest.FakeState{ 99 Identity: "user:requestor@example.com", 100 FakeDB: authtest.NewFakeDB( 101 authtest.MockMembership("user:requestor-group-member@example.com", "requestor-group"), 102 authtest.MockMembership("user:delegators-group-member@example.com", "delegators-group"), 103 authtest.MockMembership("user:audience-group-member@example.com", "audience-group"), 104 authtest.MockMembership("user:luci-service@example.com", "auth-luci-services"), 105 ), 106 }) 107 108 Convey("with example config", t, func() { 109 cfg, err := loadConfig(ctx, ` 110 rules { 111 name: "rule 1" 112 requestor: "user:requestor@example.com" 113 target_service: "service:some-service" 114 allowed_to_impersonate: "user:allowed-to-impersonate@example.com" 115 allowed_audience: "user:allowed-audience@example.com" 116 max_validity_duration: 86400 117 } 118 119 rules { 120 name: "rule 2" 121 requestor: "group:requestor-group" 122 target_service: "service:some-service" 123 allowed_to_impersonate: "group:delegators-group" 124 allowed_audience: "group:audience-group" 125 max_validity_duration: 86400 126 } 127 128 rules { 129 name: "rule 3" 130 requestor: "group:requestor-group" 131 target_service: "service:some-service" 132 allowed_to_impersonate: "REQUESTOR" 133 allowed_audience: "REQUESTOR" 134 max_validity_duration: 86400 135 } 136 137 rules { 138 name: "rule 4" 139 requestor: "user:some-requestor@example.com" 140 requestor: "user:conflicts-with-rule-5@example.com" 141 target_service: "*" 142 allowed_to_impersonate: "REQUESTOR" 143 allowed_audience: "*" 144 max_validity_duration: 86400 145 } 146 147 rules { 148 name: "rule 5" 149 requestor: "user:conflicts-with-rule-5@example.com" 150 target_service: "*" 151 allowed_to_impersonate: "REQUESTOR" 152 allowed_audience: "*" 153 max_validity_duration: 86400 154 } 155 `) 156 So(err, ShouldBeNil) 157 So(cfg, ShouldNotBeNil) 158 159 Convey("Direct matches and misses", func() { 160 // Match. 161 res, err := cfg.FindMatchingRule(ctx, &RulesQuery{ 162 Requestor: "user:requestor@example.com", 163 Delegator: "user:allowed-to-impersonate@example.com", 164 Audience: makeSet("user:allowed-audience@example.com"), 165 Services: makeSet("service:some-service"), 166 }) 167 So(err, ShouldBeNil) 168 So(res, ShouldNotBeNil) 169 So(res.Name, ShouldEqual, "rule 1") 170 171 // Unknown requestor. 172 res, err = cfg.FindMatchingRule(ctx, &RulesQuery{ 173 Requestor: "user:unknown-requestor@example.com", 174 Delegator: "user:allowed-to-impersonate@example.com", 175 Audience: makeSet("user:allowed-audience@example.com"), 176 Services: makeSet("service:some-service"), 177 }) 178 So(err, ShouldErrLike, "no matching delegation rules in the config") 179 So(res, ShouldBeNil) 180 181 // Unknown delegator. 182 res, err = cfg.FindMatchingRule(ctx, &RulesQuery{ 183 Requestor: "user:requestor@example.com", 184 Delegator: "user:unknown-allowed-to-impersonate@example.com", 185 Audience: makeSet("user:allowed-audience@example.com"), 186 Services: makeSet("service:some-service"), 187 }) 188 So(err, ShouldErrLike, "no matching delegation rules in the config") 189 So(res, ShouldBeNil) 190 191 // Unknown audience. 192 res, err = cfg.FindMatchingRule(ctx, &RulesQuery{ 193 Requestor: "user:requestor@example.com", 194 Delegator: "user:allowed-to-impersonate@example.com", 195 Audience: makeSet("user:unknown-allowed-audience@example.com"), 196 Services: makeSet("service:some-service"), 197 }) 198 So(err, ShouldErrLike, "no matching delegation rules in the config") 199 So(res, ShouldBeNil) 200 201 // Unknown target service. 202 res, err = cfg.FindMatchingRule(ctx, &RulesQuery{ 203 Requestor: "user:requestor@example.com", 204 Delegator: "user:allowed-to-impersonate@example.com", 205 Audience: makeSet("user:allowed-audience@example.com"), 206 Services: makeSet("service:unknown-some-service"), 207 }) 208 So(err, ShouldErrLike, "no matching delegation rules in the config") 209 So(res, ShouldBeNil) 210 }) 211 212 Convey("Matches via groups", func() { 213 res, err := cfg.FindMatchingRule(ctx, &RulesQuery{ 214 Requestor: "user:requestor-group-member@example.com", 215 Delegator: "user:delegators-group-member@example.com", 216 Audience: makeSet("group:audience-group"), 217 Services: makeSet("service:some-service"), 218 }) 219 So(err, ShouldBeNil) 220 So(res, ShouldNotBeNil) 221 So(res.Name, ShouldEqual, "rule 2") 222 223 // Doesn't do group lookup when checking audience! 224 res, err = cfg.FindMatchingRule(ctx, &RulesQuery{ 225 Requestor: "user:requestor-group-member@example.com", 226 Delegator: "user:delegators-group-member@example.com", 227 Audience: makeSet("user:audience-group-member@example.com"), 228 Services: makeSet("service:some-service"), 229 }) 230 So(err, ShouldErrLike, "no matching delegation rules in the config") 231 So(res, ShouldBeNil) 232 }) 233 234 Convey("REQUESTOR rules work", func() { 235 res, err := cfg.FindMatchingRule(ctx, &RulesQuery{ 236 Requestor: "user:requestor-group-member@example.com", 237 Delegator: "user:requestor-group-member@example.com", 238 Audience: makeSet("user:requestor-group-member@example.com"), 239 Services: makeSet("service:some-service"), 240 }) 241 So(err, ShouldBeNil) 242 So(res, ShouldNotBeNil) 243 So(res.Name, ShouldEqual, "rule 3") 244 }) 245 246 Convey("'*' rules work", func() { 247 res, err := cfg.FindMatchingRule(ctx, &RulesQuery{ 248 Requestor: "user:some-requestor@example.com", 249 Delegator: "user:some-requestor@example.com", 250 Audience: makeSet("group:abc", "user:def@example.com"), 251 Services: makeSet("service:unknown"), 252 }) 253 So(err, ShouldBeNil) 254 So(res, ShouldNotBeNil) 255 So(res.Name, ShouldEqual, "rule 4") 256 }) 257 258 Convey("a conflict is handled", func() { 259 res, err := cfg.FindMatchingRule(ctx, &RulesQuery{ 260 Requestor: "user:conflicts-with-rule-5@example.com", 261 Delegator: "user:conflicts-with-rule-5@example.com", 262 Audience: makeSet("group:abc", "user:def@example.com"), 263 Services: makeSet("service:unknown"), 264 }) 265 So(err, ShouldErrLike, `ambiguous request, multiple delegation rules match ("rule 4", "rule 5")`) 266 So(res, ShouldBeNil) 267 }) 268 269 Convey("implicit project:* rule works", func() { 270 res, err := cfg.FindMatchingRule(ctx, &RulesQuery{ 271 Requestor: "user:luci-service@example.com", 272 Delegator: "project:some-project", 273 Audience: makeSet("user:luci-service@example.com"), 274 Services: makeSet("service:some-target-service"), 275 }) 276 So(err, ShouldBeNil) 277 So(res, ShouldNotBeNil) 278 So(res.Name, ShouldEqual, "allow-project-identities") 279 }) 280 }) 281 } 282 283 func loadConfig(ctx context.Context, text string) (*Rules, error) { 284 cfg := &admin.DelegationPermissions{} 285 err := prototext.Unmarshal([]byte(text), cfg) 286 if err != nil { 287 return nil, err 288 } 289 rules, err := prepareRules(ctx, policy.ConfigBundle{delegationCfg: cfg}, "fake-revision") 290 if err != nil { 291 return nil, err 292 } 293 return rules.(*Rules), nil 294 } 295 296 func makeSet(ident ...string) *identityset.Set { 297 s, err := identityset.FromStrings(ident, nil) 298 if err != nil { 299 panic(err) 300 } 301 return s 302 }