Skip to content

Commit

Permalink
Merge pull request #27 from aserto-dev/logging
Browse files Browse the repository at this point in the history
Gin improvements + zerolog
  • Loading branch information
ronenh authored Aug 8, 2024
2 parents 1397b90 + f10cce0 commit d4cf89f
Show file tree
Hide file tree
Showing 40 changed files with 1,890 additions and 381 deletions.
170 changes: 99 additions & 71 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ Only a reader and writer are configured. `Client.Importer` and `Client.Exporter`
To easily integrate Aserto authorization into your own services middleware implementations for common
frameworks are available as submodules of `go-aserto/middleware`.

* `middleware/httpz` provides middleware for HTTP servers using the standard [net/http](https://pkg.go.dev/net/http) package.
* `middleware/gorillaz` provides middleware for HTTP servers using [gorilla/mux](https://github.com/gorilla/mux).
* `middleware/ginz` provides middleware for HTTP servers using the [Gin web framework](https://gin-gonic.com).
* `middleware/grpcz` provides middleware for gRPC servers.
* `middleware/gorillaz` provides middleware for HTTP servers using [gorilla/mux](https://github.com/gorilla/mux)
or the standard `net/http` package.
* `middleware/ginz` provides middleware for HTTP servers using the [Gin](https://gin-gonic.com) web framework.

When authorization middleware is configured and attached to a server, it examines incoming requests, extracts
authorization parameters such as the caller's identity, calls the Aserto authorizers, and rejects requests if their
Expand All @@ -240,9 +240,6 @@ type Policy struct {

// Decision is the authorization rule to use.
Decision string

// Label name of the aserto policy's instance being queried for authorization.
InstanceLabel string
}
```

Expand Down Expand Up @@ -324,65 +321,48 @@ or remove fields from the resoruce context. Mappers are called in the order in w

In addition to these, each middleware has built-in mappers that can handle common use-cases.

### gRPC Middleware

The gRPC middleware is available in the sub-package `middleware/grpcz`.
It implements unary and stream gRPC server interceptors in its `.Unary()` and `.Stream()` methods.
### HTTP Middleware

Two flavors of HTTP middleware are available:

* `middleware/httpz`: Middleware for HTTP servers using the standard [net/http](https://pkg.go.dev/net/http) package.
* `middleware/gorillaz`: Middleware with support for [gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux).
* `middleware/ginz`: Middleware for the [Gin](https://github.com/gin-gonic/gin) web framework.

Both are constructed and configured in a similar way. They differ in the signature of their `Handler()`
function, which is used to attach them to HTTP routes, and in the signatures of their mapper functions.

#### net/http Middleware

```go
import (
"github.com/aserto-dev/go-aserto/middleware"
"github.com/aserto-dev/go-aserto/middleware/grpcz"
"google.golang.org/grpc"
"github.com/aserto-dev/go-aserto/middleware/httpz"
)
...
middleware, err := grpcz.New(
mw := httpz.New(
azClient,
middleware.Policy{
Decision: "allowed",
Decision: "allowed",
},
)

server := grpc.NewServer(
grpc.UnaryInterceptor(middleware.Unary),
grpc.StreamInterceptor(middleware.Stream),
)
```

#### Mappers

In addition to the general `WithIdentityMapper`, `WithPolicyPathMapper`, and `WithResourceMapper`, the gRPC middleware
provides methods to help construct resource contexts from incoming messages.

**`WithResourceFromFields(fields ...string)`** selects a specified set of fields from the incoming message to be
included in the resource context.

**WithResourceFromMessageByPath(fieldsByPath map[string][]string, defaults ...string)** is similar to
`WithResourceFromFields` but can select different sets of fields depending on which service method is called.

**WithResourceFromContextValue(ctxKey interface{}, field string)** reads a value from the incoming request context
and adds it as a field to the resource context.

#### Default Mappers

The default behavior of the gRPC middleware is:

* Identity is pulled form the `"authorization"` metadata field (i.e. `middleware.Identity.FromMetadata("authorization")`).
* Policy path is constructed from `grpc.Method()` with dots (`.`) replacing path delimiters (`/`).
* No Resource Context is included in authorization calls by default.

Adding the created authorization middleware to a basic `net/http` server may look something like this:

### HTTP Middleware
```go
http.Handle("/users", mw.HandlerFunc(usersHandler))
```

Two flavors of HTTP middleware are available:
The default behavior of the HTTP middleware is:

* `middleware/gorillaz`: Standard `net/http` middleware with support for [gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux).
* `middleware/ginz`: Middleware for the [Gin](https://github.com/gin-gonic/gin) web framework.
* Identity is retrieved from the "Authorization" HTTP Header, if present.
* Policy path is retrieved from the request URL and method to form a path of the form `METHOD.path.to.endpoint`.
* No resource context is included in authorization calls by default.

Both are constructed and configured in a similar way. They differ in the signature of their `Handler()`
function, which is used to attach them to HTTP routes, and in the signatures of their mapper functions.

#### net/http Middleware
#### gorilla/mux Middleware

```go
import (
Expand Down Expand Up @@ -415,40 +395,40 @@ router.Use(mw.Handler)
router.HandleFunc("/users/{id}", userHandler).Methods("GET")
```


#### Mappers

The default behavior of the HTTP middleware is:
The default behavior of the gorilla/mux middleware is:

* Identity is retrieved from the "Authorization" HTTP Header, if present.
* Policy path is retrieved from the request URL and method to form a path of the form `METHOD.path.to.endpoint`.
If the server uses [`gorilla/mux`](https://github.com/gorilla/mux) and
the route contains path parameters (e.g. `"api/products/{id}"`), the surrounding braces are replaced with a
double-underscore prefix. For example, with policy root `"myApp"`, a request to `GET api/products/{id}` gets the
policy path `myApp.GET.api.products.__id`.
* Any path parameters defined using [`gorilla/mux`](https://github.com/gorilla/mux) are included in the resource
context. For example, if the route is defined as `"api/products/{id}"` and the incoming request URL path is
If the route contains path parameters (e.g. `"api/products/{id}"`), the surrounding braces are replaced with a
double-underscore prefix. For example, a request to `GET api/products/{id}` gets the policy path `GET.api.products.__id`.
* All path parameters are included in the resource context.
For example, if the route is defined as `"api/products/{id}"` and the incoming request URL path is
`"api/products/123"` then the resource context will be `{"id": "123"}`.


#### Gin Middleware

The gin middleware looks and behaves just like the net/http middleware but uses `gin.Context` instead of `http.Request`.

### Check Middleware (ReBAC)

### Relation-Based Access Control (ReBAC)

In addition to the pattern described above, in which each route is authorized by its own policy module,
the HTTP middleware can be used to implement Relation-Based Access Control (rebac) in which authorization
the HTTP middleware can be used to implement Relation-Based Access Control (ReBAC) in which authorization
decisions are made by checking if a given subject has the necessary permission or relation to the object being accessed.

This is achieved using the `Check` function on `http.Middleware`.
See [here](https://www.topaz.sh/docs/directory) for a more in-depth overview of ReBAC in Aserto.

The canonical policy for ReBAC is [ghcr.io/aserto-policies/policy-rebac](https://github.com/aserto-templates/policy-rebac/tree/main/content).

The `Check()` function on HTTP middleware (`httpz`, `gorillaz`, or `ginz`) to annotate individual routes with
instructions for populating the resource context for ReBAC checks.

A check call needs three pieces of information:

* The type and key of the object.
* The name of the relation or permission to look for.
* The type and key of the subject. When omitted, the subject is derived from the middleware's [Identity](#identity)
with type `"user"`.
* The type and ID of the object being accessed.
* The name of the relation or permission to check.
* The type and ID of the subject attempting to access the object.

Example:
```go
Expand All @@ -466,35 +446,83 @@ router.Handle(
`GetItem()` is an http handler function that serves GET request to the `/items/{id}` route.
The `mw.Check` call only authorizes requests if the calling user has the `read` permission on an object of type `item`
with the object ID extracted from the route's `{id}` parameter.
The subject type is `user` by default and the subject ID is inferred from the `Authorization` header.

#### Check Options

The `Check()` function accepts options that configure the object, subject, and relation sent to the authorizer.

`WithIdentityMapper(IdentityMapper)` can be used to override the identity context sent to the authorizer. The `mapper` is a
function that takes an `http.Request` and a `middleware.Identity` and can set options on the `Identity` object based on
**`WithIdentityMapper(IdentityMapper)`** can be used to override the identity context sent to the authorizer. The `mapper` is a
function that takes the incoming request and a `middleware.Identity` and can set options on the `Identity` object based on
information from the request.
If an identity mapper isn't provided, the check call uses the identity configured on the middleware object on which
the `Check` call is made.

**`WithRelation(string)`** sets the relation name sent to the authorizer.

**`WithRelationMapper(StringMapper)`** can be used in cases where the relation to be checked isn't known ahead of time. It
receives a function that takes an `http.Request` object and returns the name of the relation.
receives a function that takes the incoming request and returns the name of the relation or permission to check.

**`WithObjectType(string)`** sets the object type sent to the authorizer.

**`WithObjectID(string)`** sets the object ID sent to the authorizer.

**`WithObjectIDMapper(StringMapper)`** is used to determine the object ID sent to the authorizer at runtime. It receives
a function that takes an `http.Request` object and returns an object ID.
a function that takes the incoming request and returns an object ID.

**`WithObjectIDFromVar(string)`** configures the check call to use the value of a path parameter as the object ID sent to
the authorizer.
**`WithObjectIDFromVar(string)`** (only in `gorillaz` and `ginz` middleware) configures the check call to use the value of
a path parameter as the object ID sent to the authorizer.

**`WithObjectMapper(ObjectMapper)`** can be used to set both the object type and ID at runtime. It receives a function that
takes an `http.Request` and returns a `(objectType string, objectID string)` pair.
takes the incoming request and returns a `(objectType string, objectID string)` pair.

**`WithPolicyPath(string)`** sets the name of the policy module to evaluate in check calls. It defaults to `check`.
If the `Policy` object used to construct the middleware contains the `Root` field, the root is used as a prefix.
For example, if the root is set to `"myPolicy"`, the `Check` call looks for a policy module named `myPolicy.check`.

### gRPC Middleware

The gRPC middleware is available in the sub-package `middleware/grpcz`.
It implements unary and stream gRPC server interceptors in its `.Unary()` and `.Stream()` methods.

```go
import (
"github.com/aserto-dev/go-aserto/middleware"
"github.com/aserto-dev/go-aserto/middleware/grpcz"
"google.golang.org/grpc"
)
...
middleware, err := grpcz.New(
azClient,
middleware.Policy{
Decision: "allowed",
},
)

server := grpc.NewServer(
grpc.UnaryInterceptor(middleware.Unary),
grpc.StreamInterceptor(middleware.Stream),
)
```

#### Mappers

In addition to the general `WithIdentityMapper`, `WithPolicyPathMapper`, and `WithResourceMapper`, the gRPC middleware
provides methods to help construct resource contexts from incoming messages.

**`WithResourceFromFields(fields ...string)`** selects a specified set of fields from the incoming message to be
included in the resource context.

**WithResourceFromMessageByPath(fieldsByPath map[string][]string, defaults ...string)** is similar to
`WithResourceFromFields` but can select different sets of fields depending on which service method is called.

**WithResourceFromContextValue(ctxKey interface{}, field string)** reads a value from the incoming request context
and adds it as a field to the resource context.

#### Default Mappers

The default behavior of the gRPC middleware is:

* Identity is pulled form the `"authorization"` metadata field (i.e. `middleware.Identity.FromMetadata("authorization")`).
* Policy path is constructed from `grpc.Method()` with dots (`.`) replacing path delimiters (`/`).
* No Resource Context is included in authorization calls by default.
62 changes: 62 additions & 0 deletions examples/middleware/http/gin/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module gin_example

go 1.22.4

replace github.com/aserto-dev/go-aserto => ../../../..

replace github.com/aserto-dev/go-aserto/middleware/ginz => ../../../../middleware/ginz

require (
github.com/aserto-dev/go-aserto v0.31.5
github.com/aserto-dev/go-aserto/middleware/ginz v0.0.0-00010101000000-000000000000
github.com/gin-gonic/gin v1.10.0
)

require (
github.com/aserto-dev/errors v0.0.10 // indirect
github.com/aserto-dev/go-authorizer v0.20.8 // indirect
github.com/aserto-dev/header v0.0.7 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.21.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx/v2 v2.1.1 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240723171418-e6d459c13d2a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit d4cf89f

Please sign in to comment.