Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
9cc85f5
Merge branch 'main' into auditlog-to-rmapi
benjamsf Feb 22, 2026
f484a28
chore: update deps
benjamsf Feb 22, 2026
5922737
fix: use x509 parser to grab DN from presented cert, to the actor field
benjamsf Feb 22, 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.2
current_version = 1.13.0
commit = False
tag = False

Expand Down
26 changes: 5 additions & 21 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.2"
version = "1.13.0"
description = "python-rasenmaeher-api"
authors = [
"Aciid <703382+Aciid@users.noreply.github.com>",
Expand Down Expand Up @@ -86,8 +86,8 @@ requests = "^2.31"
multikeyjwt = "^1.0"
uvicorn = {version = "^0.20", extras = ["standard"]}
gunicorn = "^20.1"
pyopenssl = ">=25.3"
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 @@ -114,7 +114,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.2" # 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,
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