Skip to content

Commit

Permalink
enhancement: Support optional gRPC channel options
Browse files Browse the repository at this point in the history
Signed-off-by: Sam Lock <sam@swlock.co.uk>
  • Loading branch information
Sambigeara committed Jul 26, 2023
1 parent 51eb6ab commit 75a76fc
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 14 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

### Enhancements

- Support optional gRPC channel options

## v0.9.1 (2023-07-25)

### Bug fixes
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,37 @@ with CerbosClient("unix:/var/cerbos.sock", tls_verify=False) as c:
...
```

**Enabling TLS**

`tls_verify` can either be the certificate location (string) or a boolean. If `True`, it'll look for the file at the location specified by the environment variable `SSL_CERT_FILE`, else the default OS location.

```python
with CerbosClient("localhost:3593", tls_verify=True) as c:
...
```

```python
with CerbosClient("localhost:3593", tls_verify="path/to/tls.crt") as c:
...
```

**Optional channel arguments**

You can pass additional options in the `channel_options` dict.
Available options are described [here](https://github.com/grpc/grpc/blob/7536d8a849c0096e4c968e7730306872bb5ec674/include/grpc/impl/grpc_types.h).
The argument is of type `dict[str, Any]` where the `Any` value must match the expected type defined in the previous link.

NOTE: We provide this as a generic method to set arbitrary options for particular use cases.
For purely demonstrative purposes, our example below overrides `grpc.ssl_target_name_override`, which is certainly not recommended practice for production applications.

```python
opts = {
"grpc.ssl_target_name_override": "localhost"
}
with CerbosClient("localhost:3593", tls_verify=True, channel_options=opts) as c:
...
```

### HTTP client

We maintain this for backwards compatibility. It is recommended to use the [gRPC client](#grpc-client).
Expand Down
24 changes: 17 additions & 7 deletions cerbos/sdk/_async/_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(
timeout_secs: float | None = None,
request_retries: int = 0,
wait_for_ready: bool = False,
channel_options: dict[str, Any] | None = None,
):
if timeout_secs and not isinstance(timeout_secs, (int, float)):
raise TypeError("timeout_secs must be a number type")
Expand All @@ -100,7 +101,7 @@ def __init__(
if request_retries < 2:
request_retries = 0

method_config: dict[Any, Any] = {}
method_config: dict[str, Any] = {}

if methods:
method_config["name"] = methods
Expand All @@ -120,19 +121,22 @@ def __init__(
if wait_for_ready:
method_config["waitForReady"] = wait_for_ready

service_config = {"methodConfig": [method_config]}
options = [
("grpc.service_config", json.dumps(service_config)),
]
options = {
"grpc.service_config": json.dumps({"methodConfig": [method_config]}),
}

if channel_options:
options |= channel_options

opts = [(k, v) for k, v in options.items()]
if tls_verify:
self._channel = grpc.aio.secure_channel(
host,
credentials=creds,
options=options,
options=opts,
)
else:
self._channel = grpc.aio.insecure_channel(host, options=options)
self._channel = grpc.aio.insecure_channel(host, options=opts)

async def __aenter__(self):
return self
Expand All @@ -154,6 +158,7 @@ class AsyncCerbosClient(AsyncClientBase):
timeout_secs (float): Optional request timeout in seconds (no timeout by default)
request_retries (int): Optional maximum number of retries, including the original attempt. Anything below 2 will be treated as 0 (disabled)
wait_for_ready (bool): Boolean specifying whether RPCs should wait until the connection is ready. Defaults to False
channel_options (dict[str, Any]): Optional gRPC channel options to pass on channel creation. The values need to match the expected types: https://github.com/grpc/grpc/blob/7536d8a849c0096e4c968e7730306872bb5ec674/include/grpc/impl/grpc_types.h
Example:
with AsyncCerbosClient("localhost:3593") as cerbos:
Expand All @@ -175,6 +180,7 @@ def __init__(
timeout_secs: float | None = None,
request_retries: int = 0,
wait_for_ready: bool = False,
channel_options: dict[str, Any] | None = None,
):
creds: grpc.ChannelCredentials = None
if tls_verify:
Expand Down Expand Up @@ -203,6 +209,7 @@ def __init__(
timeout_secs,
request_retries,
wait_for_ready,
channel_options,
)

self._client = svc_pb2_grpc.CerbosServiceStub(self._channel)
Expand Down Expand Up @@ -426,6 +433,7 @@ class AsyncCerbosAdminClient(AsyncClientBase):
timeout_secs (float): Optional request timeout in seconds (no timeout by default)
request_retries (int): Optional maximum number of retries, including the original attempt. Anything below 2 will be treated as 0 (disabled)
wait_for_ready (bool): Boolean specifying whether RPCs should wait until the connection is ready. Defaults to False
channel_options (dict[str, Any]): Optional gRPC channel options to pass on channel creation. The values need to match the expected types: https://github.com/grpc/grpc/blob/7536d8a849c0096e4c968e7730306872bb5ec674/include/grpc/impl/grpc_types.h
Example:
with AsyncCerbosAdminClient("localhost:3593", admin_credentials=AdminCredentials("admin", "some_password")) as cerbos:
Expand All @@ -448,6 +456,7 @@ def __init__(
timeout_secs: float | None = None,
request_retries: int = 0,
wait_for_ready: bool = False,
channel_options: dict[str, Any] | None = None,
):
admin_credentials = admin_credentials or AdminCredentials()
self._creds_metadata = admin_credentials.metadata()
Expand Down Expand Up @@ -479,6 +488,7 @@ def __init__(
timeout_secs,
request_retries,
wait_for_ready,
channel_options,
)

self._client = svc_pb2_grpc.CerbosAdminServiceStub(self._channel)
Expand Down
24 changes: 17 additions & 7 deletions cerbos/sdk/_sync/_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def __init__(
timeout_secs: float | None = None,
request_retries: int = 0,
wait_for_ready: bool = False,
channel_options: dict[str, Any] | None = None,
):
if timeout_secs and not isinstance(timeout_secs, (int, float)):
raise TypeError("timeout_secs must be a number type")
Expand All @@ -100,7 +101,7 @@ def __init__(
if request_retries < 2:
request_retries = 0

method_config: dict[Any, Any] = {}
method_config: dict[str, Any] = {}

if methods:
method_config["name"] = methods
Expand All @@ -120,19 +121,22 @@ def __init__(
if wait_for_ready:
method_config["waitForReady"] = wait_for_ready

service_config = {"methodConfig": [method_config]}
options = [
("grpc.service_config", json.dumps(service_config)),
]
options = {
"grpc.service_config": json.dumps({"methodConfig": [method_config]}),
}

if channel_options:
options |= channel_options

opts = [(k, v) for k, v in options.items()]
if tls_verify:
self._channel = grpc.secure_channel(
host,
credentials=creds,
options=options,
options=opts,
)
else:
self._channel = grpc.insecure_channel(host, options=options)
self._channel = grpc.insecure_channel(host, options=opts)

def __enter__(self):
return self
Expand All @@ -154,6 +158,7 @@ class CerbosClient(SyncClientBase):
timeout_secs (float): Optional request timeout in seconds (no timeout by default)
request_retries (int): Optional maximum number of retries, including the original attempt. Anything below 2 will be treated as 0 (disabled)
wait_for_ready (bool): Boolean specifying whether RPCs should wait until the connection is ready. Defaults to False
channel_options (dict[str, Any]): Optional gRPC channel options to pass on channel creation. The values need to match the expected types: https://github.com/grpc/grpc/blob/7536d8a849c0096e4c968e7730306872bb5ec674/include/grpc/impl/grpc_types.h
Example:
with AsyncCerbosClient("localhost:3593") as cerbos:
Expand All @@ -175,6 +180,7 @@ def __init__(
timeout_secs: float | None = None,
request_retries: int = 0,
wait_for_ready: bool = False,
channel_options: dict[str, Any] | None = None,
):
creds: grpc.ChannelCredentials = None
if tls_verify:
Expand Down Expand Up @@ -203,6 +209,7 @@ def __init__(
timeout_secs,
request_retries,
wait_for_ready,
channel_options,
)

self._client = svc_pb2_grpc.CerbosServiceStub(self._channel)
Expand Down Expand Up @@ -426,6 +433,7 @@ class CerbosAdminClient(SyncClientBase):
timeout_secs (float): Optional request timeout in seconds (no timeout by default)
request_retries (int): Optional maximum number of retries, including the original attempt. Anything below 2 will be treated as 0 (disabled)
wait_for_ready (bool): Boolean specifying whether RPCs should wait until the connection is ready. Defaults to False
channel_options (dict[str, Any]): Optional gRPC channel options to pass on channel creation. The values need to match the expected types: https://github.com/grpc/grpc/blob/7536d8a849c0096e4c968e7730306872bb5ec674/include/grpc/impl/grpc_types.h
Example:
with AsyncCerbosAdminClient("localhost:3593", admin_credentials=AdminCredentials("admin", "some_password")) as cerbos:
Expand All @@ -448,6 +456,7 @@ def __init__(
timeout_secs: float | None = None,
request_retries: int = 0,
wait_for_ready: bool = False,
channel_options: dict[str, Any] | None = None,
):
admin_credentials = admin_credentials or AdminCredentials()
self._creds_metadata = admin_credentials.metadata()
Expand Down Expand Up @@ -479,6 +488,7 @@ def __init__(
timeout_secs,
request_retries,
wait_for_ready,
channel_options,
)

self._client = svc_pb2_grpc.CerbosAdminServiceStub(self._channel)
Expand Down

0 comments on commit 75a76fc

Please sign in to comment.