Skip to content

Commit

Permalink
Move flask_aserto from aserto-python
Browse files Browse the repository at this point in the history
  • Loading branch information
oanatmaria committed Jan 16, 2024
1 parent a19dceb commit 882f0aa
Show file tree
Hide file tree
Showing 26 changed files with 2,757 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
poetry.lock linguist-generated=true
packages/aserto-authorizer-grpc/go.sum linguist-generated=true
packages/aserto-authorizer-grpc/src/aserto_authorizer_grpc linguist-generated=true
142 changes: 142 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: ci

on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
push:
branches:
- main
- v*
# Publish `v1.2.3` tags as releases.
tags:
- v*
# Run tests for PRs
pull_request:

env:
VAULT_ADDR: https://vault.eng.aserto.com/


jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
steps:
- name: Read Configuration
uses: hashicorp/[email protected]
id: vault
with:
url: ${{ env.VAULT_ADDR }}
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
kv/data/github "SSH_PRIVATE_KEY" | SSH_PRIVATE_KEY;
- name: Setup git
run: |
mkdir -p $HOME/.ssh
umask 0077 && echo -e "${SSH_PRIVATE_KEY}" > $HOME/.ssh/id_rsa
ssh-keyscan github.com >> $HOME/.ssh/known_hosts
git config --global url."[email protected]:".insteadOf https://github.com/
- name: Checkout Repo
uses: actions/checkout@v2

- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master

- name: Install topaz
run: brew tap aserto-dev/tap && brew install aserto-dev/tap/topaz && topaz install

- name: Use python 3.9
uses: actions/setup-python@v4
with:
python-version: '3.9'

- name: Install and configure Poetry
uses: snok/install-poetry@v1
with:
version: 1.2.1

- name: Run lint
run: |
poetry install
poetry run pyright .
- name: Run tests
run: |
poetry run pytest -vv
release:
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')

name: Release to pypi
steps:
- name: Read Configuration
uses: hashicorp/[email protected]
id: vault
with:
url: ${{ env.VAULT_ADDR }}
token: ${{ secrets.VAULT_TOKEN }}
secrets: |
kv/data/github "SSH_PRIVATE_KEY" | SSH_PRIVATE_KEY;
kv/data/pypi "USERNAME" | POETRY_HTTP_BASIC_PYPI_USERNAME;
kv/data/pypi "PASSWORD" | POETRY_HTTP_BASIC_PYPI_PASSWORD;
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ env.GO_VERSION }}

- name: Setup caching
uses: actions/cache@v2
with:
path: |
~/.cache/go-build
~/go/pkg/mod
.ext
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum', 'Depfile') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install dependencies
run: |
mkdir -p $HOME/.ssh
umask 0077 && echo -e "${SSH_PRIVATE_KEY}" > $HOME/.ssh/id_rsa
ssh-keyscan github.com >> $HOME/.ssh/known_hosts
git config --global url."[email protected]:".insteadOf https://github.com/
git config --global user.email "[email protected]"
git config --global user.name "Aserto Bot"
eval `ssh-agent`
ssh-add $HOME/.ssh/id_rsa
go run mage.go deps
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install Poetry
uses: snok/install-poetry@v1

- name: Build and push the python package
run: go run mage.go release

- name: Bump to the next version
run: go run mage.go bump patch

- name: Commit changes
uses: EndBug/add-and-commit@v9
with:
default_author: github_actions
message: 'Bump to next version'
add: 'pyproject.toml'
push: origin HEAD:main
13 changes: 13 additions & 0 deletions .github/workflows/gitleaks-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: gitleaks-check

on: [pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: gitleaks-check
uses: aserto-dev/gitleaks-action@master
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dist
__pycache__
.DS_Store
.env
.mypy_cache
.pytest_cache
.vscode
.coverage
.python-version
5 changes: 5 additions & 0 deletions Depfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
go:
sver:
importPath: "github.com/aserto-dev/sver/cmd/sver"
version: "v1.3.13"
156 changes: 156 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Aserto Flask middleware
This is the official library for integrating [Aserto](https://www.aserto.com/) authorization into your [Flask](https://github.com/pallets/flask) applications.

## Aserto Middleware
When authorization middleware is configured and attached to a server, it examines incoming requests, extracts authorization parameters like the caller's identity, calls the Aserto authorizers, and rejects messages if their access is denied.

`AuthorizerOptions` are needed for the cration of an `AsertoMiddleware`.

```py
options = AuthorizerOptions(
url=authorizer_service_url,
tenant_id=tenant_id,
api_key=authorizer_api_key,
cert_file_path=cert_file_path,
)
```

To instatiate the middleware, after creating the authorizer's options:

```py
from flask_aserto import AsertoMiddleware, AuthorizationError


app = Flask(__name__)
aserto = AsertoMiddleware(options)

```

Besides the authorizer's options, the following can be configure when creating the middleware:

```py
authorizer_options: AuthorizerOptions,
policy_path_root: str,
identity_provider: IdentityMapper,
policy_instance_name: Optional[str]= None,
policy_instance_label: Optional[str]= None,
policy_path_resolver: Optional[StringMapper] = None,
resource_context_provider: Optional[ResourceMapper] = None,
```

### Policy
`policy_path_root` is the name of the authorization policy package to evaluate.`policy_instance_name`, `policy_instance_label` are the name and label of the policy that is used by the authorizer.

The authorization policy's ID and the decision to be evaluated are specified when creating authorization Middleware, but the policy path is often derived from the URL or method being called. To provide custom logic, `policy_path_resolver` can be provided. An example can be found
https://github.com/aserto-dev/flask-aserto/tree/HEAD/src/flask_aserto/_defaults.py

### Identity
Middleware offer control over the identity used in authorization calls by providing an `IdentityMapper`. Example of a method that takes the identity from flask's `g` object:

```py
def identity_provider() -> Identity:
identity = g.identity

if identity is None:
return Identity(IdentityType.IDENTITY_TYPE_NONE)

return Identity(type=IdentityType.IDENTITY_TYPE_SUB, value=identity)
```

### Resource
A resource can be any structured data that the authorization policy uses to evaluate decisions. By default, middleware do not include a resource in authorization calls.

To add resource data, you can provide a `ResourceMapper` to `resource_context_provider` to attach custom logic. For example:

```py
def resource_context_from_request() -> ResourceContext:
return request.view_args or {}
```

### Add authorization checks to your routes
Below, there is an example of how to add the Middleware to your routes:

```py
from flask_aserto import AsertoMiddleware, AuthorizationError


app = Flask(__name__)
aserto = AsertoMiddleware(**aserto_options)


@app.route("/api/users/<id>", methods=["GET"])
@aserto.authorize
def api_user(id: str) -> Response:
# Raises an AuthorizationError if the `GET.api.users.__id`
# policy returns a decision of "allowed = false"
...
```

## Check Middleware (ReBAC)
In addition to the pattern described above, in which each route is authorized by its own policy module, the 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 `AsertoMiddleware`.

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 with type "user".

Example:
```py

def id_mapper() -> str:
return request.view_args['asset']

@app.route("/resource/<asset>", methods=["GET"])
@requires_auth
@aserto.check(objType="resource", objIdMapper=id_mapper, relationName="can_read").authorize
def get_resource(asset: str):
return {"message": "Hello from GET /resource/" + asset}

```

GetResource(asset) is an http handler function that serves GET request to the /resource/<asset> route. The `check` call only authorizes requests if the calling user has the `can_read` permission on an object of type resource with the object name extracted from the route's {asset} parameter.

### Check Options
The `check` function accepts options that configure the object, subject, and relation sent to the authorizer.

```py
def check(
self,
objId: Optional[str] = "",
objType: Optional[str] = "",
objIdMapper: Optional[StringMapper] = None,
objMapper: Optional[ObjectMapper] = None,
relationName: Optional[str] = "",
relationMapper: Optional[StringMapper] = None,
subjType: Optional[str] = "",
subjMapper: Optional[IdentityMapper] = None,
policyPath: Optional[str] = "",
policyRoot: Optional[str] = "",
policyPathMapper: Optional[StringMapper] = None,
```

`subjType` can be used to override `subject_type` in the resource context. If an subject mapper isn't provided, the check call uses the default one which is `user`.

`relationName` sets the relation name sent to the authorizer.

`relationMapper` can be used in cases where the relation to be checked isn't known ahead of time. It receives a function that returns the name of the relation.

`objType` sets the object type sent to the authorizer.

`objId` sets the object ID sent to the authorizer.

`objIdMapper` is used to determine the object ID sent to the authorizer at runtime. It receives a function that returns an object ID.

`objMapper` can be used to set both the object type and ID at runtime. It receives a function that takes returns an `Obj`.

```py
class Obj:
id: str
objType: str
```

`policyPath` sets the name of the policy module to evaluate in check calls. It defaults to `check`.

`policyRoot` sets the root of the policy module. For example, if the root is set to "myPolicy", the Check call looks for a policy module named `myPolicy.check`.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/aserto-dev/python-authorizer

go 1.19

require github.com/magefile/mage v1.14.0
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
12 changes: 12 additions & 0 deletions mage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
//go:build zeroinstall
// +build zeroinstall

package main

import (
"os"

"github.com/magefile/mage/mage"
)

func main() { os.Exit(mage.Main()) }
Loading

0 comments on commit 882f0aa

Please sign in to comment.