mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-06 02:23:03 +00:00
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:
co-authored by
Claude Opus 4.7
parent
923525a310
commit
a2096c6f68
@@ -1309,6 +1309,15 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
auditLog('api.application.created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => data_get($application, 'uuid'),
|
||||
'application_name' => data_get($application, 'name'),
|
||||
'application_type' => $type,
|
||||
'build_pack' => data_get($application, 'build_pack'),
|
||||
'instant_deploy' => (bool) ($instantDeploy ?? false),
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => data_get($application, 'uuid'),
|
||||
'domains' => data_get($application, 'fqdn'),
|
||||
@@ -1539,6 +1548,15 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
auditLog('api.application.created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => data_get($application, 'uuid'),
|
||||
'application_name' => data_get($application, 'name'),
|
||||
'application_type' => $type,
|
||||
'build_pack' => data_get($application, 'build_pack'),
|
||||
'instant_deploy' => (bool) ($instantDeploy ?? false),
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => data_get($application, 'uuid'),
|
||||
'domains' => data_get($application, 'fqdn'),
|
||||
@@ -1739,6 +1757,15 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
auditLog('api.application.created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => data_get($application, 'uuid'),
|
||||
'application_name' => data_get($application, 'name'),
|
||||
'application_type' => $type,
|
||||
'build_pack' => data_get($application, 'build_pack'),
|
||||
'instant_deploy' => (bool) ($instantDeploy ?? false),
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => data_get($application, 'uuid'),
|
||||
'domains' => data_get($application, 'fqdn'),
|
||||
@@ -1846,6 +1873,15 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
auditLog('api.application.created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => data_get($application, 'uuid'),
|
||||
'application_name' => data_get($application, 'name'),
|
||||
'application_type' => $type,
|
||||
'build_pack' => data_get($application, 'build_pack'),
|
||||
'instant_deploy' => (bool) ($instantDeploy ?? false),
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => data_get($application, 'uuid'),
|
||||
'domains' => data_get($application, 'fqdn'),
|
||||
@@ -1956,6 +1992,15 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
auditLog('api.application.created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => data_get($application, 'uuid'),
|
||||
'application_name' => data_get($application, 'name'),
|
||||
'application_type' => $type,
|
||||
'build_pack' => data_get($application, 'build_pack'),
|
||||
'instant_deploy' => (bool) ($instantDeploy ?? false),
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => data_get($application, 'uuid'),
|
||||
'domains' => data_get($application, 'fqdn'),
|
||||
@@ -2039,6 +2084,14 @@ class ApplicationsController extends Controller
|
||||
StartService::dispatch($service);
|
||||
}
|
||||
|
||||
auditLog('api.application.created', [
|
||||
'team_id' => $teamId,
|
||||
'service_uuid' => data_get($service, 'uuid'),
|
||||
'service_name' => data_get($service, 'name'),
|
||||
'application_type' => $type,
|
||||
'instant_deploy' => (bool) ($instantDeploy ?? false),
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => data_get($service, 'uuid'),
|
||||
'domains' => data_get($service, 'domains'),
|
||||
@@ -2297,6 +2350,12 @@ class ApplicationsController extends Controller
|
||||
dockerCleanup: $request->boolean('docker_cleanup', true)
|
||||
);
|
||||
|
||||
auditLog('api.application.deleted', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Application deletion request queued.',
|
||||
]);
|
||||
@@ -2796,6 +2855,13 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
$application->save();
|
||||
|
||||
auditLog('api.application.updated', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'changed_fields' => array_values(array_intersect($allowedFields, array_keys($request->all()))),
|
||||
]);
|
||||
|
||||
if ($instantDeploy) {
|
||||
$deployment_uuid = new Cuid2;
|
||||
|
||||
@@ -3048,6 +3114,14 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
$env->save();
|
||||
|
||||
auditLog('api.application.env_updated', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'env_uuid' => $env->uuid,
|
||||
'env_key' => $env->key,
|
||||
'is_preview' => (bool) $is_preview,
|
||||
]);
|
||||
|
||||
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
|
||||
} else {
|
||||
return response()->json([
|
||||
@@ -3081,6 +3155,14 @@ class ApplicationsController extends Controller
|
||||
}
|
||||
$env->save();
|
||||
|
||||
auditLog('api.application.env_updated', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'env_uuid' => $env->uuid,
|
||||
'env_key' => $env->key,
|
||||
'is_preview' => (bool) $is_preview,
|
||||
]);
|
||||
|
||||
return response()->json($this->removeSensitiveData($env))->setStatusCode(201);
|
||||
} else {
|
||||
return response()->json([
|
||||
@@ -3307,6 +3389,12 @@ class ApplicationsController extends Controller
|
||||
$returnedEnvs->push($this->removeSensitiveData($env));
|
||||
}
|
||||
|
||||
auditLog('api.application.env_bulk_upserted', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'env_count' => $returnedEnvs->count(),
|
||||
]);
|
||||
|
||||
return response()->json($returnedEnvs)->setStatusCode(201);
|
||||
}
|
||||
|
||||
@@ -3446,6 +3534,14 @@ class ApplicationsController extends Controller
|
||||
'resourceable_id' => $application->id,
|
||||
]);
|
||||
|
||||
auditLog('api.application.env_created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'env_uuid' => $env->uuid,
|
||||
'env_key' => $env->key,
|
||||
'is_preview' => (bool) $is_preview,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $env->uuid,
|
||||
])->setStatusCode(201);
|
||||
@@ -3471,6 +3567,14 @@ class ApplicationsController extends Controller
|
||||
'resourceable_id' => $application->id,
|
||||
]);
|
||||
|
||||
auditLog('api.application.env_created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'env_uuid' => $env->uuid,
|
||||
'env_key' => $env->key,
|
||||
'is_preview' => (bool) $is_preview,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $env->uuid,
|
||||
])->setStatusCode(201);
|
||||
@@ -3562,8 +3666,17 @@ class ApplicationsController extends Controller
|
||||
'message' => 'Environment variable not found.',
|
||||
], 404);
|
||||
}
|
||||
$envKey = $found_env->key;
|
||||
$envUuid = $found_env->uuid;
|
||||
$found_env->forceDelete();
|
||||
|
||||
auditLog('api.application.env_deleted', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'env_uuid' => $envUuid,
|
||||
'env_key' => $envKey,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Environment variable deleted.',
|
||||
]);
|
||||
@@ -3675,6 +3788,15 @@ class ApplicationsController extends Controller
|
||||
);
|
||||
}
|
||||
|
||||
auditLog('api.application.deployed', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'force_rebuild' => $force,
|
||||
'instant_deploy' => $instant_deploy,
|
||||
]);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Deployment request queued.',
|
||||
@@ -3763,6 +3885,13 @@ class ApplicationsController extends Controller
|
||||
$dockerCleanup = $request->boolean('docker_cleanup', true);
|
||||
StopApplication::dispatch($application, false, $dockerCleanup);
|
||||
|
||||
auditLog('api.application.stopped', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'docker_cleanup' => $dockerCleanup,
|
||||
]);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Application stopping request queued.',
|
||||
@@ -3853,6 +3982,13 @@ class ApplicationsController extends Controller
|
||||
], 200);
|
||||
}
|
||||
|
||||
auditLog('api.application.restarted', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'application_name' => $application->name,
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
]);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Restart request queued.',
|
||||
@@ -4221,6 +4357,15 @@ class ApplicationsController extends Controller
|
||||
|
||||
$storage->save();
|
||||
|
||||
auditLog('api.application.storage_updated', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'storage_uuid' => $storage->uuid ?? null,
|
||||
'storage_id' => $storage->id,
|
||||
'storage_type' => $request->type,
|
||||
'mount_path' => $storage->mount_path ?? null,
|
||||
]);
|
||||
|
||||
return response()->json($storage);
|
||||
}
|
||||
|
||||
@@ -4399,6 +4544,15 @@ class ApplicationsController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
auditLog('api.application.storage_created', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'storage_uuid' => $storage->uuid ?? null,
|
||||
'storage_id' => $storage->id,
|
||||
'storage_type' => $request->type,
|
||||
'mount_path' => $storage->mount_path,
|
||||
]);
|
||||
|
||||
return response()->json($storage, 201);
|
||||
}
|
||||
|
||||
@@ -4472,8 +4626,18 @@ class ApplicationsController extends Controller
|
||||
$storage->deleteStorageOnServer();
|
||||
}
|
||||
|
||||
$storageType = $storage instanceof LocalFileVolume ? 'file' : 'persistent';
|
||||
$storageMountPath = $storage->mount_path ?? null;
|
||||
$storage->delete();
|
||||
|
||||
auditLog('api.application.storage_deleted', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'storage_uuid' => $storageUuid,
|
||||
'storage_type' => $storageType,
|
||||
'mount_path' => $storageMountPath,
|
||||
]);
|
||||
|
||||
return response()->json(['message' => 'Storage deleted.']);
|
||||
}
|
||||
|
||||
@@ -4543,6 +4707,12 @@ class ApplicationsController extends Controller
|
||||
$preview->delete();
|
||||
CleanupPreviewDeployment::run($application, $pullRequestId, $preview);
|
||||
|
||||
auditLog('api.application.preview_deleted', [
|
||||
'team_id' => $teamId,
|
||||
'application_uuid' => $application->uuid,
|
||||
'pull_request_id' => $pullRequestId,
|
||||
]);
|
||||
|
||||
return response()->json(['message' => 'Preview deletion request queued.']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user