Skip to content

feat(stac): policy enforcement point proof of concept#569

Merged
botanical merged 34 commits intodevelopfrom
mt-uma/pep-poc
Mar 9, 2026
Merged

feat(stac): policy enforcement point proof of concept#569
botanical merged 34 commits intodevelopfrom
mt-uma/pep-poc

Conversation

@botanical
Copy link
Member

@botanical botanical commented Feb 14, 2026

Issue

#557
#558

What?

  • Creates proof of concept Policy Enforcement Point on STAC and Ingest API's POST /collections endpoints
  • Handles writes for tenantless collections for users who are developers but do not belong to any tenant group

Testing?

API Test Case Expected Result Actual Result
STAC User is an Admin of Tenant 1 Can POST to tenant1
STAC User is an Editor of Tenant 1 Can POST to tenant1
STAC User is NOT an Admin of Tenant 1, and NOT an editor Can't POST to tenant1
Ingest User is an Admin of Tenant 1 Can POST to tenant1
Ingest User is an Editor of Tenant 1 Can POST to tenant1
Ingest User is NOT an Admin of Tenant 1, and NOT an editor Can't POST to tenant1
STAC User is an Admin of Tenant 2 Can POST to tenant2
STAC User is an Editor of Tenant 2 Can POST to tenant2
STAC User is NOT an Admin of Tenant 2, and NOT an editor of Tenant 2 Can't POST to tenant2
Ingest User is an Admin of Tenant 2 Can POST to tenant2
Ingest User is an Editor of Tenant 2 Can POST to tenant2
Ingest User is NOT an Admin of Tenant 2, and NOT an editor of Tenant 2 Can't POST to tenant2
STAC User is an Admin of Tenant 3 Can POST to tenant3
STAC User is an Editor of Tenant 3 Can POST to tenant3
STAC User is NOT an Admin of Tenant 3, and NOT an editor of Tenant 3 Can't POST to tenant3
Ingest User is an Admin of Tenant 3 Can POST to tenant3
Ingest User is an Editor of Tenant 3 Can POST to tenant3
Ingest User is NOT an Admin of Tenant 3, and NOT an editor of Tenant 3 Can't POST to tenant3
STAC User is NOT a part of any tenancy but is a developer Can post a public "tenant-less" collection
Ingest User is NOT a part of any tenancy but is a developer Can post a public "tenant-less" collection

@botanical botanical marked this pull request as ready for review February 19, 2026 23:19
return None


async def extract_ingest_resource_id(request: Request) -> Optional[str]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have asked this in the resource extractor PR, sorry...do we need to consider the ingest api /ingestions/* endpoints POST, PATCH and DELETE?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries @smohiudd. Yes, eventually to cover the ingestions endpoints but not all of them. We need to cover POST, but I think PATCH only updates the status and message right?

Copy link
Collaborator

@ividito ividito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple questions and very minor suggestions. It all works as intended AFAIK. Ran most tests locally and also tried a few requests to SIT.

See https://www.keycloak.org/docs/latest/authorization_services/#_service_rpt_overview
"""
for permission in permissions:
rsname = permission.get("rsname") or permission.get("resource_id")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unclear on why rsid isn't relevant to check anymore

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's a good question! Basically we're not using rsid because the RPT puts the name in rsname (e.g. "stac:item:faketenant2:*") and the UUID in rsid (and sometimes in resource_id). You can check this yourself by requesting an RPT and introspecting it but basically it will show something like

{
            "scopes": [
                "read"
            ],
            "rsid": "some-uuid",
            "rsname": "stac:item:faketenant2:*",
            "resource_id": "some-uuid",
            "resource_scopes": [
                "read"
            ]
        }

We need the name to match and to parse type/tenant, so we use rsname (and resource_id when it’s the name). rsid only ever gives us the UUID which we don't use. I guess really we don't need resource_id but I had that as a fallback because I wasn't sure where keycloak would put the resource name.

"Access token is expired or invalid. Please re-authenticate."
) from error
if error.response.status_code == 403:
return False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about raising another error (PermissionDeniedError?) here and catching it in the middleware? I think the return False/raise Error pattern is a bit confusing, this way we could replace https://github.com/NASA-IMPACT/veda-backend/pull/569/changes#diff-4857746bfd90942dbb37adfab0448bb3a8c3683230e3fadf002e8e89b615445cR187-R202 with another except block

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think your suggestion makes sense! The bool/error return is confusing, sorry about that.
2176ff9 What do you think of something like this?

)
from src.extension import TiTilerExtension
from stac_auth_proxy import configure_app
from veda_auth.pep_middleware import PEPMiddleware
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: should we put this import in the same conditional that instantiates it?

)
else:
logger.info(
"PEP middleware disabled, openid_url=%s, secret_name=%s",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"PEP middleware disabled, openid_url=%s, secret_name=%s",
"PEP middleware disabled, openid_url=%s, secret_name=%s",

resource_id=resource_id,
scope=scope,
)
except TokenError as e:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the structured and comprehensive error responses!

@botanical botanical merged commit 1f45cb4 into develop Mar 9, 2026
4 checks passed
@botanical botanical deleted the mt-uma/pep-poc branch March 9, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants