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

feat: Add gRPC client and admin API capabilities #29

Merged
merged 29 commits into from
Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4c79664
enhancement: Refactor to use gRPC
Sambigeara Jun 26, 2023
20f7df6
add proto definitions and generated python code
Sambigeara Jun 26, 2023
1687790
more tinkering: playground, refactoring, unasync fix, etc
Sambigeara Jun 27, 2023
07a2906
tidy imports
Sambigeara Jun 28, 2023
295ecfc
separated gRPC client entirely. refactored to expose a new `cerbos.sd…
Sambigeara Jul 4, 2023
8f7cf5a
refactor conftest start_container func
Sambigeara Jul 4, 2023
fc43ba8
conftest updates
Sambigeara Jul 4, 2023
acee85f
unasync
Sambigeara Jul 4, 2023
3dc2d10
got playground proxy working
Sambigeara Jul 6, 2023
f33af02
update output tests after cerbos core update
Sambigeara Jul 6, 2023
4544cb1
cert fn comment
Sambigeara Jul 6, 2023
5834388
docstrings
Sambigeara Jul 6, 2023
03bffbd
type checking. remove logger
Sambigeara Jul 7, 2023
ed6663b
add admin client (currently without tests)
Sambigeara Jul 7, 2023
da741ae
linting and isort
Sambigeara Jul 7, 2023
2194ad8
register correct methods for admin client service config
Sambigeara Jul 7, 2023
2eaada6
add admin client tests
Sambigeara Jul 10, 2023
fb8b868
lint
Sambigeara Jul 10, 2023
10541db
update proto defs
Sambigeara Jul 10, 2023
9e7a089
re-enable uds tests
Sambigeara Jul 10, 2023
c31d2a2
update README
Sambigeara Jul 10, 2023
f00efa2
remove grpc plan resources output test
Sambigeara Jul 10, 2023
7a3e4ec
README formatting
Sambigeara Jul 10, 2023
f4f63cf
update formatting targets in scripts
Sambigeara Jul 10, 2023
9e92101
housekeeping
Sambigeara Jul 10, 2023
930b886
explicit import
Sambigeara Jul 10, 2023
e1cdd51
changelog
Sambigeara Jul 10, 2023
2dcabc2
Update cerbos/sdk/container.py
Sambigeara Jul 11, 2023
47db2ed
use `buf generate` to generate python classes without need to store d…
Sambigeara Jul 11, 2023
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Unreleased

### Enhancements

- Refactor to use gRPC

## v0.7.1 (2023-06-07)

### Enhancements
Expand Down
111 changes: 109 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Cerbos Python SDK
=================

Python client for accessing [Cerbos](https://cerbos.dev).
Python clients for accessing [Cerbos](https://cerbos.dev).

Cerbos is the open core, language-agnostic, scalable authorization solution that makes user permissions and authorization simple to implement and manage by writing context-aware access control policies for your application resources.

Expand All @@ -13,6 +13,109 @@ This library is available from PyPI as `cerbos`. It supports both async and non-
pip install cerbos
```

