grappa

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2021 License: MIT Imports: 13 Imported by: 0

README

grappa

Build Status codecov Go Report Card

grappa offers a protoc plugin to generate and register method authorization rules directly from .proto definitions and a supporting JWT interceptor to validate the generated rules on method invocation.

The module started as an experiment combining protoc-gen-star for plugin creation, with jwt-go for token validation to simplify the creation of JWT middleware for GRPC services written in Go.

Getting started

go get github.com/stevecallear/grappa
go install github.com/stevecallear/grappa/cmd/protoc-gen-grappa

grappa uses a custom MethodOptions extension to allow per-method rules to be generated. The following .proto definition configures an anonymous method, and another that requires the request authorization to contain the user scope.

syntax = "proto3";
package example;

import "google/protobuf/empty.proto";
import "grappapb/annotations.proto";

option go_package = "github.com/org/repo/proto/example";

service ExampleService {    
    rpc MethodA(google.protobuf.Empty) returns (google.protobuf.Empty) {
        option (grappa.rule) = {
            allow_anonymous: True
        };
    }

    rpc MethodB(google.protobuf.Empty) returns (google.protobuf.Empty) {
        option (grappa.rule) = {
            allow_anonymous: False
            require_scope: "user"
        };
    }
}

Supporting Go code can then be generated using the protoc-gen-grappa plugin.

protoc -I. --proto_path="/path/to/proto" --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. ./proto/*.proto
protoc -I. --proto_path="/path/to/proto" --grappa_out=paths=source_relative:. ./proto/*.proto

This will generate example.pb.grappa.go alongside the standard protobuf/GRPC outputs. The rules can then be registered as a method interceptor.

auth := grappa.New(grappa.RSA(publicKey), grappa.VerifyClaims("issuer.com", "audience.com"))
example.RegisterExampleServiceServerRules(auth)

svr := grpc.NewServer(grpc.UnaryInterceptor(auth.UnaryInterceptor))
example.RegisterExampleServiceServer(svr, service)

svr.Serve(listener)

Note: the VerifyClaims option is required to evaluate the require_scope definition. This adds claim verification for iss, aud and scope.

Configuration

grappa.New returns a configured JWT authorizer that exposes a unary interceptor function. A stream interceptor is not currently implemented.

By default the options will extract the JWT bearer token from an Authorization header and will return codes.Unauthenticated for all errors. Further customisation is available by supplying one or more option functions with the signature func (o *grappa.Options).

The default KeyFn will return an error for all requests, so must be configured. grappa.HMAC and grappa.RSA can be used to configure HMAC and RSA keys respectively.

Anonymous access

Per-method anonymous access can be configured by specifying allow_anonymous in the proto definition.

By default grappa will return an error in scenarios where no rule has been configured. This ensures that all methods require authorization unlesss explicitly granted anonymous access. To override this behaviour, the grappa.Optional option can be supplied, which will grant anonymous access to all methods unless they have a rule defined that requires authorization.

auth := grappa.New(grappa.RSA(publicKey), grappa.Optional)
Claims verification

By default, claims are validated as per the behaviour of jwt-go. In addition, the grappa.VerifyClaims option can be supplied to verify the issuer, audience and required scopes.

auth := grappa.New(grappa.RSA(publicKey), grappa.VerifyClaims("issuer.com", "audience.com"))

Custom verifiers that satisfy the grappa.VerifyFunc signature can be configured as required.

auth := grappa.New(grappa.RSA(publicKey), func(o *grappa.Options) {
    o.ClaimsVerifiers = append(o.ClaimsVerifiers, func(ctx grappa.Context, c jwt.MapClaims) error {
        if v, ok := c["claim"]; ok {
            s, _ := v.(string)
            if s == "expected value" {
                return nil
            }
        }
        return errors.New("invalid claim")
    })
})

Verifiers are executed in the order that they are present within the ClaimsVerifiers slice.

Claims capture

If the server needs to evaluate token claims, such as the subject then they can be extracted using grappa.CaptureClaim.

auth := grappa.New(grappa.RSA(publicKey), grappa.CaptureClaim("sub", "auth.sub"))

This will result in the string value of the claim being available in the context metadata:

if md, ok := metadata.FromIncomingContext(ctx); ok {
    if vs := md.Get("auth.sub"); len(vs) > 0 {
        log.Println(vs[0])
    }
}
Wildcard rules

It is possible to register rules for external services, these can include a trailing wildcard. For example, the following will grant anonymous access to all health check methods.

auth := grappa.New(grappa.HMAC(key))
example.RegisterExampleServiceServerRules(auth)

auth.Register("/grpc.health/v1.Health/*", &grappapb.Rule{
    AllowAnonymous: true,
})
Error handling

By default the authorizor will return codes.Unauthenticated for all errors to avoid leaking internal information. It is possible to override this behaviour to implement logging or error customisation.

auth := grappa.New(grappa.RSA(publicKey), func(o *grappa.Options) {
    o.ErrorFn = func(ctx grappa.Context, err error) error {
        log.Printf("%s %s: %v", ctx.ID, ctx.FullMethod, err)
        return status.Error(codes.Unauthenticated, "unauthenticated")
    }
})

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CaptureClaim

func CaptureClaim(claim, metadataKey string) func(*Options)

CaptureClaim configures the authorizor to cature the value of the claim and store it in the request metadata with the specified key e.g. grappa.CaptureClaim("sub", "auth.sub")

func HMAC

func HMAC(key []byte) func(*Options)

HMAC configures the middleware to use the specified HMAC key

func Optional

func Optional(o *Options)

Optional configures the authorizor to allow anonymous access to methods that do not have a rule specified

func RSA

func RSA(key *rsa.PublicKey) func(*Options)

RSA configures the middleware to use the specified RSA PEM key

func VerifyClaims

func VerifyClaims(iss, aud string) func(*Options)

VerifyClaims configues the authorizor to use default issuer, audience and scope verification

Types

type Authorizor

type Authorizor struct {
	// contains filtered or unexported fields
}

Authorizor represents a jwt authorizor

func New

func New(optFns ...func(*Options)) *Authorizor

New returns a new authorizor for the specified options

func (*Authorizor) Register

func (a *Authorizor) Register(pattern string, r *grappapb.Rule)

Register registers the rule for the specified method pattern

func (*Authorizor) UnaryInterceptor

func (a *Authorizor) UnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error)

UnaryInterceptor is a unary interceptor func

type Context

type Context struct {
	ID         string
	FullMethod string
	Rule       *grappapb.Rule
}

Context represents a request context

type Options

type Options struct {
	TokenFn         func(Context, metadata.MD) (string, bool)
	KeyFn           func(Context, *jwt.Token) (interface{}, error)
	ErrorFn         func(Context, error) error
	ClaimsVerifiers []VerifyFunc
	ClaimsMap       map[string]string
	Optional        bool
}

Options represents a set of auth options

type Registry

type Registry interface {
	Register(pattern string, r *grappapb.Rule)
}

Registry represents a rule registry

type VerifyFunc

type VerifyFunc func(Context, jwt.MapClaims) error

VerifyFunc represents a claims verification func

func VerifyAudience

func VerifyAudience(aud []string) VerifyFunc

VerifyAudience verifies the audience claim

func VerifyIssuer

func VerifyIssuer(iss string) VerifyFunc

VerifyIssuer verifies the issuer claim

func VerifyScope

func VerifyScope() VerifyFunc

VerifyScope verifies the scope claim

Directories

Path Synopsis
cmd
internal
proto

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL