-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
consumer: add support for fine-grain Authorizations
This change closely parallels the corersponding change to the Gazette broker and uses much of the same infrastructure. Built-in Shard APIs now expect and verify Authorizations which carry a set of capabilities and a label selector which scopes the resources (shards) which are authorized to the caller. Shards which don't match the claim's selector are not visible to the client and are indistinguishable from not existing at all. AuthShardClient drives an Authorizer to obtain and attach a suitable credential prior to starting an RPC. AuthShardServer uses a Verifier to verify a caller's claims prior to dispatching a service handler. Custom and application-specific APIs will likely want to use the Service.Authorizer and Service.Verifier fields to power their own authorizing client and server middleware.
- Loading branch information
1 parent
98859d9
commit aaa6cd8
Showing
13 changed files
with
446 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package protocol | ||
|
||
import ( | ||
context "context" | ||
time "time" | ||
|
||
pb "go.gazette.dev/core/broker/protocol" | ||
grpc "google.golang.org/grpc" | ||
) | ||
|
||
// NewAuthShardClient returns an *AuthShardClient which uses the Authorizer | ||
// to obtain and attach an Authorization bearer token to every issued request. | ||
func NewAuthShardClient(sc ShardClient, auth pb.Authorizer) *AuthShardClient { | ||
return &AuthShardClient{Authorizer: auth, Inner: sc} | ||
} | ||
|
||
type AuthShardClient struct { | ||
Authorizer pb.Authorizer | ||
Inner ShardClient | ||
} | ||
|
||
func (a *AuthShardClient) Stat(ctx context.Context, in *StatRequest, opts ...grpc.CallOption) (*StatResponse, error) { | ||
var claims, ok = pb.GetClaims(ctx) | ||
if !ok { | ||
claims = pb.Claims{ | ||
Capability: pb.Capability_READ, | ||
Selector: pb.LabelSelector{ | ||
Include: pb.MustLabelSet("id", in.Shard.String()), | ||
}, | ||
} | ||
} | ||
if ctx, err := a.Authorizer.Authorize(ctx, claims, withExp(false)); err != nil { | ||
return nil, err | ||
} else { | ||
return a.Inner.Stat(ctx, in, opts...) | ||
} | ||
} | ||
|
||
func (a *AuthShardClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) { | ||
var claims, ok = pb.GetClaims(ctx) | ||
if !ok { | ||
claims = pb.Claims{ | ||
Capability: pb.Capability_LIST, | ||
Selector: in.Selector, | ||
} | ||
} | ||
if ctx, err := a.Authorizer.Authorize(ctx, claims, withExp(false)); err != nil { | ||
return nil, err | ||
} else { | ||
return a.Inner.List(ctx, in, opts...) | ||
} | ||
} | ||
|
||
func (a *AuthShardClient) Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error) { | ||
var claims, ok = pb.GetClaims(ctx) | ||
if !ok { | ||
claims = pb.Claims{Capability: pb.Capability_APPLY} | ||
} | ||
if ctx, err := a.Authorizer.Authorize(ctx, claims, withExp(false)); err != nil { | ||
return nil, err | ||
} else { | ||
return a.Inner.Apply(ctx, in, opts...) | ||
} | ||
} | ||
|
||
func (a *AuthShardClient) GetHints(ctx context.Context, in *GetHintsRequest, opts ...grpc.CallOption) (*GetHintsResponse, error) { | ||
var claims, ok = pb.GetClaims(ctx) | ||
if !ok { | ||
claims = pb.Claims{ | ||
Capability: pb.Capability_READ, | ||
Selector: pb.LabelSelector{ | ||
Include: pb.MustLabelSet("id", in.Shard.String()), | ||
}, | ||
} | ||
} | ||
if ctx, err := a.Authorizer.Authorize(ctx, claims, withExp(false)); err != nil { | ||
return nil, err | ||
} else { | ||
return a.Inner.GetHints(ctx, in, opts...) | ||
} | ||
} | ||
|
||
func (a *AuthShardClient) Unassign(ctx context.Context, in *UnassignRequest, opts ...grpc.CallOption) (*UnassignResponse, error) { | ||
var claims, ok = pb.GetClaims(ctx) | ||
if !ok { | ||
claims = pb.Claims{Capability: pb.Capability_APPLY} | ||
for _, id := range in.Shards { | ||
claims.Selector.Include.AddValue("id", id.String()) | ||
} | ||
} | ||
if ctx, err := a.Authorizer.Authorize(ctx, claims, withExp(false)); err != nil { | ||
return nil, err | ||
} else { | ||
return a.Inner.Unassign(ctx, in, opts...) | ||
} | ||
} | ||
|
||
func withExp(blocking bool) time.Duration { | ||
if blocking { | ||
return time.Hour | ||
} else { | ||
return time.Minute | ||
} | ||
} | ||
|
||
// AuthShardServer is similar to ShardServer except: | ||
// - Requests have already been verified with accompanying Claims. | ||
// - The Context or Stream.Context() argument may be subject to a deadline | ||
// bound to the expiration of the user's Claims. | ||
type AuthShardServer interface { | ||
Stat(context.Context, pb.Claims, *StatRequest) (*StatResponse, error) | ||
List(context.Context, pb.Claims, *ListRequest) (*ListResponse, error) | ||
Apply(context.Context, pb.Claims, *ApplyRequest) (*ApplyResponse, error) | ||
GetHints(context.Context, pb.Claims, *GetHintsRequest) (*GetHintsResponse, error) | ||
Unassign(context.Context, pb.Claims, *UnassignRequest) (*UnassignResponse, error) | ||
} | ||
|
||
// NewVerifiedShardServer adapts an AuthShardServer into a ShardServer by | ||
// using the provided Verifier to verify incoming request Authorizations. | ||
func NewVerifiedShardServer(ajs AuthShardServer, verifier pb.Verifier) *VerifiedAuthServer { | ||
return &VerifiedAuthServer{ | ||
Verifier: verifier, | ||
Inner: ajs, | ||
} | ||
} | ||
|
||
type VerifiedAuthServer struct { | ||
Verifier pb.Verifier | ||
Inner AuthShardServer | ||
} | ||
|
||
func (s *VerifiedAuthServer) Stat(ctx context.Context, req *StatRequest) (*StatResponse, error) { | ||
if ctx, cancel, claims, err := s.Verifier.Verify(ctx, pb.Capability_READ); err != nil { | ||
return nil, err | ||
} else { | ||
defer cancel() | ||
return s.Inner.Stat(ctx, claims, req) | ||
} | ||
} | ||
|
||
func (s *VerifiedAuthServer) List(ctx context.Context, req *ListRequest) (*ListResponse, error) { | ||
if ctx, cancel, claims, err := s.Verifier.Verify(ctx, pb.Capability_LIST); err != nil { | ||
return nil, err | ||
} else { | ||
defer cancel() | ||
return s.Inner.List(ctx, claims, req) | ||
} | ||
} | ||
|
||
func (s *VerifiedAuthServer) Apply(ctx context.Context, req *ApplyRequest) (*ApplyResponse, error) { | ||
if ctx, cancel, claims, err := s.Verifier.Verify(ctx, pb.Capability_APPLY); err != nil { | ||
return nil, err | ||
} else { | ||
defer cancel() | ||
return s.Inner.Apply(ctx, claims, req) | ||
} | ||
} | ||
|
||
func (s *VerifiedAuthServer) GetHints(ctx context.Context, req *GetHintsRequest) (*GetHintsResponse, error) { | ||
if ctx, cancel, claims, err := s.Verifier.Verify(ctx, pb.Capability_READ); err != nil { | ||
return nil, err | ||
} else { | ||
defer cancel() | ||
return s.Inner.GetHints(ctx, claims, req) | ||
} | ||
} | ||
|
||
func (s *VerifiedAuthServer) Unassign(ctx context.Context, req *UnassignRequest) (*UnassignResponse, error) { | ||
if ctx, cancel, claims, err := s.Verifier.Verify(ctx, pb.Capability_APPLY); err != nil { | ||
return nil, err | ||
} else { | ||
defer cancel() | ||
return s.Inner.Unassign(ctx, claims, req) | ||
} | ||
} | ||
|
||
var _ ShardServer = &VerifiedAuthServer{} | ||
var _ ShardClient = &AuthShardClient{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.