There are two clients available; [gRPC](#grpc-client) and [HTTP](#http-client). New projects should use the gRPC client.

### gRPC Client

(Available from v0.8.0 onwards)

**Making a request**

```python
from cerbos.sdk.grpc.client import CerbosClient
from cerbos.engine.v1 import engine_pb2
from cerbos.request.v1 import request_pb2
from google.protobuf.struct_pb2 import Value

principal = engine_pb2.Principal(
id="john",
roles={"employee"},
policy_version="20210210",
attr={
"department": Value(string_value="marketing"),
"geography": Value(string_value="GB"),
"team": Value(string_value="design"),
},
)

resource = engine_pb2.Resource(
id="XX125",
kind="leave_request",
attr={
"id": Value(string_value="XX125"),
"department": Value(string_value="marketing"),
"geography": Value(string_value="GB"),
"team": Value(string_value="design"),
"owner": Value(string_value="john"),
}
)

plan_resource = engine_pb2.PlanResourcesInput.Resource(
kind="leave_request",
policy_version="20210210"
)

with CerbosClient("localhost:3593", tls_verify=False) as c:
# Check a single action on a single resource
if c.is_allowed("view", principal, resource):
# perform some action
pass

# Get the query plan for "view" action
plan = c.plan_resources(action="view", principal=principal, resource=plan_resource)
````

**Async usage**

```python
from cerbos.sdk.grpc.client import AsyncCerbosClient

async with AsyncCerbosClient("localhost:3593", tls_verify=False) as c:
...

allowed = await c.is_allowed("view:public", p, r)
print(allowed)

# Get the query plan for "view" action
...
plan = await c.plan_resources("view", p, rd)
print(plan.filter.to_json())

```

**Admin API**

There is also a client available for interacting with the Admin API. See [the docs](https://docs.cerbos.dev/cerbos/latest/api/admin_api.html) for information on how to configure your PDP to enable this.

```python
from cerbos.policy.v1 import policy_pb2
from cerbos.sdk.grpc.client import AdminCredentials, AsyncCerbosAdminClient

admin_credentials = AdminCredentials(username="admin", password="some_password")
async with AsyncCerbosAdminClient("localhost:3593", admin_credentials=admin_credentials) as c:
await c.add_or_update_policy(
[
policy_pb2.Policy(
api_version="api.cerbos.dev/v1",
principal_policy=policy_pb2.PrincipalPolicy(
principal="terry", version="default"
),
)
]
)
```

**Connecting to a Unix domain socket**

```python
with CerbosClient("unix:/var/cerbos.sock", tls_verify=False) as c:
...
```

### HTTP client

We maintain this for backwards compatibility. It is recommended to use the [gRPC client](#grpc-client).

**Making a request**

```python
Expand Down Expand Up @@ -52,7 +155,6 @@ with CerbosClient("https://localhost:3592", debug=True, tls_verify=False) as c:

**Async usage**


```python
from cerbos.sdk.model import *
from cerbos.sdk.client import AsyncCerbosClient
Expand Down Expand Up @@ -104,6 +206,11 @@ with container:

See the tests available in the `tests` directory for more examples.

## Contributing

The gRPC client uses protoc generated python classes from definitions retrieved from our [buf registry](https://buf.build/cerbos/cerbos-api).
When making changes to this library, be sure to run the `./proto/generate_protos.sh` to update definitions and generate python classes.

## Get help

- Visit the [Cerbos website](https://cerbos.dev)
Expand Down
47 changes: 47 additions & 0 deletions cerbos/audit/v1/audit_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

96 changes: 96 additions & 0 deletions cerbos/audit/v1/audit_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from cerbos.engine.v1 import engine_pb2 as _engine_pb2
from google.protobuf import timestamp_pb2 as _timestamp_pb2
from google.protobuf.internal import containers as _containers
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from typing import ClassVar as _ClassVar, Iterable as _Iterable, Mapping as _Mapping, Optional as _Optional, Union as _Union

DESCRIPTOR: _descriptor.FileDescriptor

class AccessLogEntry(_message.Message):
__slots__ = ["call_id", "timestamp", "peer", "metadata", "method", "status_code"]
class MetadataEntry(_message.Message):
__slots__ = ["key", "value"]
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: MetaValues
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[MetaValues, _Mapping]] = ...) -> None: ...
CALL_ID_FIELD_NUMBER: _ClassVar[int]
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
PEER_FIELD_NUMBER: _ClassVar[int]
METADATA_FIELD_NUMBER: _ClassVar[int]
METHOD_FIELD_NUMBER: _ClassVar[int]
STATUS_CODE_FIELD_NUMBER: _ClassVar[int]
call_id: str
timestamp: _timestamp_pb2.Timestamp
peer: Peer
metadata: _containers.MessageMap[str, MetaValues]
method: str
status_code: int
def __init__(self, call_id: _Optional[str] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., peer: _Optional[_Union[Peer, _Mapping]] = ..., metadata: _Optional[_Mapping[str, MetaValues]] = ..., method: _Optional[str] = ..., status_code: _Optional[int] = ...) -> None: ...

class DecisionLogEntry(_message.Message):
__slots__ = ["call_id", "timestamp", "peer", "inputs", "outputs", "error", "check_resources", "plan_resources", "metadata"]
class CheckResources(_message.Message):
__slots__ = ["inputs", "outputs", "error"]
INPUTS_FIELD_NUMBER: _ClassVar[int]
OUTPUTS_FIELD_NUMBER: _ClassVar[int]
ERROR_FIELD_NUMBER: _ClassVar[int]
inputs: _containers.RepeatedCompositeFieldContainer[_engine_pb2.CheckInput]
outputs: _containers.RepeatedCompositeFieldContainer[_engine_pb2.CheckOutput]
error: str
def __init__(self, inputs: _Optional[_Iterable[_Union[_engine_pb2.CheckInput, _Mapping]]] = ..., outputs: _Optional[_Iterable[_Union[_engine_pb2.CheckOutput, _Mapping]]] = ..., error: _Optional[str] = ...) -> None: ...
class PlanResources(_message.Message):
__slots__ = ["input", "output", "error"]
INPUT_FIELD_NUMBER: _ClassVar[int]
OUTPUT_FIELD_NUMBER: _ClassVar[int]
ERROR_FIELD_NUMBER: _ClassVar[int]
input: _engine_pb2.PlanResourcesInput
output: _engine_pb2.PlanResourcesOutput
error: str
def __init__(self, input: _Optional[_Union[_engine_pb2.PlanResourcesInput, _Mapping]] = ..., output: _Optional[_Union[_engine_pb2.PlanResourcesOutput, _Mapping]] = ..., error: _Optional[str] = ...) -> None: ...
class MetadataEntry(_message.Message):
__slots__ = ["key", "value"]
KEY_FIELD_NUMBER: _ClassVar[int]
VALUE_FIELD_NUMBER: _ClassVar[int]
key: str
value: MetaValues
def __init__(self, key: _Optional[str] = ..., value: _Optional[_Union[MetaValues, _Mapping]] = ...) -> None: ...
CALL_ID_FIELD_NUMBER: _ClassVar[int]
TIMESTAMP_FIELD_NUMBER: _ClassVar[int]
PEER_FIELD_NUMBER: _ClassVar[int]
INPUTS_FIELD_NUMBER: _ClassVar[int]
OUTPUTS_FIELD_NUMBER: _ClassVar[int]
ERROR_FIELD_NUMBER: _ClassVar[int]
CHECK_RESOURCES_FIELD_NUMBER: _ClassVar[int]
PLAN_RESOURCES_FIELD_NUMBER: _ClassVar[int]
METADATA_FIELD_NUMBER: _ClassVar[int]
call_id: str
timestamp: _timestamp_pb2.Timestamp
peer: Peer
inputs: _containers.RepeatedCompositeFieldContainer[_engine_pb2.CheckInput]
outputs: _containers.RepeatedCompositeFieldContainer[_engine_pb2.CheckOutput]
error: str
check_resources: DecisionLogEntry.CheckResources
plan_resources: DecisionLogEntry.PlanResources
metadata: _containers.MessageMap[str, MetaValues]
def __init__(self, call_id: _Optional[str] = ..., timestamp: _Optional[_Union[_timestamp_pb2.Timestamp, _Mapping]] = ..., peer: _Optional[_Union[Peer, _Mapping]] = ..., inputs: _Optional[_Iterable[_Union[_engine_pb2.CheckInput, _Mapping]]] = ..., outputs: _Optional[_Iterable[_Union[_engine_pb2.CheckOutput, _Mapping]]] = ..., error: _Optional[str] = ..., check_resources: _Optional[_Union[DecisionLogEntry.CheckResources, _Mapping]] = ..., plan_resources: _Optional[_Union[DecisionLogEntry.PlanResources, _Mapping]] = ..., metadata: _Optional[_Mapping[str, MetaValues]] = ...) -> None: ...

class MetaValues(_message.Message):
__slots__ = ["values"]
VALUES_FIELD_NUMBER: _ClassVar[int]
values: _containers.RepeatedScalarFieldContainer[str]
def __init__(self, values: _Optional[_Iterable[str]] = ...) -> None: ...

class Peer(_message.Message):
__slots__ = ["address", "auth_info", "user_agent", "forwarded_for"]
ADDRESS_FIELD_NUMBER: _ClassVar[int]
AUTH_INFO_FIELD_NUMBER: _ClassVar[int]
USER_AGENT_FIELD_NUMBER: _ClassVar[int]
FORWARDED_FOR_FIELD_NUMBER: _ClassVar[int]
address: str
auth_info: str
user_agent: str
forwarded_for: str
def __init__(self, address: _Optional[str] = ..., auth_info: _Optional[str] = ..., user_agent: _Optional[str] = ..., forwarded_for: _Optional[str] = ...) -> None: ...
4 changes: 4 additions & 0 deletions cerbos/audit/v1/audit_pb2_grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

27 changes: 27 additions & 0 deletions cerbos/effect/v1/effect_pb2.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions cerbos/effect/v1/effect_pb2.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper
from google.protobuf import descriptor as _descriptor
from typing import ClassVar as _ClassVar

DESCRIPTOR: _descriptor.FileDescriptor

class Effect(int, metaclass=_enum_type_wrapper.EnumTypeWrapper):
__slots__ = []
EFFECT_UNSPECIFIED: _ClassVar[Effect]
EFFECT_ALLOW: _ClassVar[Effect]
EFFECT_DENY: _ClassVar[Effect]
EFFECT_NO_MATCH: _ClassVar[Effect]
EFFECT_UNSPECIFIED: Effect
EFFECT_ALLOW: Effect
EFFECT_DENY: Effect
EFFECT_NO_MATCH: Effect
4 changes: 4 additions & 0 deletions cerbos/effect/v1/effect_pb2_grpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc

Loading