@@ -218,7 +218,7 @@ func (sv *Variables) Read(args *structs.VariablesReadRequest, reply *structs.Var
218
218
}
219
219
defer metrics .MeasureSince ([]string {"nomad" , "variables" , "read" }, time .Now ())
220
220
221
- _ , err := sv .handleMixedAuthEndpoint (args .QueryOptions ,
221
+ _ , _ , err := sv .handleMixedAuthEndpoint (args .QueryOptions ,
222
222
acl .PolicyRead , args .Path )
223
223
if err != nil {
224
224
return err
@@ -269,8 +269,7 @@ func (sv *Variables) List(
269
269
return sv .listAllVariables (args , reply )
270
270
}
271
271
272
- aclObj , err := sv .handleMixedAuthEndpoint (args .QueryOptions ,
273
- acl .PolicyList , args .Prefix )
272
+ aclObj , claims , err := sv .authenticate (args .QueryOptions )
274
273
if err != nil {
275
274
return err
276
275
}
@@ -299,9 +298,12 @@ func (sv *Variables) List(
299
298
filters := []paginator.Filter {
300
299
paginator.GenericFilter {
301
300
Allow : func (raw interface {}) (bool , error ) {
302
- sv := raw .(* structs.VariableEncrypted )
303
- return strings .HasPrefix (sv .Path , args .Prefix ) &&
304
- (aclObj == nil || aclObj .AllowVariableOperation (sv .Namespace , sv .Path , acl .PolicyList )), nil
301
+ v := raw .(* structs.VariableEncrypted )
302
+ if ! strings .HasPrefix (v .Path , args .Prefix ) {
303
+ return false , nil
304
+ }
305
+ err := sv .authorize (aclObj , claims , v .Namespace , acl .PolicyList , v .Path )
306
+ return err == nil , nil
305
307
},
306
308
},
307
309
}
@@ -345,43 +347,23 @@ func (sv *Variables) List(
345
347
346
348
// listAllVariables is used to list variables held within
347
349
// state where the caller has used the namespace wildcard identifier.
348
- func (s * Variables ) listAllVariables (
350
+ func (sv * Variables ) listAllVariables (
349
351
args * structs.VariablesListRequest ,
350
352
reply * structs.VariablesListResponse ) error {
351
353
352
354
// Perform token resolution. The request already goes through forwarding
353
355
// and metrics setup before being called.
354
- aclObj , err := s . srv . ResolveToken (args .AuthToken )
356
+ aclObj , claims , err := sv . authenticate (args .QueryOptions )
355
357
if err != nil {
356
358
return err
357
359
}
358
360
359
- // allowFunc checks whether the caller has the read-job capability on the
360
- // passed namespace.
361
- allowFunc := func (ns string ) bool {
362
- return aclObj .AllowVariableOperation (ns , "" , acl .PolicyList )
363
- }
364
-
365
361
// Set up and return the blocking query.
366
- return s .srv .blockingRPC (& blockingOptions {
362
+ return sv .srv .blockingRPC (& blockingOptions {
367
363
queryOpts : & args .QueryOptions ,
368
364
queryMeta : & reply .QueryMeta ,
369
365
run : func (ws memdb.WatchSet , stateStore * state.StateStore ) error {
370
366
371
- // Identify which namespaces the caller has access to. If they do
372
- // not have access to any, send them an empty response. Otherwise,
373
- // handle any error in a traditional manner.
374
- _ , err := allowedNSes (aclObj , stateStore , allowFunc )
375
- switch err {
376
- case structs .ErrPermissionDenied :
377
- reply .Data = make ([]* structs.VariableMetadata , 0 )
378
- return nil
379
- case nil :
380
- // Fallthrough.
381
- default :
382
- return err
383
- }
384
-
385
367
// Get all the variables stored within state.
386
368
iter , err := stateStore .Variables (ws )
387
369
if err != nil {
@@ -396,15 +378,17 @@ func (s *Variables) listAllVariables(
396
378
paginator.StructsTokenizerOptions {
397
379
WithNamespace : true ,
398
380
WithID : true ,
399
- },
400
- )
381
+ })
401
382
402
383
filters := []paginator.Filter {
403
384
paginator.GenericFilter {
404
385
Allow : func (raw interface {}) (bool , error ) {
405
- sv := raw .(* structs.VariableEncrypted )
406
- return strings .HasPrefix (sv .Path , args .Prefix ) &&
407
- (aclObj == nil || aclObj .AllowVariableOperation (sv .Namespace , sv .Path , acl .PolicyList )), nil
386
+ v := raw .(* structs.VariableEncrypted )
387
+ if ! strings .HasPrefix (v .Path , args .Prefix ) {
388
+ return false , nil
389
+ }
390
+ err := sv .authorize (aclObj , claims , v .Namespace , acl .PolicyList , v .Path )
391
+ return err == nil , nil
408
392
},
409
393
},
410
394
}
@@ -413,8 +397,8 @@ func (s *Variables) listAllVariables(
413
397
// responsible for appending a variable to the stubs array.
414
398
paginatorImpl , err := paginator .NewPaginator (iter , tokenizer , filters , args .QueryOptions ,
415
399
func (raw interface {}) error {
416
- sv := raw .(* structs.VariableEncrypted )
417
- svStub := sv .VariableMetadata
400
+ v := raw .(* structs.VariableEncrypted )
401
+ svStub := v .VariableMetadata
418
402
svs = append (svs , & svStub )
419
403
return nil
420
404
})
@@ -437,7 +421,7 @@ func (s *Variables) listAllVariables(
437
421
438
422
// Use the index table to populate the query meta as we have no way
439
423
// of tracking the max index on deletes.
440
- return s .srv .setReplyQueryMeta (stateStore , state .TableVariables , & reply .QueryMeta )
424
+ return sv .srv .setReplyQueryMeta (stateStore , state .TableVariables , & reply .QueryMeta )
441
425
},
442
426
})
443
427
}
@@ -475,24 +459,31 @@ func (sv *Variables) decrypt(v *structs.VariableEncrypted) (*structs.VariableDec
475
459
476
460
// handleMixedAuthEndpoint is a helper to handle auth on RPC endpoints that can
477
461
// either be called by external clients or by workload identity
478
- func (sv * Variables ) handleMixedAuthEndpoint (args structs.QueryOptions , cap , pathOrPrefix string ) (* acl.ACL , error ) {
462
+ func (sv * Variables ) handleMixedAuthEndpoint (args structs.QueryOptions , cap , pathOrPrefix string ) (* acl.ACL , * structs.IdentityClaims , error ) {
463
+
464
+ aclObj , claims , err := sv .authenticate (args )
465
+ if err != nil {
466
+ return aclObj , claims , err
467
+ }
468
+ err = sv .authorize (aclObj , claims , args .RequestNamespace (), cap , pathOrPrefix )
469
+ if err != nil {
470
+ return aclObj , claims , err
471
+ }
472
+
473
+ return aclObj , claims , nil
474
+ }
475
+
476
+ func (sv * Variables ) authenticate (args structs.QueryOptions ) (* acl.ACL , * structs.IdentityClaims , error ) {
479
477
480
478
// Perform the initial token resolution.
481
479
aclObj , err := sv .srv .ResolveToken (args .AuthToken )
482
480
if err == nil {
483
- // Perform our ACL validation. If the object is nil, this means ACLs
484
- // are not enabled, otherwise trigger the allowed namespace function.
485
- if aclObj != nil {
486
- if ! aclObj .AllowVariableOperation (args .RequestNamespace (), pathOrPrefix , cap ) {
487
- return nil , structs .ErrPermissionDenied
488
- }
489
- }
490
- return aclObj , nil
481
+ return aclObj , nil , nil
491
482
}
492
483
if helper .IsUUID (args .AuthToken ) {
493
484
// early return for ErrNotFound or other errors if it's formed
494
485
// like an ACLToken.SecretID
495
- return nil , err
486
+ return nil , nil , err
496
487
}
497
488
498
489
// Attempt to verify the token as a JWT with a workload
@@ -502,27 +493,46 @@ func (sv *Variables) handleMixedAuthEndpoint(args structs.QueryOptions, cap, pat
502
493
metrics .IncrCounter ([]string {
503
494
"nomad" , "variables" , "invalid_allocation_identity" }, 1 )
504
495
sv .logger .Trace ("allocation identity was not valid" , "error" , err )
505
- return nil , structs .ErrPermissionDenied
496
+ return nil , nil , structs .ErrPermissionDenied
506
497
}
498
+ return nil , claims , nil
499
+ }
507
500
508
- // The workload identity gets access to paths that match its
509
- // identity, without having to go thru the ACL system
510
- err = sv .authValidatePrefix (claims , args .RequestNamespace (), pathOrPrefix )
511
- if err == nil {
512
- return aclObj , nil
501
+ func (sv * Variables ) authorize (aclObj * acl.ACL , claims * structs.IdentityClaims , ns , cap , pathOrPrefix string ) error {
502
+
503
+ if aclObj == nil && claims == nil {
504
+ return nil // ACLs aren't enabled
513
505
}
514
506
515
- // If the workload identity doesn't match the implicit permissions
516
- // given to paths, check for its attached ACL policies
517
- aclObj , err = sv .srv .ResolveClaims (claims )
518
- if err != nil {
519
- return nil , err // this only returns an error when the state store has gone wrong
507
+ // Perform normal ACL validation. If the ACL object is nil, that means we're
508
+ // working with an identity claim.
509
+ if aclObj != nil {
510
+ if ! aclObj .AllowVariableOperation (ns , pathOrPrefix , cap ) {
511
+ return structs .ErrPermissionDenied
512
+ }
513
+ return nil
520
514
}
521
- if aclObj != nil && aclObj .AllowVariableOperation (
522
- args .RequestNamespace (), pathOrPrefix , cap ) {
523
- return aclObj , nil
515
+
516
+ if claims != nil {
517
+ // The workload identity gets access to paths that match its
518
+ // identity, without having to go thru the ACL system
519
+ err := sv .authValidatePrefix (claims , ns , pathOrPrefix )
520
+ if err == nil {
521
+ return nil
522
+ }
523
+
524
+ // If the workload identity doesn't match the implicit permissions
525
+ // given to paths, check for its attached ACL policies
526
+ aclObj , err = sv .srv .ResolveClaims (claims )
527
+ if err != nil {
528
+ return err // this only returns an error when the state store has gone wrong
529
+ }
530
+ if aclObj != nil && aclObj .AllowVariableOperation (
531
+ ns , pathOrPrefix , cap ) {
532
+ return nil
533
+ }
524
534
}
525
- return nil , structs .ErrPermissionDenied
535
+ return structs .ErrPermissionDenied
526
536
}
527
537
528
538
// authValidatePrefix asserts that the requested path is valid for
0 commit comments