Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/anderseknert/clj-opa
Clojure client for Open Policy Agent
https://github.com/anderseknert/clj-opa
authorization authorization-middleware clj-opa compojure middleware opa open-policy-agent rego ring
Last synced: about 2 months ago
JSON representation
Clojure client for Open Policy Agent
- Host: GitHub
- URL: https://github.com/anderseknert/clj-opa
- Owner: anderseknert
- License: apache-2.0
- Created: 2021-01-07T16:13:50.000Z (almost 4 years ago)
- Default Branch: main
- Last Pushed: 2024-01-18T11:17:48.000Z (11 months ago)
- Last Synced: 2024-10-14T15:56:59.151Z (about 2 months ago)
- Topics: authorization, authorization-middleware, clj-opa, compojure, middleware, opa, open-policy-agent, rego, ring
- Language: Clojure
- Homepage:
- Size: 29.3 KB
- Stars: 11
- Watchers: 3
- Forks: 0
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-opa - clj-opa - Middleware and utilities for app authorization with OPA in Clojure (Language and Platform Integrations / Clojure)
README
# clj-opa
[![Clojars Project](https://img.shields.io/clojars/v/clj-opa.svg)](https://clojars.org/clj-opa)
Ring middleware for app authorization using [Open Policy Agent](https://www.openpolicyagent.org/) (OPA).
## Install
To install, add the following to your project `:dependencies`
```
[clj-opa "0.1.1"]
```## Usage
#### wrap-opa-authorize
The `wrap-opa-authorize` middleware will intercept any incoming request and delegate the authorization decision to OPA.
By default, the `wrap-opa-authorize` middleware expects a response containing an `allow` attribute equal to `true` or
else the `:enforce-fn` will be called, which in the default configuration serves a **403 Forbidden** to the caller.Example configuration using [Compojure](https://github.com/weavejester/compojure) for routing:
```clojure
(ns my.app
(:require [compojure.core :refer [defroutes ANY]]
[ring.middleware.defaults :refer [wrap-defaults api-defaults]]
[opa.middleware :refer [wrap-opa-authorize]]))(defroutes app-routes
(ANY "/public" [] "Public endpoint")
(ANY "/*" [] "Protected endpoint"))(def app (-> (wrap-defaults app-routes api-defaults)
(wrap-opa-authorize)))
```Example Rego policy that allows `GET` requests on the `/public` endpoint:
```rego
package policydefault allow := false
allow {
input.method == "GET"
input.path[0] == "public"
}
```##### Configuration
The `wrap-opa-authorize` function takes a configuration map as an optional argument, offering the following options:
| Option | Description | Default |
|--------|-------------|---------|
| `:server-addr` | Base URL of OPA server | http://localhost:8181 |
| `:policy-path` | Path to policy document (excluding `/v1/data`) | /policy |
| `:input-fn` | Function taking request and returning the input map to provide OPA | See below |
| `:enforce-fn` | Function to call when authorization fails | Function returning a 403 Forbidden response |Unless a custom `:input-fn` is provided, an input map containing the path components (split on /), the request method
and - if found in the request authorization header - a bearer token to use for authN/authZ:```json
{
"path": ["public", "assets", "images"],
"method": "GET",
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbmRlcnNla25lcnQifQ.odt_88oGgYHoYU2SRdMjLYA0aG-NXDyeYqj_x9voAa4"
}
```
**Note** that the input map should _not_ contain the `input` attribute itself - that is added by the middleware.If you would like to change or extend the default input map with custom attributes, you may do so by calling the
`opa.middleware/default-input-fn` and change the result to your liking.Example, custom configuration:
```clojure
(def app
(-> (wrap-defaults app-routes api-defaults)
; Query the OPA server at `https://opa.example.com` for authorization decisions on the
; `/authz/policy/rules` path with input built from a custom request header `"X-Username"`
; and with and enforcement function that returns a 401 Unauthorized response on failures
; and forwards the response body from OPA to the caller.
(wrap-opa-authorize {:server-addr "https://opa.example.com"
:policy-path "/authz/policy/rules"
:input-fn (fn [request] {:username (get-in request [:headers :x-username])})
:enforce-fn (fn [opa-response] {:status 401 :body opa-response})})))
```#### Other utilities and helper functions
See the [API reference](https://cljdoc.org/d/clj-opa/clj-opa/0.1.0/doc/readme).