Skip to content
Snippets Groups Projects
Verified Commit 519e8d94 authored by Daniel Göbel's avatar Daniel Göbel
Browse files

Communicate directly with OPA instead of Auth service

#53
parent 4825e755
No related branches found
No related tags found
No related merge requests found
...@@ -9,12 +9,9 @@ variables: ...@@ -9,12 +9,9 @@ variables:
USER_CEPH_ACCESS_KEY: "" USER_CEPH_ACCESS_KEY: ""
USER_CEPH_SECRET_KEY: "" USER_CEPH_SECRET_KEY: ""
BUCKET_CEPH_USERNAME: "" BUCKET_CEPH_USERNAME: ""
OIDC_CLIENT_SECRET: ""
OIDC_CLIENT_ID: ""
OIDC_BASE_URI: "http://127.0.0.1:8000"
FF_NETWORK_PER_BUILD: 1 FF_NETWORK_PER_BUILD: 1
PUBLIC_KEY_VALUE: "empty" PUBLIC_KEY_VALUE: "empty"
AUTHZ_ENDPOINT: "http://127.0.0.1:8000" OPA_URI: "http://127.0.0.1:8181"
cache: cache:
paths: paths:
......
...@@ -35,15 +35,16 @@ user-friendly manner. 👍 ...@@ -35,15 +35,16 @@ user-friendly manner. 👍
| `USER_CEPH_ACCESS_KEY` | unset | \<access key> | Access key for the Ceph Object Gateway user with `user:*` privileges | | `USER_CEPH_ACCESS_KEY` | unset | \<access key> | Access key for the Ceph Object Gateway user with `user:*` privileges |
| `USER_CEPH_SECRET_KEY` | unset | \<secret key> | Secret key for the Ceph Object Gateway user with `user:*` privileges. | | `USER_CEPH_SECRET_KEY` | unset | \<secret key> | Secret key for the Ceph Object Gateway user with `user:*` privileges. |
| `PUBLIC_KEY_VALUE` / `PUBLIC_KEY_FILE` | randomly generated | Public Key / Path to Public Key | Public part of RSA Key in PEM format to verify JWTs | | `PUBLIC_KEY_VALUE` / `PUBLIC_KEY_FILE` | randomly generated | Public Key / Path to Public Key | Public part of RSA Key in PEM format to verify JWTs |
| `AUTHZ_ENDPOINT` | unset | HTTP URL | HTTP URL to ask the Auth Service for Authorization | | `OPA_URI` | unset | HTTP URL | HTTP URL of the OPA service |
### Optional Variables ### Optional Variables
| Variable | Default | Value | Description | | Variable | Default | Value | Description |
|-----------------------------|---------|-----------------------------|----------------------------------------------------------------| |-----------------------------|----------------------|-----------------------------|----------------------------------------------------------------|
| `API_PREFIX` | `/api` | URL path | Prefix before every URL path | | `API_PREFIX` | `/api` | URL path | Prefix before every URL path |
| `BACKEND_CORS_ORIGINS` | `[]` | json formatted list of urls | List of valid CORS origins | | `BACKEND_CORS_ORIGINS` | `[]` | json formatted list of urls | List of valid CORS origins |
| `SQLALCHEMY_VERBOSE_LOGGER` | `false` | `<"true"&#x7c;"false">` | Enables verbose SQL output.<br>Should be `false` in production | | `SQLALCHEMY_VERBOSE_LOGGER` | `false` | `<"true"&#x7c;"false">` | Enables verbose SQL output.<br>Should be `false` in production |
| `OPA_POLICY_PATH` | `/clowm/authz/allow` | URL path | Path to the OPA Policy for Authorization |
## Getting started ## Getting started
This service depends on multiple other services. See [DEVELOPING.md](DEVELOPING.md) how to set these up for developing This service depends on multiple other services. See [DEVELOPING.md](DEVELOPING.md) how to set these up for developing
......
...@@ -134,7 +134,7 @@ class AuthorizationDependency: ...@@ -134,7 +134,7 @@ class AuthorizationDependency:
def __call__( def __call__(
self, self,
token: HTTPAuthorizationCredentials = Depends(bearer_token), token: JWT = Depends(decode_bearer_token),
client: AsyncClient = Depends(get_httpx_client), client: AsyncClient = Depends(get_httpx_client),
) -> Callable[[str], Awaitable[AuthzResponse]]: ) -> Callable[[str], Awaitable[AuthzResponse]]:
""" """
...@@ -142,8 +142,8 @@ class AuthorizationDependency: ...@@ -142,8 +142,8 @@ class AuthorizationDependency:
Parameters Parameters
---------- ----------
token : fastapi.security.http.HTTPAuthorizationCredentials token : app.schemas.security.JWT
Bearer token sent with the HTTP request. Dependency Injection. The verified and decoded JWT. Dependency Injection.
client : httpx.AsyncClient client : httpx.AsyncClient
HTTP Client with an open connection. Dependency Injection. HTTP Client with an open connection. Dependency Injection.
...@@ -154,7 +154,7 @@ class AuthorizationDependency: ...@@ -154,7 +154,7 @@ class AuthorizationDependency:
""" """
async def authorization_wrapper(operation: str) -> AuthzResponse: async def authorization_wrapper(operation: str) -> AuthzResponse:
params = AuthzRequest(operation=operation, resource=self.resource, token=token.credentials) params = AuthzRequest(operation=operation, resource=self.resource, uid=token.sub)
return await request_authorization(request_params=params, client=client) return await request_authorization(request_params=params, client=client)
return authorization_wrapper return authorization_wrapper
......
...@@ -87,7 +87,8 @@ class Settings(BaseSettings): ...@@ -87,7 +87,8 @@ class Settings(BaseSettings):
BUCKET_CEPH_USERNAME: str = Field( BUCKET_CEPH_USERNAME: str = Field(
..., description="ID of the user in ceph who owns all the buckets. Owner of 'BUCKET_CEPH_ACCESS_KEY'" ..., description="ID of the user in ceph who owns all the buckets. Owner of 'BUCKET_CEPH_ACCESS_KEY'"
) )
AUTHZ_ENDPOINT: AnyHttpUrl = Field(..., description="Endpoint of the CloWM Auth service to authorize requests") OPA_URI: AnyHttpUrl = Field(..., description="URI of the OPA Service")
OPA_POLICY_PATH: str = Field("/clowm/authz/allow", description="Path to the OPA Policy for Authorization")
class Config: class Config:
case_sensitive = True case_sensitive = True
......
...@@ -53,7 +53,10 @@ async def request_authorization(request_params: AuthzRequest, client: AsyncClien ...@@ -53,7 +53,10 @@ async def request_authorization(request_params: AuthzRequest, client: AsyncClien
response : app.schemas.security.AuthzResponse response : app.schemas.security.AuthzResponse
Response by the Auth service about the authorization request Response by the Auth service about the authorization request
""" """
response = await client.post(settings.AUTHZ_ENDPOINT, json=request_params.dict()) response = await client.post(
f"{settings.OPA_URI}/v1/data{settings.OPA_POLICY_PATH}", json={"input": request_params.dict()}
)
parsed_response = AuthzResponse(**response.json()) parsed_response = AuthzResponse(**response.json())
if not parsed_response.result: # pragma: no cover if not parsed_response.result: # pragma: no cover
raise HTTPException( raise HTTPException(
......
...@@ -17,7 +17,7 @@ class AuthzResponse(BaseModel): ...@@ -17,7 +17,7 @@ class AuthzResponse(BaseModel):
class AuthzRequest(BaseModel): class AuthzRequest(BaseModel):
"""Schema for a Request to OPA""" """Schema for a Request to OPA"""
token: str = Field(..., description="JWT Token send by user in request") uid: str = Field(..., description="UID of user", example="28c5353b8bb34984a8bd4169ba94c606")
operation: str = Field(..., description="Operation the user wants to perform", example="read") operation: str = Field(..., description="Operation the user wants to perform", example="read")
resource: str = Field(..., description="Resource the operation should be performed on", example="bucket") resource: str = Field(..., description="Resource the operation should be performed on", example="bucket")
......
...@@ -73,7 +73,7 @@ async def client(mock_rgw_admin: MockRGWAdmin, mock_s3_service: MockS3ServiceRes ...@@ -73,7 +73,7 @@ async def client(mock_rgw_admin: MockRGWAdmin, mock_s3_service: MockS3ServiceRes
async def get_mock_httpx_client() -> AsyncGenerator[httpx.AsyncClient, None]: async def get_mock_httpx_client() -> AsyncGenerator[httpx.AsyncClient, None]:
def mock_request_handler(request: httpx.Request) -> httpx.Response: def mock_request_handler(request: httpx.Request) -> httpx.Response:
response_body = {} response_body = {}
if request.url == settings.AUTHZ_ENDPOINT: if str(request.url).startswith(settings.OPA_URI):
response_body = AuthzResponse( response_body = AuthzResponse(
result=not request_admin_permission(request), decision_id=str(uuid4()) result=not request_admin_permission(request), decision_id=str(uuid4())
).dict() ).dict()
......
...@@ -57,7 +57,7 @@ def json_datetime_converter(obj: Any) -> str | None: ...@@ -57,7 +57,7 @@ def json_datetime_converter(obj: Any) -> str | None:
def request_admin_permission(request: httpx.Request) -> bool: def request_admin_permission(request: httpx.Request) -> bool:
""" """
Helper function to determine if the authorization request needs the 'administrator' role. Rudimentary helper function to determine if the authorization request needs the 'administrator' role.
Parameters Parameters
---------- ----------
...@@ -69,7 +69,7 @@ def request_admin_permission(request: httpx.Request) -> bool: ...@@ -69,7 +69,7 @@ def request_admin_permission(request: httpx.Request) -> bool:
decision : bool decision : bool
Flag if the request needs the 'administrator' role Flag if the request needs the 'administrator' role
""" """
request_body: dict[str, str] = eval(request.content.decode("utf-8")) request_body: dict[str, str] = eval(request.content.decode("utf-8"))["input"]
operation = request_body["operation"] operation = request_body["operation"]
checks = "any" in operation checks = "any" in operation
if "bucket_permission" in request_body["resource"]: if "bucket_permission" in request_body["resource"]:
......
...@@ -5,7 +5,7 @@ pytest-cov>=4.0.0,<4.1.0 ...@@ -5,7 +5,7 @@ pytest-cov>=4.0.0,<4.1.0
coverage[toml]>=7.2.0,<7.3.0 coverage[toml]>=7.2.0,<7.3.0
# Linters # Linters
flake8>=6.0.0,<6.1.0 flake8>=6.0.0,<6.1.0
autoflake>=2.0.0,<2.1.0 autoflake>=2.1.0,<2.2.0
black>=23.03.0,<23.04.0 black>=23.03.0,<23.04.0
isort>=5.12.0,<5.13.0 isort>=5.12.0,<5.13.0
mypy>=1.2.0,<1.3.0 mypy>=1.2.0,<1.3.0
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment