Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
604aae8
feat: audit logging to /api/v1/people
benjamsf Jan 25, 2026
db4c338
chore: bump version
benjamsf Jan 25, 2026
9187cf5
feat: audit log to /api/v1/firstuser
benjamsf Jan 25, 2026
6292914
chore: update libpvarki and pyopenssl to match libpvarki 2.2.0 reqs
benjamsf Jan 31, 2026
cedce8d
feat: add temp util for request context, before we support this in li…
benjamsf Feb 1, 2026
4d3f5c6
feat: utils/auditcontext.py until libpvarki auditlogging is thoughtfu…
benjamsf Feb 1, 2026
d81ce22
feat: auditlogging to /api/v1/enrollment endpoints
benjamsf Feb 1, 2026
fb40db7
feat: utils/auditlogging to provide ecs 1.6.0 compliant fields
benjamsf Feb 1, 2026
61fcc31
fix: ecs1.6.0 conformity to fields we log
benjamsf Feb 1, 2026
0b5afd5
feat: ecs 1.6.0-style auditlog to /api/v1/firstuser
benjamsf Feb 1, 2026
d501eb5
feat: ecs 1.6.0-style auditlogging to /api/v1/people
benjamsf Feb 1, 2026
2171e09
feat: auditlogging to /api/v1/enduserpfx
benjamsf Feb 1, 2026
30c360b
fix: remove patch revision from libpvarki & pyopenssl definition
benjamsf Feb 21, 2026
3901ac8
feat: default actor to cert DN if available
benjamsf Feb 21, 2026
703636e
feat: /api/enrollment default actor to DN when mTLS auth is expected
benjamsf Feb 21, 2026
558f2e3
feat: /api/v1/people default actor to cert DN
benjamsf Feb 21, 2026
056d574
feat: auditlogging to /api/v1/token
benjamsf Feb 21, 2026
c129dee
fix: Ensure DELETE /api/v1/people responds a 404 if we try to delete …
benjamsf Feb 21, 2026
9239cc2
feat: actor from callsign on /api/v1/enrollment/invitecode/enroll suc…
benjamsf Feb 21, 2026
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
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.12.0
current_version = 1.13.0
commit = False
tag = False

Expand Down
868 changes: 441 additions & 427 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "rasenmaeher_api"
version = "1.12.0"
version = "1.13.0"
description = "python-rasenmaeher-api"
authors = [
"Aciid <703382+Aciid@users.noreply.github.com>",
Expand Down Expand Up @@ -85,8 +85,8 @@ requests = "^2.31"
multikeyjwt = "^1.0"
uvicorn = {version = "^0.20", extras = ["standard"]}
gunicorn = "^20.1"
pyopenssl = "^23.1"
libpvarki = { version="^2.0", source="nexuslocal"}
pyopenssl = "^25.3"
libpvarki = { version="^2.2", source="nexuslocal"}
openapi-readme = "^0.2"
python-multipart = ">=0.0.21,<1.0.0"
aiohttp = ">=3.11.10,<4.0"
Expand All @@ -113,7 +113,6 @@ bump2version = "^1.0"
detect-secrets = "^1.2"
httpx = ">=0.23,<1.0" # caret behaviour on 0.x is to lock to 0.x.*
types-requests = "^2.31"
types-pyopenssl = "^23.1"
async-asgi-testclient = "^1.4"
pytest-docker = "^2.0"
flaky = "^3.8"
Expand Down
2 changes: 1 addition & 1 deletion src/rasenmaeher_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""python-rasenmaeher-api"""

__version__ = "1.12.0" # NOTE Use `bump2version --config-file patch` to bump versions correctly
__version__ = "1.13.0" # NOTE Use `bump2version --config-file patch` to bump versions correctly
53 changes: 51 additions & 2 deletions src/rasenmaeher_api/web/api/enduserpfx/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import logging

from fastapi import APIRouter, HTTPException, Depends
from fastapi import APIRouter, Request, HTTPException, Depends
from fastapi.responses import FileResponse

from ....db import Person
from ..middleware.user import ValidUser
from ..utils.auditcontext import build_audit_extra
from ....rmsettings import RMSettings

router = APIRouter()
Expand All @@ -16,6 +17,7 @@
@router.get(f"/{{callsign}}_{RMSettings.singleton().deployment_name}.pem")
@router.get("/{callsign}.pem")
async def get_user_pem(
request: Request,
callsign: str,
person: Person = Depends(ValidUser(auto_error=True)),
) -> FileResponse:
Expand All @@ -27,10 +29,33 @@ async def get_user_pem(
callsign = callsign[:-4]
LOGGER.debug("PEM: Called with callsign={}".format(callsign))
if person.callsign != callsign:
LOGGER.audit( # type: ignore[attr-defined]
"Certificate download denied - callsign mismatch",
extra=build_audit_extra(
action="certificate_download",
outcome="failure",
actor=person.callsign,
target=callsign,
request=request,
error_code="CALLSIGN_MISMATCH",
certificate_type="pem",
),
)
raise HTTPException(status_code=403, detail="Callsign must match authenticated user")
# Make sure the pfx exists, this is no-op if it does
await person.create_pfx()

LOGGER.audit( # type: ignore[attr-defined]
"Certificate downloaded",
extra=build_audit_extra(
action="certificate_download",
outcome="success",
actor=person.callsign,
request=request,
certificate_type="pem",
),
)

return FileResponse(
path=person.certfile,
media_type="application/x-pem-file",
Expand All @@ -42,6 +67,7 @@ async def get_user_pem(
@router.get("/{callsign}.pfx")
@router.get("/{callsign}")
async def get_user_pfx(
request: Request,
callsign: str,
person: Person = Depends(ValidUser(auto_error=True)),
) -> FileResponse:
Expand All @@ -57,13 +83,36 @@ async def get_user_pfx(
callsign = callsign[:-4]
if callsign.endswith(".pem"):
LOGGER.debug("PFX: got .pem suffix, delegating")
return await get_user_pem(callsign, person)
return await get_user_pem(request, callsign, person)
LOGGER.debug("PFX: Called with callsign={}".format(callsign))
if person.callsign != callsign:
LOGGER.audit( # type: ignore[attr-defined]
"Certificate download denied - callsign mismatch",
extra=build_audit_extra(
action="certificate_download",
outcome="failure",
actor=person.callsign,
Copy link
Contributor

Choose a reason for hiding this comment

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

actor should be defaulted from the Certificate info header or just request context info

Copy link
Member Author

Choose a reason for hiding this comment

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

defaulted to this in utils/auditcontext.py, so when actor is not defined we check if there's cert DN to be shown. If not, unknown. In cases without cert where we should log actor (Enrolling with invitecode and downloading a cert, auth is via JWT), it is set as callsign.

With /api/v1/enrollment/invitecode/enroll, in successpaths we take actor from the callsign the enrolling user posted. Failure paths log the target but actor ends up to be nil/unknown.

target=callsign,
request=request,
error_code="CALLSIGN_MISMATCH",
certificate_type="pfx",
),
)
raise HTTPException(status_code=403, detail="Callsign must match authenticated user")
# Make sure the pfx exists, this is no-op if it does
await person.create_pfx()

LOGGER.audit( # type: ignore[attr-defined]
"Certificate downloaded",
extra=build_audit_extra(
action="certificate_download",
outcome="success",
actor=person.callsign,
request=request,
certificate_type="pfx",
),
)

return FileResponse(
path=person.pfxfile,
media_type="application/x-pkcs12",
Expand Down
Loading