feat(observability): add structured audit log channel for API and webhook events

Introduce a dedicated `audit` log channel (daily rotation, configurable retention via
LOG_AUDIT_DAYS) and a small `auditLog()` / `auditLogWebhookFailure()` helper used to
record state-changing API operations and webhook events.

Instrumented:

- API mutation endpoints (create / update / delete / start / stop / restart) across
  applications, services, databases (incl. backups, env vars, storage), servers,
  projects + environments, scheduled tasks, private keys, GitHub apps, cloud provider
  tokens, Hetzner server provisioning, instance enable/disable.
- Webhook signature verification outcomes for GitHub, GitLab, Bitbucket, Gitea and
  Stripe, plus the Sentinel push endpoint.
- Authentication and authorization outcomes via the global exception handler and
  the `ApiAbility` middleware (unauthenticated, ability-denied, policy-denied).

The helper is wrapped in try/catch so logging failures never affect the request
path. Successful operations log at `info`; suspicious/denied requests log at
`warning`. Operators wanting a failures-only feed can set `LOG_AUDIT_LEVEL=warning`.

Includes a feature test suite covering the helper, the webhook providers and the
new auth/authorization log paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Andras Bacsai
2026-04-28 14:50:37 +02:00
parent 923525a310
commit a2096c6f68
25 changed files with 1438 additions and 42 deletions
@@ -271,6 +271,12 @@ class GithubController extends Controller
$githubApp = GithubApp::create($payload);
auditLog('api.github_app.created', [
'team_id' => $teamId,
'github_app_uuid' => $githubApp->uuid,
'github_app_name' => $githubApp->name,
]);
return response()->json($githubApp, 201);
} catch (\Throwable $e) {
return handleError($e);
@@ -650,6 +656,13 @@ class GithubController extends Controller
// Update the GitHub app
$githubApp->update($payload);
auditLog('api.github_app.updated', [
'team_id' => $teamId,
'github_app_uuid' => $githubApp->uuid,
'github_app_name' => $githubApp->name,
'changed_fields' => array_values(array_diff($allowedFields, ['client_secret', 'webhook_secret', 'private_key_uuid'])),
]);
return response()->json([
'message' => 'GitHub app updated successfully',
'data' => $githubApp,
@@ -734,8 +747,16 @@ class GithubController extends Controller
], 409);
}
$deletedUuid = $githubApp->uuid;
$deletedName = $githubApp->name;
$githubApp->delete();
auditLog('api.github_app.deleted', [
'team_id' => $teamId,
'github_app_uuid' => $deletedUuid,
'github_app_name' => $deletedName,
]);
return response()->json([
'message' => 'GitHub app deleted successfully',
]);