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

Protect ASAB API #664

Merged
merged 8 commits into from
Jan 27, 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
7 changes: 4 additions & 3 deletions asab/api/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@

from ..web.rest.json import json_response
from ..log import LOG_NOTICE
from ..web.auth import noauth
from ..web.auth import require
from ..web.tenant import allow_no_tenant

##

L = logging.getLogger(__name__)
LOG_ACCESS_RESOURCE_ID = "asab:service:access"

##

Expand Down Expand Up @@ -86,7 +87,7 @@ def emit(self, record):
asyncio.ensure_future(self._send_ws(log_entry))


@noauth
@require(LOG_ACCESS_RESOURCE_ID)
@allow_no_tenant
async def get_logs(self, request):
"""
Expand Down Expand Up @@ -137,7 +138,7 @@ async def get_logs(self, request):
return json_response(request, self.Buffer)


@noauth
@require(LOG_ACCESS_RESOURCE_ID)
@allow_no_tenant
async def ws(self, request):
'''
Expand Down
10 changes: 5 additions & 5 deletions asab/api/web_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from .. import Config
from ..web.rest import json_response
from ..web.auth import noauth
from ..web.auth import noauth, require_superuser
from ..web.tenant import allow_no_tenant


Expand All @@ -15,8 +15,8 @@ def __init__(self, api_svc, webapp, log_handler):
self.ApiService = api_svc

# Add routes
webapp.router.add_get("/asab/v1/environ", self.environ)
webapp.router.add_get("/asab/v1/config", self.config)
# webapp.router.add_get("/asab/v1/environ", self.environ) # Disabled for security reasons
# webapp.router.add_get("/asab/v1/config", self.config) # Disabled for security reasons

webapp.router.add_get("/asab/v1/logs", log_handler.get_logs)
webapp.router.add_get("/asab/v1/logws", log_handler.ws)
Expand Down Expand Up @@ -78,7 +78,7 @@ async def manifest(self, request):
return json_response(request, self.ApiService.Manifest)


@noauth
@require_superuser
@allow_no_tenant
async def environ(self, request):
"""
Expand Down Expand Up @@ -110,7 +110,7 @@ async def environ(self, request):
return json_response(request, dict(os.environ))


@noauth
@require_superuser
@allow_no_tenant
async def config(self, request):
"""
Expand Down
1 change: 1 addition & 0 deletions asab/metrics/web_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ def __init__(self, metrics_svc, webapp):
self.App = self.MetricsService.App

# Add routes
# TODO: Add access control (asab:service:access). Test with Prometheus first.
webapp.router.add_get("/asab/v1/metrics", self.metrics)
webapp.router.add_get("/asab/v1/watch_metrics", self.watch)
webapp.router.add_get("/asab/v1/metrics.json", self.metrics_json)
Expand Down
3 changes: 2 additions & 1 deletion asab/web/auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from .decorator import require, noauth
from .decorator import require, require_superuser, noauth
from .service import AuthService
from .authorization import Authorization

__all__ = (
"AuthService",
"Authorization",
"require",
"require_superuser",
"noauth",
)
27 changes: 27 additions & 0 deletions asab/web/auth/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,33 @@ async def _require_resource_access_wrapper(*args, **kwargs):
return _require_resource_access_decorator


def require_superuser(handler):
"""
Require that the request have authorized access to the superuser resource.
Requests without superuser access result in AccessDeniedError and consequently in an HTTP 403 response.

Examples:

```python
@asab.web.auth.require_superuser
async def get_confidential_info(self, request):
data = await self.service.get_confidential_info()
return asab.web.rest.json_response(request, data)
```
"""
@functools.wraps(handler)
async def _require_superuser_access_wrapper(*args, **kwargs):
authz = Authz.get()
if authz is None:
raise AccessDeniedError()

authz.require_superuser_access()

return await handler(*args, **kwargs)

return _require_superuser_access_wrapper


def noauth(handler):
"""
Exempt the decorated handler from authentication and authorization.
Expand Down
Loading