Categorygithub.com/stevecallear/grappa
modulepackage
0.2.0
Repository: https://github.com/stevecallear/grappa.git
Documentation: pkg.go.dev

# 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")
    }
})

# Packages

No description provided by the author
No description provided by the author

# Functions

CaptureClaim configures the authorizor to cature the value of the claim and store it in the request metadata with the specified key e.g.
HMAC configures the middleware to use the specified HMAC key.
New returns a new authorizor for the specified options.
Optional configures the authorizor to allow anonymous access to methods that do not have a rule specified.
RSA configures the middleware to use the specified RSA PEM key.
VerifyAudience verifies the audience claim.
VerifyClaims configues the authorizor to use default issuer, audience and scope verification.
VerifyIssuer verifies the issuer claim.
VerifyScope verifies the scope claim.

# Structs

No description provided by the author
No description provided by the author
Options represents a set of auth options.

# Interfaces

No description provided by the author

# Type aliases

VerifyFunc represents a claims verification func.