Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to Cerbos 0.3.0 #8

Merged
merged 2 commits into from
Jul 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@ Securing a REST API with Cerbos
This project demonstrates how to secure a REST API using Cerbos policies. It also shows how to run Cerbos as a sidecar.


How it works
------------

HTTP middleware checks the username and password sent with each request against the user database and builds a Cerbos principal object containing roles and attributes.

```go
principal := cerbos.NewPrincipal(username).
WithRoles(record.Roles...).
WithAttr("aisles", record.Aisles).
WithAttr("ipAddress", r.RemoteAddr)
```

Checking access is as simple as making a call to Cerbos PDP.

```go
resource := cerbos.NewResource("inventory", item.ID).WithAttr("aisle", item.Aisle)
allowed, err := cerbos.IsAllowed(ctx, principal, resource, "DELETE")
```


The Store API
-------------

Expand Down Expand Up @@ -277,3 +297,11 @@ curl -i -u bella:bellasStrongPassword -XDELETE http://localhost:9999/backoffice/
}
```
</details>


Get help
--------

- Visit the [Cerbos website](https://cerbos.dev)
- [Join the Cerbos community on Slack](http://go.cerbos.io/slack)
- Email us at [email protected]
2 changes: 1 addition & 1 deletion cerbos/start_server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONTAINER_IMG=${CONTAINER_IMG:-"pkg.cerbos.dev/containers/cerbos"}
CONTAINER_TAG=${CONTAINER_TAG:-"0.2.1"}
CONTAINER_TAG=${CONTAINER_TAG:-"0.3.0"}

docker run -i -t -p 3592:3592 -p 3593:3593 \
-v ${SCRIPT_DIR}/policies:/policies \
Expand Down
1 change: 1 addition & 0 deletions db/inventory.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2021 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

package db

Expand Down
1 change: 1 addition & 0 deletions db/orders.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2021 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

package db

Expand Down
1 change: 1 addition & 0 deletions db/users.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2021 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

package db

Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
version: "3.9"
services:
cerbos:
image: pkg.cerbos.dev/containers/cerbos:0.2.1
image: pkg.cerbos.dev/containers/cerbos:0.3.0
command: ["server", "--config=/data/config.yaml"]
volumes:
- ./cerbos:/data
- shared-tmpfs:/sock
demo:
image: pkg.cerbos.dev/containers/demo-rest:0.0.2
image: pkg.cerbos.dev/containers/demo-rest:0.1.0
command: ["-cerbos=unix:/sock/cerbos-grpc.sock", "-listen=:9999"]
ports:
- 9999:9999
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/cerbos/demo-rest
go 1.16

require (
github.com/cerbos/cerbos v0.0.2
github.com/cerbos/cerbos v0.3.0
github.com/gorilla/handlers v1.5.1
github.com/gorilla/mux v1.8.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
Expand Down
354 changes: 254 additions & 100 deletions go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2021 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

package main

Expand Down
43 changes: 24 additions & 19 deletions service/service.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright 2021 Zenauth Ltd.
// SPDX-License-Identifier: Apache-2.0

package service

Expand All @@ -18,9 +19,14 @@ import (
"golang.org/x/crypto/bcrypt"
)

type principalKeyType struct{}
type authCtxKeyType struct{}

var principalKey = principalKeyType{}
var authCtxKey = authCtxKeyType{}

type authContext struct {
username string
principal *cerbos.Principal
}

const (
inventoryResource = "inventory"
Expand Down Expand Up @@ -91,13 +97,13 @@ func authenticationMiddleware(next http.Handler) http.Handler {
// Get the basic auth credentials from the request.
user, password, ok := r.BasicAuth()
if ok {
// check the password and retrieve the user record.
principal, err := retrievePrincipal(user, password, r)
// check the password and retrieve the auth context.
authCtx, err := buildAuthContext(user, password, r)
if err != nil {
log.Printf("Failed to authenticate user [%s]: %v", user, err)
} else {
// Add the retrieved principal to the context.
ctx := context.WithValue(r.Context(), principalKey, principal)
ctx := context.WithValue(r.Context(), authCtxKey, authCtx)
next.ServeHTTP(w, r.WithContext(ctx))

return
Expand All @@ -110,8 +116,8 @@ func authenticationMiddleware(next http.Handler) http.Handler {
})
}

// retrievePrincipal verifies the username and password and returns a new principal object.
func retrievePrincipal(username, password string, r *http.Request) (*cerbos.Principal, error) {
// buildAuthContext verifies the username and password and returns a new authContext object.
func buildAuthContext(username, password string, r *http.Request) (*authContext, error) {
// Lookup the user from the database.
record, err := db.LookupUser(r.Context(), username)
if err != nil {
Expand All @@ -129,17 +135,17 @@ func retrievePrincipal(username, password string, r *http.Request) (*cerbos.Prin
WithAttr("aisles", record.Aisles).
WithAttr("ipAddress", r.RemoteAddr)

return principal, nil
return &authContext{username: username, principal: principal}, nil
}

// isAllowed is a utility function to check each action against a Cerbos policy.
func (s *Service) isAllowed(ctx context.Context, resource *cerbos.Resource, action string) bool {
principal := getPrincipal(ctx)
if principal == nil {
authCtx := getAuthContext(ctx)
if authCtx == nil {
return false
}

allowed, err := s.cerbos.IsAllowed(ctx, principal, resource, action)
allowed, err := s.cerbos.IsAllowed(ctx, authCtx.principal, resource, action)
if err != nil {
log.Printf("ERROR: %v", err)
return false
Expand All @@ -148,14 +154,14 @@ func (s *Service) isAllowed(ctx context.Context, resource *cerbos.Resource, acti
return allowed
}

// getPrincipal retrieves the principal stored in the context by the authentication middleware.
func getPrincipal(ctx context.Context) *cerbos.Principal {
p := ctx.Value(principalKey)
if p == nil {
// getAuthContext retrieves the principal stored in the context by the authentication middleware.
func getAuthContext(ctx context.Context) *authContext {
ac := ctx.Value(authCtxKey)
if ac == nil {
return nil
}

return p.(*cerbos.Principal)
return ac.(*authContext)
}

func (s *Service) handleOrderCreate(w http.ResponseWriter, r *http.Request) {
Expand All @@ -174,8 +180,8 @@ func (s *Service) handleOrderCreate(w http.ResponseWriter, r *http.Request) {
return
}

principal := getPrincipal(r.Context())
orderID := s.orders.Create(principal.Id, order)
authCtx := getAuthContext(r.Context())
orderID := s.orders.Create(authCtx.username, order)

writeJSON(w, http.StatusCreated, struct {
OrderID uint64 `json:"orderID"`
Expand Down Expand Up @@ -414,7 +420,6 @@ func (s *Service) handleInventoryPick(w http.ResponseWriter, r *http.Request) {
}

resource := toInventoryResource(record).WithAttr("pickQuantity", pickQty)
fmt.Printf("%+v\n", resource)
if !s.isAllowed(r.Context(), resource, "PICK") {
writeMessage(w, http.StatusForbidden, "Operation not allowed")
return
Expand Down