https://github.com/guicassolato/authorino-spicedb
Zanzibar-like authorization with SpiceDB and Authorino (demo)
https://github.com/guicassolato/authorino-spicedb
Last synced: 7 days ago
JSON representation
Zanzibar-like authorization with SpiceDB and Authorino (demo)
- Host: GitHub
- URL: https://github.com/guicassolato/authorino-spicedb
- Owner: guicassolato
- Created: 2023-02-08T17:59:46.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2023-02-16T13:24:09.000Z (about 2 years ago)
- Last Synced: 2025-02-13T23:46:57.103Z (about 2 months ago)
- Size: 3.04 MB
- Stars: 2
- Watchers: 4
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- awesome-spicedb - guicassolato/authorino-spicedb - Implementation of [Envoy external authz](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter) that can be driven by SpiceDB (Integrations / Third-party Integrations)
README
# Demo: Zanzibar-like authorization with SpiceDB and Authorino
This is a demo of integrating Authorino with Authzed's [SpiceDB](https://authzed.com/spicedb).
SpiceDB is a Google [Zanzibar](https://research.google/pubs/pub48190)-inspired authorization system that, like Google Zanzibar, allows for the modeling of fine-grained permissions based on relationships (Relationship-Based Access Control, or ReBAC).
One of the main challenges of implementing fine-grained permissions with an external authorization system is making that system aware of the existing relations. In this demo, we use Authorino [callbacks](https://github.com/Kuadrant/authorino/blob/main/docs/features.md#callbacks-callbacks) to inform SpiceDB about the permissions implied by the operations requested by the users, such as creating or deleting an application resource, as well as granting and revoking access to resources for third-party users.
The full scope of the demo consists of protecting endpoints of a REST API that handles documents, the Docs API. Any authenticated user with a valid API key is allowed to create documents. Users can read and delete their own documents, as well as grant read access to their documents for other users. All fine-grained permissions involved are automatically stored in SpiceDB by Authorino, based on the operations requested by the users to the Docs API.
## Requirements
- [Docker](https://docker.com)
- [Kind](https://kind.sigs.k8s.io)## The stack
- **Kubernetes cluster**
Started locally with [Kind](https://kind.sigs.k8s.io/).
- **Docs API**
A REST API application that will be protected using SpiceDB and Authorino.
The following HTTP endpoints are available:
```
GET /docs List all docs
GET /docs/{id} Read a doc
POST /docs/{id} Create a doc
DELETE /docs/{id} Delete a doc
```
- **[Envoy proxy](https://envoyproxy.io)**
Deployed as sidecar of the Docs API, to serve the application with the [External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter#config-http-filters-ext-authz) filter enabled and pointing to Authorino. After deploying the sidecar, the following additional endpoints are introduced:
```
POST /docs/{id}/allow/{user} Grant read access to the doc
DELETE /docs/{id}/allow/{user} Revoke read access to the doc
```
- **[Authorino Operator](https://github.com/kuadrant/authorino-operator)**
Cluster-wide installation of the operator and CRDs to manage and use Authorino authorization services.
- **[Authorino](https://github.com/kuadrant/authorino)**
The external authorization service, deployed in [`namespaced`](https://github.com/Kuadrant/authorino/blob/main/docs/architecture.md#cluster-wide-vs-namespaced-instances) reconciliation mode, in the same K8s namespace as the Docs API.
- **[SpiceDB](https://authzed.com/spicedb)**
Open source Zanzibar-inspired database to store, compute, and validate fine grained permissions.
- **[Contour](https://projectcontour.io)**
Kubernetes Ingress Controller based on the Envoy proxy, to handle the ingress traffic to the Docs API and to Keycloak.
> **Note:** For simplicity, in the demo all components are deployed without TLS.
## Setup
🅰 Create the cluster
```sh
kind create cluster --name authorino-demo --config -<🅱 Install Contour
```sh
kubectl apply -f https://raw.githubusercontent.com/guicassolato/authorino-spicedb/main/contour.yaml
```🅲 Install the Authorino Operator
```sh
kubectl apply -f https://raw.githubusercontent.com/Kuadrant/authorino-operator/main/config/deploy/manifests.yaml
```> **Note:** In OpenShift, the Authorino Operator can alternatively be installed directly from the Red Hat OperatorHub, using [Operator Lifecycle Manager](https://olm.operatorframework.io/).
## Run the demo ① → ④
### ① Deploy the Docs API
🅰 Create the namespace
```sh
kubectl create namespace docs-api
```🅱 Deploy the Docs API in the namespace
```sh
kubectl -n docs-api apply -f -<🅲 Try the Docs API unprotected
```sh
curl http://docs-api.127.0.0.1.nip.io/docs -i
# HTTP/1.1 200 OK
```### ② Create the permissions database
🅰 Create the namespace
```sh
kubectl create namespace spicedb
```🅱 Deploy the SpiceDB instance
```sh
kubectl -n spicedb apply -f -<🅲 Create the permission schema
```sh
curl -X POST http://spicedb.127.0.0.1.nip.io/v1/schema/write \
-H 'Authorization: Bearer secret' \
-H 'Content-Type: application/json' \
-d @- <### ③ Lock down the Docs API
🅰 Request an instance of Authorino
```sh
kubectl -n docs-api apply -f -<🅱 Redeploy the Docs API with the sidecar proxy
```sh
kubectl -n docs-api apply -f -<🅲 Try the Docs API without authentication
```sh
curl http://docs-api.127.0.0.1.nip.io/docs -i
# HTTP/1.1 404 Not Found
# x-ext-auth-reason: Service not found
# server: envoy
# ...
```### ④ Open up the Docs API for authenticated and authorized users
🅰 Create the AuthConfig
```sh
kubectl -n docs-api apply -f -<🅱 Create the API keys for users to consume the Docs API
```sh
kubectl -n docs-api apply -f -<🅲 Consume the Docs API fully protected
As 👩🏾 Emilia, **create** a doc:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
-X POST \
-H 'Content-Type: application/json' \
-d '{"title":"Emilia´s doc","body":"This is Emilia´s doc."}' \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 200 OK
# ...
# {"id":"123","title":"Emilia´s doc","body":"This is Emilia´s doc.","date":"2023-02-07 18:17:30 +0000","author":"👩🏾 Emilia Jones","user_id":"emilia"}
```
As 👩🏾 Emilia, **read** the doc just created:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
-X GET \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 200 OK
```
As 🧑🏻🦰 Beatrice, try to **read** the doc created by Emilia:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMBEATRICE' \
-X GET \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 403 Forbidden
# x-ext-auth-reason: PERMISSIONSHIP_NO_PERMISSION;token=...
```
As 👩🏾 Emilia, **grant** access to the doc for 🧑🏻🦰 Beatrice:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
-X POST \
http://docs-api.127.0.0.1.nip.io/docs/123/allow/beatrice -i
# HTTP/1.1 200 OK
```
As 🧑🏻🦰 Beatrice, try again to **read** the doc owned by Emilia:
```sh
curl -H 'Authorization: APIKEY IAMBEATRICE' \
-X GET \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 200 OK
```
As 🧑🏻🦰 Beatrice, **create** a doc of her own:
```sh
curl -H 'Authorization: APIKEY IAMBEATRICE' \
-X POST \
-H 'Content-Type: application/json' \
-d '{"title":"Beatrice´s doc","body":"This is Beatrice´s doc."}' \
http://docs-api.127.0.0.1.nip.io/docs/456 -i
# HTTP/1.1 200 OK
# ...
# {"id":"456","title":"Beatrice´s doc","body":"This is Beatrice´s doc.","date":"2023-02-07 18:25:10 +0000","author":"🧑🏻🦰 Beatrice Smith","user_id":"beatrice"}
```
As 🧑🏻🦰 Beatrice, **list** all the docs Beatrice has access to:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMBEATRICE' \
http://docs-api.127.0.0.1.nip.io/docs -i
# HTTP/1.1 200 OK
# ...
# [
# {"id":"123","title":"Emilia´s doc","body":"This is Emilia´s doc.","date":"2023-02-07 18:17:30 +0000","author":"👩🏾 Emilia Jones","user_id":"emilia"},
# {"id":"456","title":"Beatrice´s doc","body":"This is Beatrice´s doc.","date":"2023-02-07 18:25:10 +0000","author":"🧑🏻🦰 Beatrice Smith","user_id":"beatrice"}
# ]
```
As 👩🏾 Emilia, **list** all the docs Emilia has access to:
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
http://docs-api.127.0.0.1.nip.io/docs -i
# HTTP/1.1 200 OK
# ...
# [{"id":"123","title":"Emilia´s doc","body":"This is Emilia´s doc.","date":"2023-02-07 18:17:30 +0000","author":"👩🏾 Emilia Jones","user_id":"emilia"}]
```
As 👩🏾 Emilia, **revoke** 🧑🏻🦰 Beatrice's access to the doc:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
-X DELETE \
http://docs-api.127.0.0.1.nip.io/docs/123/allow/beatrice -i
# HTTP/1.1 200 OK
```
As 🧑🏻🦰 Beatrice, **list** again the docs Beatrice has access to:
```sh
curl -H 'Authorization: APIKEY IAMBEATRICE' \
http://docs-api.127.0.0.1.nip.io/docs -i
# HTTP/1.1 200 OK
# ...
# [{"id":"456","title":"Beatrice´s doc","body":"This is Beatrice´s doc.","date":"2023-02-07 18:25:10 +0000","author":"🧑🏻🦰 Beatrice Smith","user_id":"beatrice"}]
```
As 🧑🏻🦰 Beatrice, try one last time to **read** the doc owned by Emilia:
```sh
curl -H 'Authorization: APIKEY IAMBEATRICE' \
-X GET \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 403 Forbidden
# x-ext-auth-reason: PERMISSIONSHIP_NO_PERMISSION;token=...
```
As 👩🏾 Emilia, **delete** the doc:
What should happen?
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
-X DELETE \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 200 OK
```
As 👩🏾 Emilia, retry to **read** the doc just deleted:
```sh
curl -H 'Authorization: APIKEY IAMEMILIA' \
-X GET \
http://docs-api.127.0.0.1.nip.io/docs/123 -i
# HTTP/1.1 403 Forbidden
# x-ext-auth-reason: PERMISSIONSHIP_NO_PERMISSION;token=...
```## Cleanup
```sh
kind delete cluster --name authorino-demo
```## Caveats
### Consistency
Because Authorino builds in SpiceDB the permission relationships implied by the requests sent to the Docs API _before_ these requests effectively hit the application, and at the same time the application itself has no knowledge of the authorization system in place at all, the system may run into a situation where the resources and relations stored in the application mismatch the state of the relationships in SpiceDB. This can happen, for example, if an authorized request (i.e. after passing Authorino) fails to be processed by the application due to a server error.
To mitigate the risk of consistency issues, the HTTP requests sent to the Docs API must be treated as an atomic transaction from the moment Envoy hands it over to Authorino, until the upstream application response is processed by Envoy.
To be able to recover from possible consistency issues in case the mitigation fails, logs of the requests handled by Authorino can be stored including timestamp, username, as well as method and path of the HTTP request. Such logs can be implemented by adding another Authorino [callback](https://github.com/Kuadrant/authorino/blob/main/docs/features.md#callbacks-callbacks) in the AuthConfig. The system should occasionally check for consistency issues and use the logs to rebuild the desired state incrementally.
### Latency
Compared to monolithic approach of embedded authorization rules and without proxy in the middle, another caveat of this implementation comes from the extra hops involved in the communication between sidecar proxy (Envoy) and authorization service (Authorino), authorization service and permission database/policy engine (SpiceDB), and sidecar proxy and upstream application (Docs API), and its significance in terms of added latency to the overall request.
To mitigate the impact on latency especially due to the HTTP and GRPC communication between Authorino and SpiceDB, [caching](https://github.com/Kuadrant/authorino/blob/main/docs/features.md#common-feature-caching-cache) can be enabled in Authorino for the `metadata` and `authorization` rules.
Performance can also be improved once `callbacks` in the AuthConfig can be processed asynchronously (see https://github.com/Kuadrant/authorino/issues/369).