Skip to content

DRAFT - feature: Add OWASP Logging#1029

Draft
ajzobro wants to merge 3 commits intomainfrom
add-owasp-logging
Draft

DRAFT - feature: Add OWASP Logging#1029
ajzobro wants to merge 3 commits intomainfrom
add-owasp-logging

Conversation

@ajzobro
Copy link
Copy Markdown
Collaborator

@ajzobro ajzobro commented Apr 23, 2026

Description

Resolved issues

Documentation

Web service API changes

Tests

@ajzobro ajzobro requested a review from Copilot April 23, 2026 22:12
@ajzobro ajzobro changed the title feature: Add OWASP Logging DRAFT - feature: Add OWASP Logging Apr 23, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 23, 2026

Codecov Report

❌ Patch coverage is 70.96774% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.18%. Comparing base (426436e) to head (0861f44).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1029      +/-   ##
==========================================
- Coverage   77.21%   77.18%   -0.04%     
==========================================
  Files         116      118       +2     
  Lines       12065    12114      +49     
  Branches      996      997       +1     
==========================================
+ Hits         9316     9350      +34     
- Misses       2520     2535      +15     
  Partials      229      229              
Flag Coverage Δ *Carryforward flag
agent 75.78% <ø> (ø)
cli 91.80% <ø> (ø) Carriedforward from 426436e
device 63.34% <ø> (ø) Carriedforward from 426436e
server 86.49% <70.96%> (-0.43%) ⬇️

*This pull request uses carry forward flags. Click here to find out more.

