go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/tokenserver/appengine/impl/delegation/rpc_mint_delegation_token_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 "fmt" 20 "net" 21 "net/url" 22 "testing" 23 "time" 24 25 "go.opentelemetry.io/otel/trace" 26 27 "go.chromium.org/luci/appengine/gaetesting" 28 "go.chromium.org/luci/auth/identity" 29 "go.chromium.org/luci/common/clock/testclock" 30 "go.chromium.org/luci/server/auth" 31 "go.chromium.org/luci/server/auth/authdb" 32 "go.chromium.org/luci/server/auth/authtest" 33 "go.chromium.org/luci/server/auth/signing" 34 "go.chromium.org/luci/server/auth/signing/signingtest" 35 "go.chromium.org/luci/tokenserver/api/admin/v1" 36 "go.chromium.org/luci/tokenserver/api/minter/v1" 37 38 . "github.com/smartystreets/goconvey/convey" 39 . "go.chromium.org/luci/common/testing/assertions" 40 ) 41 42 var testingRequestID = trace.TraceID{1, 2, 3, 4, 5} 43 44 func mockedFetchLUCIServiceIdentity(c context.Context, u string) (identity.Identity, error) { 45 l, err := url.Parse(u) 46 if err != nil { 47 return "", err 48 } 49 if l.Scheme != "https" { 50 return "", fmt.Errorf("wrong scheme") 51 } 52 if l.Host == "crash" { 53 return "", fmt.Errorf("boom") 54 } 55 return identity.MakeIdentity("service:" + l.Host) 56 } 57 58 func init() { 59 fetchLUCIServiceIdentity = mockedFetchLUCIServiceIdentity 60 } 61 62 func testingContext() context.Context { 63 ctx := gaetesting.TestingContext() 64 ctx = trace.ContextWithSpanContext(ctx, trace.NewSpanContext(trace.SpanContextConfig{ 65 TraceID: testingRequestID, 66 })) 67 ctx, _ = testclock.UseTime(ctx, time.Date(2015, time.February, 3, 4, 5, 6, 0, time.UTC)) 68 return auth.WithState(ctx, &authtest.FakeState{ 69 Identity: "user:requestor@example.com", 70 PeerIPOverride: net.ParseIP("127.10.10.10"), 71 FakeDB: &authdb.SnapshotDB{Rev: 1234}, 72 }) 73 } 74 75 func testingSigner() *signingtest.Signer { 76 return signingtest.NewSigner(&signing.ServiceInfo{ 77 ServiceAccountName: "signer@testing.host", 78 AppID: "unit-tests", 79 AppVersion: "mocked-ver", 80 }) 81 } 82 83 func TestBuildRulesQuery(t *testing.T) { 84 t.Parallel() 85 86 ctx := testingContext() 87 88 Convey("Happy path", t, func() { 89 q, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 90 DelegatedIdentity: "user:delegated@example.com", 91 Audience: []string{"group:A", "group:B", "user:c@example.com"}, 92 Services: []string{"service:A", "*"}, 93 }, "user:requestor@example.com") 94 So(err, ShouldBeNil) 95 So(q, ShouldNotBeNil) 96 97 So(q.Requestor, ShouldEqual, identity.Identity("user:requestor@example.com")) 98 So(q.Delegator, ShouldEqual, identity.Identity("user:delegated@example.com")) 99 So(q.Audience.ToStrings(), ShouldResemble, []string{"group:A", "group:B", "user:c@example.com"}) 100 So(q.Services.ToStrings(), ShouldResemble, []string{"*"}) 101 }) 102 103 Convey("REQUESTOR usage works", t, func() { 104 q, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 105 DelegatedIdentity: "REQUESTOR", 106 Audience: []string{"group:A", "group:B", "REQUESTOR"}, 107 Services: []string{"*"}, 108 }, "user:requestor@example.com") 109 So(err, ShouldBeNil) 110 So(q, ShouldNotBeNil) 111 112 So(q.Requestor, ShouldEqual, identity.Identity("user:requestor@example.com")) 113 So(q.Delegator, ShouldEqual, identity.Identity("user:requestor@example.com")) 114 So(q.Audience.ToStrings(), ShouldResemble, []string{"group:A", "group:B", "user:requestor@example.com"}) 115 }) 116 117 Convey("bad 'delegated_identity'", t, func() { 118 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 119 Audience: []string{"REQUESTOR"}, 120 Services: []string{"*"}, 121 }, "user:requestor@example.com") 122 So(err, ShouldErrLike, `'delegated_identity' is required`) 123 124 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 125 DelegatedIdentity: "junk", 126 Audience: []string{"REQUESTOR"}, 127 Services: []string{"*"}, 128 }, "user:requestor@example.com") 129 So(err, ShouldErrLike, `bad 'delegated_identity' - auth: bad identity string "junk"`) 130 }) 131 132 Convey("bad 'audience'", t, func() { 133 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 134 DelegatedIdentity: "REQUESTOR", 135 Services: []string{"*"}, 136 }, "user:requestor@example.com") 137 So(err, ShouldErrLike, `'audience' is required`) 138 139 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 140 DelegatedIdentity: "REQUESTOR", 141 Audience: []string{"REQUESTOR", "junk"}, 142 Services: []string{"*"}, 143 }, "user:requestor@example.com") 144 So(err, ShouldErrLike, `bad 'audience' - auth: bad identity string "junk"`) 145 }) 146 147 Convey("bad 'services'", t, func() { 148 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 149 DelegatedIdentity: "REQUESTOR", 150 Audience: []string{"REQUESTOR"}, 151 }, "user:requestor@example.com") 152 So(err, ShouldErrLike, `'services' is required`) 153 154 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 155 DelegatedIdentity: "REQUESTOR", 156 Audience: []string{"REQUESTOR"}, 157 Services: []string{"junk"}, 158 }, "user:requestor@example.com") 159 So(err, ShouldErrLike, `bad 'services' - auth: bad identity string "junk"`) 160 161 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 162 DelegatedIdentity: "REQUESTOR", 163 Audience: []string{"REQUESTOR"}, 164 Services: []string{"user:abc@example.com"}, 165 }, "user:requestor@example.com") 166 So(err, ShouldErrLike, `bad 'services' - "user:abc@example.com" is not a service ID`) 167 168 _, err = buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 169 DelegatedIdentity: "REQUESTOR", 170 Audience: []string{"REQUESTOR"}, 171 Services: []string{"group:abc"}, 172 }, "user:requestor@example.com") 173 So(err, ShouldErrLike, `bad 'services' - can't specify groups`) 174 }) 175 176 Convey("resolves https:// service refs", t, func() { 177 q, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 178 DelegatedIdentity: "user:delegated@example.com", 179 Audience: []string{"*"}, 180 Services: []string{ 181 "service:A", 182 "service:B", 183 "https://C", 184 "https://B", 185 "https://A", 186 }, 187 }, "user:requestor@example.com") 188 So(err, ShouldBeNil) 189 So(q, ShouldNotBeNil) 190 191 So(q.Services.ToStrings(), ShouldResemble, []string{ 192 "service:A", 193 "service:B", 194 "service:C", 195 }) 196 }) 197 198 Convey("handles errors when resolving https:// service refs", t, func() { 199 _, err := buildRulesQuery(ctx, &minter.MintDelegationTokenRequest{ 200 DelegatedIdentity: "user:delegated@example.com", 201 Audience: []string{"*"}, 202 Services: []string{ 203 "https://A", 204 "https://B", 205 "https://crash", 206 }, 207 }, "user:requestor@example.com") 208 So(err, ShouldErrLike, `could not resolve "https://crash" to service ID - boom`) 209 }) 210 } 211 212 func TestMintDelegationToken(t *testing.T) { 213 t.Parallel() 214 215 ctx := testingContext() 216 217 Convey("with mocked config and state", t, func() { 218 cfg, err := loadConfig(ctx, ` 219 rules { 220 name: "requstor for itself" 221 requestor: "user:requestor@example.com" 222 target_service: "*" 223 allowed_to_impersonate: "REQUESTOR" 224 allowed_audience: "REQUESTOR" 225 max_validity_duration: 3600 226 } 227 `) 228 So(err, ShouldBeNil) 229 230 mintMock := func(c context.Context, p *mintParams) (*minter.MintDelegationTokenResponse, error) { 231 return &minter.MintDelegationTokenResponse{Token: "valid_token", ServiceVersion: p.serviceVer}, nil 232 } 233 234 var loggedInfo *MintedTokenInfo 235 rpc := MintDelegationTokenRPC{ 236 Signer: testingSigner(), 237 Rules: func(context.Context) (*Rules, error) { return cfg, nil }, 238 LogToken: func(c context.Context, i *MintedTokenInfo) error { 239 loggedInfo = i 240 return nil 241 }, 242 mintMock: mintMock, 243 } 244 245 Convey("Happy path", func() { 246 req := &minter.MintDelegationTokenRequest{ 247 DelegatedIdentity: "REQUESTOR", 248 Audience: []string{"REQUESTOR"}, 249 Services: []string{"*"}, 250 Tags: []string{"k:v"}, 251 } 252 resp, err := rpc.MintDelegationToken(ctx, req) 253 So(err, ShouldBeNil) 254 So(resp.Token, ShouldEqual, "valid_token") 255 So(resp.ServiceVersion, ShouldEqual, "unit-tests/mocked-ver") 256 257 // LogToken called. 258 So(loggedInfo.Request, ShouldResembleProto, req) 259 So(loggedInfo.Response, ShouldResembleProto, resp) 260 So(loggedInfo.ConfigRev, ShouldEqual, cfg.ConfigRevision()) 261 So(loggedInfo.Rule, ShouldResembleProto, &admin.DelegationRule{ 262 Name: "requstor for itself", 263 Requestor: []string{"user:requestor@example.com"}, 264 AllowedToImpersonate: []string{"REQUESTOR"}, 265 AllowedAudience: []string{"REQUESTOR"}, 266 TargetService: []string{"*"}, 267 MaxValidityDuration: 3600, 268 }) 269 So(loggedInfo.PeerIP, ShouldResemble, net.ParseIP("127.10.10.10")) 270 So(loggedInfo.RequestID, ShouldEqual, testingRequestID.String()) 271 So(loggedInfo.AuthDBRev, ShouldEqual, 1234) 272 }) 273 274 Convey("Using delegated identity for auth is forbidden", func() { 275 ctx := auth.WithState(ctx, &authtest.FakeState{ 276 Identity: "user:requestor@example.com", 277 PeerIdentityOverride: "user:impersonator@example.com", 278 }) 279 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 280 DelegatedIdentity: "REQUESTOR", 281 Audience: []string{"REQUESTOR"}, 282 Services: []string{"*"}, 283 }) 284 So(err, ShouldBeRPCPermissionDenied, "delegation is forbidden for this API call") 285 }) 286 287 Convey("Anonymous calls are forbidden", func() { 288 ctx := auth.WithState(ctx, &authtest.FakeState{ 289 Identity: "anonymous:anonymous", 290 }) 291 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 292 DelegatedIdentity: "REQUESTOR", 293 Audience: []string{"REQUESTOR"}, 294 Services: []string{"*"}, 295 }) 296 So(err, ShouldBeRPCUnauthenticated, "authentication required") 297 }) 298 299 Convey("Unauthorized requestor", func() { 300 ctx := auth.WithState(ctx, &authtest.FakeState{ 301 Identity: "user:unknown@example.com", 302 }) 303 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 304 DelegatedIdentity: "REQUESTOR", 305 Audience: []string{"REQUESTOR"}, 306 Services: []string{"*"}, 307 }) 308 So(err, ShouldBeRPCPermissionDenied, "not authorized") 309 }) 310 311 Convey("Negative validity duration", func() { 312 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 313 DelegatedIdentity: "REQUESTOR", 314 Audience: []string{"REQUESTOR"}, 315 Services: []string{"*"}, 316 ValidityDuration: -1, 317 }) 318 So(err, ShouldBeRPCInvalidArgument, "bad request - invalid 'validity_duration' (-1)") 319 }) 320 321 Convey("Bad tags", func() { 322 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 323 DelegatedIdentity: "REQUESTOR", 324 Audience: []string{"REQUESTOR"}, 325 Services: []string{"*"}, 326 Tags: []string{"not key value"}, 327 }) 328 So(err, ShouldBeRPCInvalidArgument, "bad request - invalid 'tags': tag #1: not in <key>:<value> form") 329 }) 330 331 Convey("Malformed request", func() { 332 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 333 DelegatedIdentity: "REQUESTOR", 334 Audience: []string{"junk"}, 335 Services: []string{"*"}, 336 }) 337 So(err, ShouldBeRPCInvalidArgument, `bad request - bad 'audience' - auth: bad identity string "junk"`) 338 }) 339 340 Convey("No matching rules", func() { 341 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 342 DelegatedIdentity: "REQUESTOR", 343 Audience: []string{"user:someone-else@example.com"}, 344 Services: []string{"*"}, 345 }) 346 So(err, ShouldBeRPCPermissionDenied, "forbidden - no matching delegation rules in the config") 347 }) 348 349 Convey("Forbidden validity duration", func() { 350 _, err := rpc.MintDelegationToken(ctx, &minter.MintDelegationTokenRequest{ 351 DelegatedIdentity: "REQUESTOR", 352 Audience: []string{"REQUESTOR"}, 353 Services: []string{"*"}, 354 ValidityDuration: 3601, 355 }) 356 So(err, ShouldBeRPCPermissionDenied, "forbidden - the requested validity duration (3601 sec) exceeds the maximum allowed one (3600 sec)") 357 }) 358 359 }) 360 }