Skip to content

Adding security API #63

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

Merged
merged 1 commit into from
Aug 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 78 additions & 0 deletions arangoasync/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@
PermissionListError,
PermissionResetError,
PermissionUpdateError,
ServerEncryptionError,
ServerStatusError,
ServerTLSError,
ServerTLSReloadError,
ServerVersionError,
TaskCreateError,
TaskDeleteError,
Expand Down Expand Up @@ -2072,6 +2075,81 @@ def response_handler(resp: Response) -> Json:

return await self._executor.execute(request, response_handler)

async def tls(self) -> Result[Json]:
"""Return TLS data (keyfile, clientCA).

This API requires authentication.

Returns:
dict: dict containing the following components:
- keyfile: Information about the key file.
- clientCA: Information about the Certificate Authority (CA) for client certificate verification.

Raises:
ServerTLSError: If the operation fails.

References:
- `get-the-tls-data <https://docs.arangodb.com/stable/develop/http-api/security/#get-the-tls-data>`__
""" # noqa: E501
request = Request(method=Method.GET, endpoint="/_admin/server/tls")

def response_handler(resp: Response) -> Json:
if not resp.is_success:
raise ServerTLSError(resp, request)
result: Json = self.deserializer.loads(resp.raw_body)["result"]
return result

return await self._executor.execute(request, response_handler)

async def reload_tls(self) -> Result[Json]:
"""Reload TLS data (keyfile, clientCA).

This is a protected API and can only be executed with superuser rights.

Returns:
dict: New TLS data.

Raises:
ServerTLSReloadError: If the operation fails.

References:
- `reload-the-tls-data <https://docs.arangodb.com/stable/develop/http-api/security/#reload-the-tls-data>`__
""" # noqa: E501
request = Request(method=Method.POST, endpoint="/_admin/server/tls")

def response_handler(resp: Response) -> Json:
if not resp.is_success:
raise ServerTLSReloadError(resp, request)
result: Json = self.deserializer.loads(resp.raw_body)["result"]
return result

return await self._executor.execute(request, response_handler)

async def encryption(self) -> Result[Json]:
"""Rotate the user-supplied keys for encryption.

This is a protected API and can only be executed with superuser rights.
This API is not available on Coordinator nodes.

Returns:
dict: Encryption keys.

Raises:
ServerEncryptionError: If the operation fails.

References:
- `rotate-the-encryption-keys <https://docs.arangodb.com/stable/develop/http-api/security/#rotate-the-encryption-keys>`__
""" # noqa: E501
request = Request(method=Method.POST, endpoint="/_admin/server/encryption")

def response_handler(resp: Response) -> Json:
if not resp.is_success:
raise ServerEncryptionError(resp, request)
result: Json = self.deserializer.loads(resp.raw_body)["result"]
return result

return await self._executor.execute(request, response_handler)

async def list_transactions(self) -> Result[Jsons]:
"""List all currently running stream transactions.

Expand Down
12 changes: 12 additions & 0 deletions arangoasync/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@ class SerializationError(ArangoClientError):
"""Failed to serialize the request."""


class ServerEncryptionError(ArangoServerError):
"""Failed to reload user-defined encryption keys."""


class ServerConnectionError(ArangoServerError):
"""Failed to connect to ArangoDB server."""

Expand All @@ -443,6 +447,14 @@ class ServerStatusError(ArangoServerError):
"""Failed to retrieve server status."""


class ServerTLSError(ArangoServerError):
"""Failed to retrieve TLS data."""


class ServerTLSReloadError(ArangoServerError):
"""Failed to reload TLS."""


class ServerVersionError(ArangoServerError):
"""Failed to retrieve server version."""

Expand Down
22 changes: 22 additions & 0 deletions docs/certificates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,25 @@ Use a client certificate chain

If you want to have fine-grained control over the HTTP connection, you should define
your HTTP client as described in the :ref:`HTTP` section.

Security features
=================

See the `ArangoDB Manual`_ for more information on security features.

**Example:**

.. code-block:: python

async with ArangoClient(hosts=url) as client:
db = await client.db(
sys_db_name, auth_method="superuser", token=token, verify=True
)

# Get TLS data
tls = await db.tls()

# Reload TLS data
tls = await db.reload_tls()

.. _ArangoDB Manual: https://docs.arangodb.com/stable/develop/http-api/security/
2 changes: 1 addition & 1 deletion docs/migration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Coming from python-arango
-------------------------

Generally, migrating from `python-arango`_ should be a smooth transition. For the most part, the API is similar,
but there are a few things to note._
but there are a few things to note.

Helpers
=======
Expand Down
14 changes: 14 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from arangoasync.auth import JwtToken
from arangoasync.client import ArangoClient
from arangoasync.compression import DefaultCompressionManager
from arangoasync.exceptions import ServerEncryptionError
from arangoasync.http import DefaultHTTPClient
from arangoasync.resolver import DefaultHostResolver, RoundRobinHostResolver
from arangoasync.version import __version__
Expand Down Expand Up @@ -131,6 +132,19 @@ async def test_client_jwt_superuser_auth(
await db.jwt_secrets()
await db.reload_jwt_secrets()

# Get TLS data
tls = await db.tls()
assert isinstance(tls, dict)

# Reload TLS data
tls = await db.reload_tls()
assert isinstance(tls, dict)

# Rotate
with pytest.raises(ServerEncryptionError):
# Not allowed on coordinators
await db.encryption()

# token missing
async with ArangoClient(hosts=url) as client:
with pytest.raises(ValueError):
Expand Down
Loading