mirror of
https://github.com/coder/coder.git
synced 2026-06-03 21:18:24 +00:00
f42de9fe12
### Breaking change (changelog note):
>With new connection events appearing in the Connection Log, connection events older than 90 days will now be deleted from the Audit Log. If you require this legacy data, we recommend querying it from the REST API or making a backup of the database/these events before upgrading your Coder deployment. Please see the PR for details on what exactly will be deleted.
Of note is that there are currently no plans to delete connection events from the Connection Log.
### Context
This is the fifth PR for moving connection events out of the audit log.
In previous PRs:
- **New** connection logs have been routed to the `connection_logs` table. They will *not* appear in the audit log.
- These new connection logs are served from the new `/api/v2/connectionlog` endpoint.
In this PR:
- We'll now clean existing connection events out of the audit log, if they are older than 90 days, We do this in batches of 1000, every 10 minutes.
The criteria for deletion is simple:
```
WHERE
(
action = 'connect'
OR action = 'disconnect'
OR action = 'open'
OR action = 'close'
)
AND "time" < @before_time::timestamp with time zone
```
where `@before_time` is currently configured to 90 days in the past.
Future PRs:
- Write documentation for the endpoint / feature
256 lines
7.4 KiB
SQL
256 lines
7.4 KiB
SQL
-- GetAuditLogsBefore retrieves `row_limit` number of audit logs before the provided
|
|
-- ID.
|
|
-- name: GetAuditLogsOffset :many
|
|
SELECT sqlc.embed(audit_logs),
|
|
-- sqlc.embed(users) would be nice but it does not seem to play well with
|
|
-- left joins.
|
|
users.username AS user_username,
|
|
users.name AS user_name,
|
|
users.email AS user_email,
|
|
users.created_at AS user_created_at,
|
|
users.updated_at AS user_updated_at,
|
|
users.last_seen_at AS user_last_seen_at,
|
|
users.status AS user_status,
|
|
users.login_type AS user_login_type,
|
|
users.rbac_roles AS user_roles,
|
|
users.avatar_url AS user_avatar_url,
|
|
users.deleted AS user_deleted,
|
|
users.quiet_hours_schedule AS user_quiet_hours_schedule,
|
|
COALESCE(organizations.name, '') AS organization_name,
|
|
COALESCE(organizations.display_name, '') AS organization_display_name,
|
|
COALESCE(organizations.icon, '') AS organization_icon
|
|
FROM audit_logs
|
|
LEFT JOIN users ON audit_logs.user_id = users.id
|
|
LEFT JOIN organizations ON audit_logs.organization_id = organizations.id
|
|
-- First join on workspaces to get the initial workspace create
|
|
-- to workspace build 1 id. This is because the first create is
|
|
-- is a different audit log than subsequent starts.
|
|
LEFT JOIN workspaces ON audit_logs.resource_type = 'workspace'
|
|
AND audit_logs.resource_id = workspaces.id
|
|
-- Get the reason from the build if the resource type
|
|
-- is a workspace_build
|
|
LEFT JOIN workspace_builds wb_build ON audit_logs.resource_type = 'workspace_build'
|
|
AND audit_logs.resource_id = wb_build.id
|
|
-- Get the reason from the build #1 if this is the first
|
|
-- workspace create.
|
|
LEFT JOIN workspace_builds wb_workspace ON audit_logs.resource_type = 'workspace'
|
|
AND audit_logs.action = 'create'
|
|
AND workspaces.id = wb_workspace.workspace_id
|
|
AND wb_workspace.build_number = 1
|
|
WHERE
|
|
-- Filter resource_type
|
|
CASE
|
|
WHEN @resource_type::text != '' THEN resource_type = @resource_type::resource_type
|
|
ELSE true
|
|
END
|
|
-- Filter resource_id
|
|
AND CASE
|
|
WHEN @resource_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN resource_id = @resource_id
|
|
ELSE true
|
|
END
|
|
-- Filter organization_id
|
|
AND CASE
|
|
WHEN @organization_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN audit_logs.organization_id = @organization_id
|
|
ELSE true
|
|
END
|
|
-- Filter by resource_target
|
|
AND CASE
|
|
WHEN @resource_target::text != '' THEN resource_target = @resource_target
|
|
ELSE true
|
|
END
|
|
-- Filter action
|
|
AND CASE
|
|
WHEN @action::text != '' THEN action = @action::audit_action
|
|
ELSE true
|
|
END
|
|
-- Filter by user_id
|
|
AND CASE
|
|
WHEN @user_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN user_id = @user_id
|
|
ELSE true
|
|
END
|
|
-- Filter by username
|
|
AND CASE
|
|
WHEN @username::text != '' THEN user_id = (
|
|
SELECT id
|
|
FROM users
|
|
WHERE lower(username) = lower(@username)
|
|
AND deleted = false
|
|
)
|
|
ELSE true
|
|
END
|
|
-- Filter by user_email
|
|
AND CASE
|
|
WHEN @email::text != '' THEN users.email = @email
|
|
ELSE true
|
|
END
|
|
-- Filter by date_from
|
|
AND CASE
|
|
WHEN @date_from::timestamp with time zone != '0001-01-01 00:00:00Z' THEN "time" >= @date_from
|
|
ELSE true
|
|
END
|
|
-- Filter by date_to
|
|
AND CASE
|
|
WHEN @date_to::timestamp with time zone != '0001-01-01 00:00:00Z' THEN "time" <= @date_to
|
|
ELSE true
|
|
END
|
|
-- Filter by build_reason
|
|
AND CASE
|
|
WHEN @build_reason::text != '' THEN COALESCE(wb_build.reason::text, wb_workspace.reason::text) = @build_reason
|
|
ELSE true
|
|
END
|
|
-- Filter request_id
|
|
AND CASE
|
|
WHEN @request_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN audit_logs.request_id = @request_id
|
|
ELSE true
|
|
END
|
|
-- Authorize Filter clause will be injected below in GetAuthorizedAuditLogsOffset
|
|
-- @authorize_filter
|
|
ORDER BY "time" DESC
|
|
LIMIT -- a limit of 0 means "no limit". The audit log table is unbounded
|
|
-- in size, and is expected to be quite large. Implement a default
|
|
-- limit of 100 to prevent accidental excessively large queries.
|
|
COALESCE(NULLIF(@limit_opt::int, 0), 100) OFFSET @offset_opt;
|
|
|
|
-- name: InsertAuditLog :one
|
|
INSERT INTO audit_logs (
|
|
id,
|
|
"time",
|
|
user_id,
|
|
organization_id,
|
|
ip,
|
|
user_agent,
|
|
resource_type,
|
|
resource_id,
|
|
resource_target,
|
|
action,
|
|
diff,
|
|
status_code,
|
|
additional_fields,
|
|
request_id,
|
|
resource_icon
|
|
)
|
|
VALUES (
|
|
$1,
|
|
$2,
|
|
$3,
|
|
$4,
|
|
$5,
|
|
$6,
|
|
$7,
|
|
$8,
|
|
$9,
|
|
$10,
|
|
$11,
|
|
$12,
|
|
$13,
|
|
$14,
|
|
$15
|
|
)
|
|
RETURNING *;
|
|
|
|
-- name: CountAuditLogs :one
|
|
SELECT COUNT(*)
|
|
FROM audit_logs
|
|
LEFT JOIN users ON audit_logs.user_id = users.id
|
|
LEFT JOIN organizations ON audit_logs.organization_id = organizations.id
|
|
-- First join on workspaces to get the initial workspace create
|
|
-- to workspace build 1 id. This is because the first create is
|
|
-- is a different audit log than subsequent starts.
|
|
LEFT JOIN workspaces ON audit_logs.resource_type = 'workspace'
|
|
AND audit_logs.resource_id = workspaces.id
|
|
-- Get the reason from the build if the resource type
|
|
-- is a workspace_build
|
|
LEFT JOIN workspace_builds wb_build ON audit_logs.resource_type = 'workspace_build'
|
|
AND audit_logs.resource_id = wb_build.id
|
|
-- Get the reason from the build #1 if this is the first
|
|
-- workspace create.
|
|
LEFT JOIN workspace_builds wb_workspace ON audit_logs.resource_type = 'workspace'
|
|
AND audit_logs.action = 'create'
|
|
AND workspaces.id = wb_workspace.workspace_id
|
|
AND wb_workspace.build_number = 1
|
|
WHERE
|
|
-- Filter resource_type
|
|
CASE
|
|
WHEN @resource_type::text != '' THEN resource_type = @resource_type::resource_type
|
|
ELSE true
|
|
END
|
|
-- Filter resource_id
|
|
AND CASE
|
|
WHEN @resource_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN resource_id = @resource_id
|
|
ELSE true
|
|
END
|
|
-- Filter organization_id
|
|
AND CASE
|
|
WHEN @organization_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN audit_logs.organization_id = @organization_id
|
|
ELSE true
|
|
END
|
|
-- Filter by resource_target
|
|
AND CASE
|
|
WHEN @resource_target::text != '' THEN resource_target = @resource_target
|
|
ELSE true
|
|
END
|
|
-- Filter action
|
|
AND CASE
|
|
WHEN @action::text != '' THEN action = @action::audit_action
|
|
ELSE true
|
|
END
|
|
-- Filter by user_id
|
|
AND CASE
|
|
WHEN @user_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN user_id = @user_id
|
|
ELSE true
|
|
END
|
|
-- Filter by username
|
|
AND CASE
|
|
WHEN @username::text != '' THEN user_id = (
|
|
SELECT id
|
|
FROM users
|
|
WHERE lower(username) = lower(@username)
|
|
AND deleted = false
|
|
)
|
|
ELSE true
|
|
END
|
|
-- Filter by user_email
|
|
AND CASE
|
|
WHEN @email::text != '' THEN users.email = @email
|
|
ELSE true
|
|
END
|
|
-- Filter by date_from
|
|
AND CASE
|
|
WHEN @date_from::timestamp with time zone != '0001-01-01 00:00:00Z' THEN "time" >= @date_from
|
|
ELSE true
|
|
END
|
|
-- Filter by date_to
|
|
AND CASE
|
|
WHEN @date_to::timestamp with time zone != '0001-01-01 00:00:00Z' THEN "time" <= @date_to
|
|
ELSE true
|
|
END
|
|
-- Filter by build_reason
|
|
AND CASE
|
|
WHEN @build_reason::text != '' THEN COALESCE(wb_build.reason::text, wb_workspace.reason::text) = @build_reason
|
|
ELSE true
|
|
END
|
|
-- Filter request_id
|
|
AND CASE
|
|
WHEN @request_id::uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN audit_logs.request_id = @request_id
|
|
ELSE true
|
|
END
|
|
-- Authorize Filter clause will be injected below in CountAuthorizedAuditLogs
|
|
-- @authorize_filter
|
|
;
|
|
|
|
-- name: DeleteOldAuditLogConnectionEvents :exec
|
|
DELETE FROM audit_logs
|
|
WHERE id IN (
|
|
SELECT id FROM audit_logs
|
|
WHERE
|
|
(
|
|
action = 'connect'
|
|
OR action = 'disconnect'
|
|
OR action = 'open'
|
|
OR action = 'close'
|
|
)
|
|
AND "time" < @before_time::timestamp with time zone
|
|
ORDER BY "time" ASC
|
|
LIMIT @limit_count
|
|
);
|