Components Coverage Δ
Agent 75.78% <ø> (ø)
CLI 91.80% <ø> (ø)
Common ∅ <ø> (∅)
Device Connectors 63.34% <ø> (ø)
Server 86.49% <70.96%> (-0.43%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces OWASP-style security/audit logging into the Testflinger server, wiring OWASP log events into authentication/authorization flows and client-permission management, and adds tests to validate key auth-related events.

Changes:

  • Add owasp-logger (and related dependencies) and regenerate uv.lock.
  • Introduce a testflinger.owasp.OWASPLogger wrapper and attach an OWASP logger instance to the Flask app for use across request handlers.
  • Emit OWASP audit events for authn/authz and client permission create/update/delete, plus add unit tests for core authn/authz logging.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
server/pyproject.toml Adds owasp-logger dependency (currently with otel extra).
server/uv.lock Lockfile updates for new deps; includes some dependency downgrades.
server/src/testflinger/owasp/init.py Adds OWASP logger wrapper + request metadata helper + OTELResource stub.
server/src/testflinger/owasp/views.py Adds stub OWASP views module (no active endpoints yet).
server/src/testflinger/application.py Attaches OWASP logger to the Flask app and uses it for exception handlers.
server/src/testflinger/api/auth.py Logs authz failures and normalizes role values to ServerRoles.
server/src/testflinger/api/v1.py Logs authn events on token issuance/revocation and user management events.
server/src/testflinger/api/schemas.py Updates Marshmallow boolean default handling for NoProvisionData.
server/src/testflinger/secrets/init.py Wraps secrets-store logger with OWASP logger.
server/tests/test_owasp_logging.py New tests validating OWASP authn/authz logging events.
common/src/testflinger_common/enums.py Changes ServerRoles.__str__ to return the raw value.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1057 to +1061
# Log token revocation
target_client_id = token_entry.get("client_id", "unknown")
current_app.owasp_logger.authn_token_revoked(
userid=target_client_id,
tokenid=token[:16] + "...", # Log first 16 chars of token for tracking
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

authn_token_revoked logs a portion of the refresh token (token[:16] + '...'). Even partial tokens are sensitive credentials and shouldn’t be written to logs. Prefer logging a non-sensitive identifier (e.g., a hash/fingerprint of the token, the DB document id, or just the client_id) to allow correlation without credential exposure.

Suggested change
# Log token revocation
target_client_id = token_entry.get("client_id", "unknown")
current_app.owasp_logger.authn_token_revoked(
userid=target_client_id,
tokenid=token[:16] + "...", # Log first 16 chars of token for tracking
# Log token revocation without exposing token material
target_client_id = token_entry.get("client_id", "unknown")
token_log_id = str(token_entry.get("_id") or target_client_id)
current_app.owasp_logger.authn_token_revoked(
userid=target_client_id,
tokenid=token_log_id,

Copilot uses AI. Check for mistakes.
Comment on lines +247 to +253
def test_authn_token_revoked(self, mongo_app_with_permissions, caplog):
"""Verify authn_token_revoked is logged when refresh token
is revoked.
"""
import os

os.environ["JWT_SIGNING_KEY"] = "my_secret_key"
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

This test does an in-function import os and then sets os.environ["JWT_SIGNING_KEY"] directly. Consider removing the redundant import and using monkeypatch.setenv(...) so the env var is reliably restored even if the test fails partway through.

Suggested change
def test_authn_token_revoked(self, mongo_app_with_permissions, caplog):
"""Verify authn_token_revoked is logged when refresh token
is revoked.
"""
import os
os.environ["JWT_SIGNING_KEY"] = "my_secret_key"
def test_authn_token_revoked(
self, mongo_app_with_permissions, caplog, monkeypatch
):
"""Verify authn_token_revoked is logged when refresh token
is revoked.
"""
monkeypatch.setenv("JWT_SIGNING_KEY", "my_secret_key")

Copilot uses AI. Check for mistakes.
Comment thread server/pyproject.toml
"gunicorn>=23.0.0",
"gevent>=24.2.1",
"hvac>=2.3.0",
"owasp-logger[otel]>=0.1.5",
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

The dependency is added as owasp-logger[otel], which pulls in OpenTelemetry SDK/instrumentation dependencies, but this PR’s code doesn’t appear to use OTEL yet (and the testflinger.owasp module describes OTEL as future work). Consider depending on owasp-logger without the otel extra for now, or making the OTEL extra optional via a project extra to avoid increasing the default dependency footprint.

Suggested change
"owasp-logger[otel]>=0.1.5",
"owasp-logger>=0.1.5",

Copilot uses AI. Check for mistakes.
role = permissions.get("role", ServerRoles.CONTRIBUTOR)
# Convert role string to enum if needed
if isinstance(role, str):
g.role = ServerRoles(role)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

Converting a stored role string via ServerRoles(role) can raise ValueError if the DB contains an unexpected value (e.g. legacy data/corruption), turning an auth failure into a 500. Wrap this conversion in try/except and fall back to a safe default (or abort with 403) when the role isn’t recognized.

Suggested change
g.role = ServerRoles(role)
try:
g.role = ServerRoles(role)
except ValueError:
current_app.owasp_logger.authz_fail(
userid=permissions.get("client_id", "unknown"),
resource=request.path,
description=(
f"Authorization denied: client "
f"{permissions.get('client_id', 'unknown')} "
f"provided unrecognized role {role!r}"
),
**OWASPLogger.get_request_metadata(request),
)
abort(
HTTPStatus.FORBIDDEN,
"Authentication contains an unrecognized role",
)

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +71
"""Initialize OTEL resource from Flask context."""
try:
version = importlib.metadata.version("testflinger")
except importlib.metadata.PackageNotFoundError:
version = "devel"
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

OTELResource references importlib.metadata but this module only does import importlib. Submodules aren’t imported automatically, so importlib.metadata will raise AttributeError when executed. Import importlib.metadata explicitly (or from importlib import metadata) and update these references.

Copilot uses AI. Check for mistakes.
"""Schema for the `provision_data` section of a no-provision job."""

skip = fields.Boolean(required=False, default=True)
skip = fields.Boolean(required=False, dump_default=True)
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

NoProvisionData.skip previously defaulted to True when loading request payloads. Switching to dump_default=True only affects serialization; it won’t apply a default on input deserialization. Use load_default=True (and optionally keep dump_default=True) to preserve the prior behavior for API requests.

Suggested change
skip = fields.Boolean(required=False, dump_default=True)
skip = fields.Boolean(required=False, load_default=True, dump_default=True)

Copilot uses AI. Check for mistakes.
Comment on lines +1249 to +1252
description=(
f"Admin {g.client_id} updated permissions for "
f"client {client_id}: role={new_role}"
),
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

user_updated / user_created logs interpolate new_role, but new_role can be None when the request didn’t include a role change. This can make audit logs misleading (e.g. “role=None”). Consider logging the effective role (new if provided else current) and/or only including fields actually changed.

Copilot uses AI. Check for mistakes.
Comment on lines +1242 to +1244
current_app.owasp_logger.user_updated(
userid=g.client_id,
onuserid=client_id,
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

New OWASP audit events are emitted for client permission create/update/delete (user_created, user_updated, user_deleted), but the new/updated tests only cover authn/authz events. Since logging is the main feature here, consider adding assertions in existing client-permissions endpoint tests to verify these events are logged as expected.

Copilot uses AI. Check for mistakes.
Comment on lines +143 to +149
self, endpoint, method, test_role, mongo_app, caplog
):
"""
Verify authz_fail is logged when authenticated user with
insufficient role attempts to access protected endpoint.
"""
os.environ["JWT_SIGNING_KEY"] = "my_secret_key"
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

This test mutates os.environ["JWT_SIGNING_KEY"] directly, which can leak state to other tests if an assertion fails. Prefer using the monkeypatch fixture (or a context manager) so the environment change is automatically reverted.

Suggested change
self, endpoint, method, test_role, mongo_app, caplog
):
"""
Verify authz_fail is logged when authenticated user with
insufficient role attempts to access protected endpoint.
"""
os.environ["JWT_SIGNING_KEY"] = "my_secret_key"
self,
endpoint,
method,
test_role,
mongo_app,
caplog,
monkeypatch,
):
"""
Verify authz_fail is logged when authenticated user with
insufficient role attempts to access protected endpoint.
"""
monkeypatch.setenv("JWT_SIGNING_KEY", "my_secret_key")

Copilot uses AI. Check for mistakes.
"""Schema for the `provision_data` section of a no-provision job."""

skip = fields.Boolean(required=False, default=True)
skip = fields.Boolean(required=False, dump_default=True)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@rene-oromtz this is due to the marshmallow version

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants