1
0
mirror of https://github.com/quay/quay.git synced 2025-04-18 10:44:06 +03:00

logs: Audit export logs requests (PROJQUAY-7679) (#3146)

* logs: Audit export logs requests (PROJQUAY-7679))
We add the ability to audit export logs requests that were previously not tracked.

* Add UI elements to properly render new audit log

* Truncate date/time column on exterme zooms

* Add initdb.py entries

* Fix migration and add test db data

* Add test database and fix migration paths

* Changed logging mechanism to grab raised exceptions

* Fix improper import

* Add date/time timestamp to saved metadata

* Change message on export logs screen in UI

* Changed message in old UI as well

* Change log description in new UI too

* Simplify call logic and add additonal information to logged errors
This commit is contained in:
Ivan Bazulic 2024-10-03 13:07:22 -04:00 committed by GitHub
parent 212cb80741
commit 77bc70a637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 241 additions and 44 deletions

View File

@ -0,0 +1,36 @@
"""Add export logs auditing
Revision ID: 8e97c2cfee57
Revises: 9085e82074f2
Create Date: 2024-08-19 13:56:50.063519
"""
# revision identifiers, used by Alembic.
revision = "8e97c2cfee57"
down_revision = "9085e82074f2"
import sqlalchemy as sa
def upgrade(op, tables, tester):
op.bulk_insert(
tables.logentrykind,
[
{"name": "export_logs_success"},
{"name": "export_logs_failure"},
],
)
def downgrade(op, tables, tester):
op.execute(
tables.logentrykind.delete().where(
tables.logentrykind.c.name == op.inline_literal("export_logs_success")
)
)
op.execute(
tables.logentrykind.delete().where(
tables.logentrykind.c.name == op.inline_literal("export_logs_failure")
)
)

View File

@ -20,6 +20,7 @@ from endpoints.api import (
allow_if_global_readonly_superuser,
allow_if_superuser,
format_date,
log_action,
nickname,
page_support,
parse_args,
@ -335,7 +336,7 @@ def _queue_logs_export(start_time, end_time, options, namespace_name, repository
(start_time, end_time) = _validate_logs_arguments(start_time, end_time)
if end_time < start_time:
abort(400)
raise InvalidLogsDateRangeError("Invalid time span selected")
export_id = logs_model.queue_logs_export(
start_time,
end_time,
@ -351,6 +352,45 @@ def _queue_logs_export(start_time, end_time, options, namespace_name, repository
return export_id
def _log_export_success(user_or_org_name, export_id, request, repository=None):
metadata = {
"date/time": datetime.utcnow(),
"export_id": export_id,
"message": "queued for export",
"url": request.get_json().get("callback_url") or None,
"email": request.get_json().get("callback_email") or None,
}
if repository:
metadata["repo"] = repository
log_action(
"export_logs_success",
user_or_org_name,
metadata,
)
def _log_export_failure(user_or_org_name, request, ex, repository=None):
metadata = {
"date/time": datetime.utcnow(),
"error": ex,
"url": request.get_json().get("callback_url") or None,
"email": request.get_json().get("callback_email") or None,
}
if repository:
metadata["repo"] = repository
log_action(
"export_logs_failure",
user_or_org_name,
metadata,
)
@resource("/v1/repository/<apirepopath:repository>/exportlogs")
@show_if(features.LOG_EXPORT)
@path_param("repository", "The full path of the repository. e.g. namespace/name")
@ -372,13 +412,21 @@ class ExportRepositoryLogs(RepositoryParamResource):
Queues an export of the logs for the specified repository.
"""
if registry_model.lookup_repository(namespace, repository) is None:
_log_export_failure(namespace, request, "non-existent repository", repository)
raise NotFound()
start_time = parsed_args["starttime"]
end_time = parsed_args["endtime"]
export_id = _queue_logs_export(
start_time, end_time, request.get_json(), namespace, repository_name=repository
)
try:
export_id = _queue_logs_export(
start_time, end_time, request.get_json(), namespace, repository_name=repository
)
except (InvalidRequest, InvalidLogsDateRangeError) as ex:
_log_export_failure(namespace, request, ex, repository)
abort(400, ex)
_log_export_success(namespace, export_id, request, repository)
return {
"export_id": export_id,
}
@ -403,11 +451,19 @@ class ExportUserLogs(ApiResource):
"""
Returns the aggregated logs for the current user.
"""
user = get_authenticated_user()
start_time = parsed_args["starttime"]
end_time = parsed_args["endtime"]
user = get_authenticated_user()
export_id = _queue_logs_export(start_time, end_time, request.get_json(), user.username)
try:
export_id = _queue_logs_export(start_time, end_time, request.get_json(), user.username)
except (InvalidRequest, InvalidLogsDateRangeError) as ex:
_log_export_failure(user.username, request, ex, None)
abort(400, ex)
_log_export_success(user.username, export_id, request, None)
return {
"export_id": export_id,
}
@ -439,9 +495,17 @@ class ExportOrgLogs(ApiResource):
start_time = parsed_args["starttime"]
end_time = parsed_args["endtime"]
export_id = _queue_logs_export(start_time, end_time, request.get_json(), orgname)
try:
export_id = _queue_logs_export(start_time, end_time, request.get_json(), orgname)
except (InvalidRequest, InvalidLogsDateRangeError) as ex:
_log_export_failure(orgname, request, ex, None)
abort(400, ex)
_log_export_success(orgname, export_id, request, None)
return {
"export_id": export_id,
}
_log_export_failure(orgname, request, "unauthorized", None)
raise Unauthorized()

View File

@ -483,6 +483,9 @@ def initialize_database():
LogEntryKind.create(name="oauth_token_assigned")
LogEntryKind.create(name="oauth_token_revoked")
LogEntryKind.create(name="export_logs_success")
LogEntryKind.create(name="export_logs_failure")
ImageStorageLocation.create(name="local_eu")
ImageStorageLocation.create(name="local_us")

View File

@ -608,6 +608,28 @@ angular.module('quay').directive('logsView', function () {
'oauth_token_assigned': function (metadata) {
return 'OAuth token assigned to user {assigned_user} by {assigning_user} for application {application}[[with client id {client_id}]]';
},
export_logs_success: function (metadata) {
if (metadata.repo) {
if (metadata.url) {
return `Logs export queued for delivery: id ${metadata.export_id}, url: ${metadata.url}, repository: ${metadata.repo}`;
} else if (metadata.email) {
return `Logs export queued for delivery: id ${metadata.export_id}, email: ${obfuscate_email(metadata.email)}, repository: ${metadata.repo}`;
} else {
return `Logs export queued for delivery: id ${metadata.export_id}, url: ${metadata.url}, email: ${obfuscate_email(metadata.email)}, repository: ${metadata.repo}`;
}
} else {
if (metadata.url) {
return `User/organization logs export queued for delivery: id ${metadata.export_id}, url: ${metadata.url}`;
} else if (metadata.email) {
return `User/organization logs export queued for delivery: id ${metadata.export_id}, email: ${obfuscate_email(metadata.email)}`;
} else {
return `User/organization logs export queued for delivery: id ${metadata.export_id}, url: ${metadata.url}, email: ${obfuscate_email(metadata.email)}`;
}
}
},
export_logs_failure: function (metadata) {
return `Export logs failure: ${metadata.error}, ${metadata.repo ? `requested repository: ${metadata.repo}` : ''}`;
},
};
var logKinds = {
@ -711,6 +733,8 @@ angular.module('quay').directive('logsView', function () {
'login_failure': 'Login failure',
'autoprune_tag_delete': 'Autoprune worker tag deletion',
'oauth_token_assigned': 'OAuth token assigned',
'export_logs_success': 'Export logs queued for delivery',
'export_logs_failure': 'Export logs failure',
// Note: these are deprecated.
'add_repo_webhook': 'Add webhook',
@ -892,3 +916,8 @@ angular.module('quay').directive('logsView', function () {
return directiveDefinitionObject;
});
function obfuscate_email(email) {
const email_array = email.split('@');
return email_array[0].substring(0, 2)+'*'.repeat(email_array[0].length - 2)+'@'+email_array[1];
}

View File

@ -5742,7 +5742,7 @@ COPY public.accesstokenkind (id, name) FROM stdin;
--
COPY public.alembic_version (version_num) FROM stdin;
ba263f9be4a6
8e97c2cfee57
\.
@ -6424,6 +6424,11 @@ COPY public.logentrykind (id, name) FROM stdin;
113 oauth_token_assigned
114 oauth_token_revoked
115 change_tag_immutability
116 create_robot_federation
117 delete_robot_federation
118 federated_robot_token_exchange
119 export_logs_success
120 export_logs_failure
\.
@ -6448,18 +6453,18 @@ COPY public.loginservice (id, name) FROM stdin;
--
COPY public.manifest (id, repository_id, digest, media_type_id, manifest_bytes, config_media_type, layers_compressed_size, subject, subject_backfilled, artifact_type, artifact_type_backfilled) FROM stdin;
1 1 sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1469,\n "digest": "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2479,\n "digest": "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2479 \N \N \N \N
2 1 sha256:7b8b7289d0536a08eabdf71c20246e23f7116641db7e1d278592236ea4dcb30c 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1482,\n "digest": "sha256:c0218de6585df06a66d67b25237bdda42137c727c367373a32639710c7a9fa94"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3684,\n "digest": "sha256:b921b04d0447ddcd82a9220d887cd146f6ef39e20a938ee5e19a90fc3323e030"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3684 \N \N \N \N
3 1 sha256:f130bd2d67e6e9280ac6d0a6c83857bfaf70234e8ef4236876eccfbd30973b1c 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1482,\n "digest": "sha256:1ec996c686eb87d8f091080ec29dd1862b39b5822ddfd8f9a1e2c9288fad89fe"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2993,\n "digest": "sha256:9b157615502ddff86482f7fe2fa7a074db74a62fce12b4e8507827ac8f08d0ce"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2993 \N \N \N \N
4 1 sha256:432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1485,\n "digest": "sha256:46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3208,\n "digest": "sha256:7050e35b49f5e348c4809f5eff915842962cb813f32062d3bbdd35c750dd7d01"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3208 \N \N \N \N
5 1 sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1468,\n "digest": "sha256:36d89aa75357c8f99e359f8cabc0aae667d47d8f25ed51cbe66e148e3a77e19c"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2736,\n "digest": "sha256:7f0d4fad461d1ac69488092b5914b5ec642133c0fb884539045de33fbcd2eadb"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2736 \N \N \N \N
6 1 sha256:eb11b1a194ff8e236a01eff392c4e1296a53b0fb4780d8b0382f7996a15d5392 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1473,\n "digest": "sha256:5004e9d559e7a75f42249ddeca4d5764fa4db05592a7a9a641e4ac37cc619ba1"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 4092,\n "digest": "sha256:bbc6052697e5fdcd1b311e0b3f65189ffbe354cf8ae97e7a55d588e855097174"\n }\n ]\n} application/vnd.docker.container.image.v1+json 4092 \N \N \N \N
7 1 sha256:b836bb24a270b9cc935962d8228517fde0f16990e88893d935efcb1b14c0017a 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1471,\n "digest": "sha256:61fff98d5ca765a4351964c8f4b5fb1a0d2c48458026f5452a389eb52d146fe8"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3929,\n "digest": "sha256:33450689bfb495ed64ead935c9933f1d6b3e42fe369b8de9680cf4ff9d89ce5c"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3929 \N \N \N \N
8 1 sha256:98c9722322be649df94780d3fbe594fce7996234b259f27eac9428b84050c849 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1471,\n "digest": "sha256:b3593dab05491cdf5ee88c29bee36603c0df0bc34798eed5067f6e1335a9d391"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3000,\n "digest": "sha256:3caa6dc69d0b73f21d29bfa75356395f2695a7abad34f010656740e90ddce399"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3000 \N \N \N \N
9 1 sha256:c7b6944911848ce39b44ed660d95fb54d69bbd531de724c7ce6fc9f743c0b861 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1469,\n "digest": "sha256:df5477cea5582b0ae6a31de2d1c9bbacb506091f42a3b0fe77a209006f409fd8"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3276,\n "digest": "sha256:abc70fcc95b2f52b325d69cc5c259dd9babb40a9df152e88b286fada1d3248bd"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3276 \N \N \N \N
10 1 sha256:7693efac53eb85ff1afb03f7f2560015c57ac2175707f1f141f31161634c9dba 15 {"manifests":[{"digest":"sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"amd64","os":"linux"},"size":525},{"digest":"sha256:7b8b7289d0536a08eabdf71c20246e23f7116641db7e1d278592236ea4dcb30c","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm","os":"linux","variant":"v5"},"size":525},{"digest":"sha256:f130bd2d67e6e9280ac6d0a6c83857bfaf70234e8ef4236876eccfbd30973b1c","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm","os":"linux","variant":"v7"},"size":525},{"digest":"sha256:432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm64","os":"linux","variant":"v8"},"size":525},{"digest":"sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"386","os":"linux"},"size":525},{"digest":"sha256:eb11b1a194ff8e236a01eff392c4e1296a53b0fb4780d8b0382f7996a15d5392","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"mips64le","os":"linux"},"size":525},{"digest":"sha256:b836bb24a270b9cc935962d8228517fde0f16990e88893d935efcb1b14c0017a","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"ppc64le","os":"linux"},"size":525},{"digest":"sha256:98c9722322be649df94780d3fbe594fce7996234b259f27eac9428b84050c849","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"riscv64","os":"linux"},"size":525},{"digest":"sha256:c7b6944911848ce39b44ed660d95fb54d69bbd531de724c7ce6fc9f743c0b861","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"s390x","os":"linux"},"size":525}],"mediaType":"application\\/vnd.docker.distribution.manifest.list.v2+json","schemaVersion":2} \N 0 \N \N \N \N
11 155 sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1469,\n "digest": "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2479,\n "digest": "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2479 \N \N \N \N
12 1 sha256:7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1470,\n "digest": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2457,\n "digest": "sha256:719385e32844401d57ecfd3eacab360bf551a1491c05b85806ed8f1b08d792f6"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2457 \N \N \N \N
2 1 sha256:7b8b7289d0536a08eabdf71c20246e23f7116641db7e1d278592236ea4dcb30c 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1482,\n "digest": "sha256:c0218de6585df06a66d67b25237bdda42137c727c367373a32639710c7a9fa94"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3684,\n "digest": "sha256:b921b04d0447ddcd82a9220d887cd146f6ef39e20a938ee5e19a90fc3323e030"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3684 \N t \N t
4 1 sha256:432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1485,\n "digest": "sha256:46331d942d6350436f64e614d75725f6de3bb5c63e266e236e04389820a234c4"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3208,\n "digest": "sha256:7050e35b49f5e348c4809f5eff915842962cb813f32062d3bbdd35c750dd7d01"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3208 \N t \N t
5 1 sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1468,\n "digest": "sha256:36d89aa75357c8f99e359f8cabc0aae667d47d8f25ed51cbe66e148e3a77e19c"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2736,\n "digest": "sha256:7f0d4fad461d1ac69488092b5914b5ec642133c0fb884539045de33fbcd2eadb"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2736 \N t \N t
6 1 sha256:eb11b1a194ff8e236a01eff392c4e1296a53b0fb4780d8b0382f7996a15d5392 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1473,\n "digest": "sha256:5004e9d559e7a75f42249ddeca4d5764fa4db05592a7a9a641e4ac37cc619ba1"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 4092,\n "digest": "sha256:bbc6052697e5fdcd1b311e0b3f65189ffbe354cf8ae97e7a55d588e855097174"\n }\n ]\n} application/vnd.docker.container.image.v1+json 4092 \N t \N t
7 1 sha256:b836bb24a270b9cc935962d8228517fde0f16990e88893d935efcb1b14c0017a 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1471,\n "digest": "sha256:61fff98d5ca765a4351964c8f4b5fb1a0d2c48458026f5452a389eb52d146fe8"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3929,\n "digest": "sha256:33450689bfb495ed64ead935c9933f1d6b3e42fe369b8de9680cf4ff9d89ce5c"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3929 \N t \N t
8 1 sha256:98c9722322be649df94780d3fbe594fce7996234b259f27eac9428b84050c849 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1471,\n "digest": "sha256:b3593dab05491cdf5ee88c29bee36603c0df0bc34798eed5067f6e1335a9d391"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3000,\n "digest": "sha256:3caa6dc69d0b73f21d29bfa75356395f2695a7abad34f010656740e90ddce399"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3000 \N t \N t
9 1 sha256:c7b6944911848ce39b44ed660d95fb54d69bbd531de724c7ce6fc9f743c0b861 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1469,\n "digest": "sha256:df5477cea5582b0ae6a31de2d1c9bbacb506091f42a3b0fe77a209006f409fd8"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 3276,\n "digest": "sha256:abc70fcc95b2f52b325d69cc5c259dd9babb40a9df152e88b286fada1d3248bd"\n }\n ]\n} application/vnd.docker.container.image.v1+json 3276 \N t \N t
10 1 sha256:7693efac53eb85ff1afb03f7f2560015c57ac2175707f1f141f31161634c9dba 15 {"manifests":[{"digest":"sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"amd64","os":"linux"},"size":525},{"digest":"sha256:7b8b7289d0536a08eabdf71c20246e23f7116641db7e1d278592236ea4dcb30c","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm","os":"linux","variant":"v5"},"size":525},{"digest":"sha256:f130bd2d67e6e9280ac6d0a6c83857bfaf70234e8ef4236876eccfbd30973b1c","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm","os":"linux","variant":"v7"},"size":525},{"digest":"sha256:432f982638b3aefab73cc58ab28f5c16e96fdb504e8c134fc58dff4bae8bf338","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"arm64","os":"linux","variant":"v8"},"size":525},{"digest":"sha256:995efde2e81b21d1ea7066aa77a59298a62a9e9fbb4b77f36c189774ec9b1089","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"386","os":"linux"},"size":525},{"digest":"sha256:eb11b1a194ff8e236a01eff392c4e1296a53b0fb4780d8b0382f7996a15d5392","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"mips64le","os":"linux"},"size":525},{"digest":"sha256:b836bb24a270b9cc935962d8228517fde0f16990e88893d935efcb1b14c0017a","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"ppc64le","os":"linux"},"size":525},{"digest":"sha256:98c9722322be649df94780d3fbe594fce7996234b259f27eac9428b84050c849","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"riscv64","os":"linux"},"size":525},{"digest":"sha256:c7b6944911848ce39b44ed660d95fb54d69bbd531de724c7ce6fc9f743c0b861","mediaType":"application\\/vnd.docker.distribution.manifest.v2+json","platform":{"architecture":"s390x","os":"linux"},"size":525}],"mediaType":"application\\/vnd.docker.distribution.manifest.list.v2+json","schemaVersion":2} \N 0 \N t \N t
11 155 sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1469,\n "digest": "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2479,\n "digest": "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2479 \N t \N t
1 1 sha256:f54a58bc1aac5ea1a25d796ae155dc228b3f0e11d046ae276b39c4bf2f13d8c4 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1469,\n "digest": "sha256:feb5d9fea6a5e9606aa995e879d862b825965ba48de054caab5ef356dc6b3412"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2479,\n "digest": "sha256:2db29710123e3e53a794f2694094b9b4338aa9ee5c40b930cb8063a1be392c54"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2479 \N t \N t
3 1 sha256:f130bd2d67e6e9280ac6d0a6c83857bfaf70234e8ef4236876eccfbd30973b1c 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1482,\n "digest": "sha256:1ec996c686eb87d8f091080ec29dd1862b39b5822ddfd8f9a1e2c9288fad89fe"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2993,\n "digest": "sha256:9b157615502ddff86482f7fe2fa7a074db74a62fce12b4e8507827ac8f08d0ce"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2993 \N t \N t
12 1 sha256:7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3 16 {\n "schemaVersion": 2,\n "mediaType": "application/vnd.docker.distribution.manifest.v2+json",\n "config": {\n "mediaType": "application/vnd.docker.container.image.v1+json",\n "size": 1470,\n "digest": "sha256:9c7a54a9a43cca047013b82af109fe963fde787f63f9e016fdc3384500c2823d"\n },\n "layers": [\n {\n "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",\n "size": 2457,\n "digest": "sha256:719385e32844401d57ecfd3eacab360bf551a1491c05b85806ed8f1b08d792f6"\n }\n ]\n} application/vnd.docker.container.image.v1+json 2457 \N t \N t
\.
@ -6710,8 +6715,8 @@ COPY public.quayservice (id, name) FROM stdin;
--
COPY public.queueitem (id, queue_name, body, available_after, available, processing_expires, retries_remaining, state_id) FROM stdin;
1 namespacegc/2/ {"marker_id": 1, "original_username": "quay"} 2024-06-07 15:55:41.812038 t 2024-06-07 18:50:41.733122 5 348c6b78-b9a0-4db1-8fa5-f67e2e42d485
2 namespacegc/3/ {"marker_id": 2, "original_username": "clair"} 2024-06-07 15:55:46.859942 t 2024-06-07 18:50:46.830201 5 81d862c7-29b5-426d-86cf-55241ed696a6
2 namespacegc/3/ {"marker_id": 2, "original_username": "clair"} 2024-09-30 17:51:28.055159 t 2024-09-30 20:46:28.041972 5 245c3f97-bac6-4742-93eb-2b61f948c6b9
1 namespacegc/2/ {"marker_id": 1, "original_username": "quay"} 2024-09-30 17:51:33.074743 t 2024-09-30 20:46:33.062514 5 daa3897c-92bf-4678-a49a-8dbf4efed18c
\.
@ -7735,23 +7740,23 @@ COPY public.star (id, user_id, repository_id, created) FROM stdin;
-- Data for Name: tag; Type: TABLE DATA; Schema: public; Owner: quay
--
COPY public.tag (id, name, repository_id, manifest_id, lifetime_start_ms, lifetime_end_ms, hidden, reversion, tag_kind_id, linked_tag_id) FROM stdin;
2 $temp-14b356fd-6475-4036-8a51-6222469ec319 1 2 1667589325995 1667592925995 t f 1 \N
3 $temp-4a1787d7-7f4c-4bb8-84d7-23e518be2e07 1 3 1667589327671 1667592927671 t f 1 \N
4 $temp-9f0521e7-4e36-4934-b467-0358b158faad 1 4 1667589329223 1667592929223 t f 1 \N
5 $temp-aa3c515a-80ce-47ab-b21f-1285e17d03ba 1 5 1667589330949 1667592930949 t f 1 \N
6 $temp-2eb4b1d2-65da-4161-97cf-762d9bb4fe93 1 6 1667589332577 1667592932577 t f 1 \N
7 $temp-4ddefd63-fdcb-43d6-8740-be334338e846 1 7 1667589334341 1667592934341 t f 1 \N
8 $temp-7c67ad17-2b65-49b2-a9ce-e018689ce7f2 1 8 1667589335850 1667592935850 t f 1 \N
9 $temp-64f33f3a-8551-49da-b36c-e4ab3a47c247 1 9 1667589337525 1667592937525 t f 1 \N
10 manifestlist 1 10 1667589337924 \N f f 1 \N
11 latest 155 11 1687976145896 \N f f 1 \N
1 latest 1 1 1667589234345 1690479044462 f f 1 \N
12 $temp-224d812a-ca19-4ccb-92c2-9a04a21924a7 1 1 1690479048789 1690479348789 t f 1 \N
13 latest 1 1 1690479048814 1690479058287 f t 1 \N
15 $temp-cfb6beff-645a-479d-be37-73ce2631b3c7 1 1 1690479070649 1690479370649 t f 1 \N
14 latest 1 12 1690479058287 1690479070667 f f 1 \N
16 latest 1 1 1690479070667 \N f t 1 \N
COPY public.tag (id, name, repository_id, manifest_id, lifetime_start_ms, lifetime_end_ms, hidden, reversion, tag_kind_id, linked_tag_id, immutable) FROM stdin;
2 $temp-14b356fd-6475-4036-8a51-6222469ec319 1 2 1667589325995 1667592925995 t f 1 \N f
3 $temp-4a1787d7-7f4c-4bb8-84d7-23e518be2e07 1 3 1667589327671 1667592927671 t f 1 \N f
4 $temp-9f0521e7-4e36-4934-b467-0358b158faad 1 4 1667589329223 1667592929223 t f 1 \N f
5 $temp-aa3c515a-80ce-47ab-b21f-1285e17d03ba 1 5 1667589330949 1667592930949 t f 1 \N f
6 $temp-2eb4b1d2-65da-4161-97cf-762d9bb4fe93 1 6 1667589332577 1667592932577 t f 1 \N f
7 $temp-4ddefd63-fdcb-43d6-8740-be334338e846 1 7 1667589334341 1667592934341 t f 1 \N f
8 $temp-7c67ad17-2b65-49b2-a9ce-e018689ce7f2 1 8 1667589335850 1667592935850 t f 1 \N f
9 $temp-64f33f3a-8551-49da-b36c-e4ab3a47c247 1 9 1667589337525 1667592937525 t f 1 \N f
10 manifestlist 1 10 1667589337924 \N f f 1 \N f
11 latest 155 11 1687976145896 \N f f 1 \N f
1 latest 1 1 1667589234345 1690479044462 f f 1 \N f
12 $temp-224d812a-ca19-4ccb-92c2-9a04a21924a7 1 1 1690479048789 1690479348789 t f 1 \N f
13 latest 1 1 1690479048814 1690479058287 f t 1 \N f
15 $temp-cfb6beff-645a-479d-be37-73ce2631b3c7 1 1 1690479070649 1690479370649 t f 1 \N f
14 latest 1 12 1690479058287 1690479070667 f f 1 \N f
16 latest 1 1 1690479070667 \N f t 1 \N f
\.
@ -8295,7 +8300,7 @@ SELECT pg_catalog.setval('public.logentry_id_seq', 1, false);
-- Name: logentrykind_id_seq; Type: SEQUENCE SET; Schema: public; Owner: quay
--
SELECT pg_catalog.setval('public.logentrykind_id_seq', 115, true);
SELECT pg_catalog.setval('public.logentrykind_id_seq', 120, true);
--

View File

@ -632,7 +632,53 @@ export function useLogDescriptions() {
oauth_token_assigned: function (metadata) {
return `OAuth token assigned to user ${metadata.assigned_user} by ${metadata.assigning_user} for application ${metadata.application}`;
},
export_logs_success: function (metadata: Metadata) {
if (metadata.repo) {
if (metadata.url) {
return `Logs export queued for delivery: id ${metadata.export_id}, url: ${metadata.url}, repository: ${metadata.repo}`;
} else if (metadata.email) {
return `Logs export queued for delivery: id ${
metadata.export_id
}, email: ${obfuscate_email(metadata.email)}, repository: ${
metadata.repo
}`;
} else {
return `Logs export queued for delivery: id ${
metadata.export_id
}, url: ${metadata.url}, email: ${obfuscate_email(
metadata.email,
)}, repository: ${metadata.repo}`;
}
} else {
if (metadata.url) {
return `User/organization logs export queued for delivery: id ${metadata.export_id}, url: ${metadata.url}`;
} else if (metadata.email) {
return `User/organization logs export queued for delivery: id ${
metadata.export_id
}, email: ${obfuscate_email(metadata.email)}`;
} else {
return `User/organization logs export queued for delivery: id ${
metadata.export_id
}, url: ${metadata.url}, email: ${obfuscate_email(metadata.email)}`;
}
}
},
export_logs_failure: function (metadata: Metadata) {
return `Export logs failure: ${metadata.error}, ${
metadata.repo ? `requested repository: ${metadata.repo}` : ''
}`;
},
};
return descriptions;
}
function obfuscate_email(email: Array) {
const email_array = email.split('@');
return (
email_array[0].substring(0, 2) +
'*'.repeat(email_array[0].length - 2) +
'@' +
email_array[1]
);
}

View File

@ -243,4 +243,6 @@ export const logKinds = {
oauth_token_assigned: 'OAuth token assigned',
enable_team_sync: 'Enable Team Sync',
disable_team_sync: 'Disable Team Sync',
export_logs_success: 'Export logs queued for delivery',
export_logs_failure: 'Export logs failure',
};

View File

@ -7,7 +7,15 @@ import {
Split,
SplitItem,
} from '@patternfly/react-core';
import {Table, Tbody, Td, Th, Thead, Tr} from '@patternfly/react-table';
import {
Table,
TableText,
Tbody,
Td,
Th,
Thead,
Tr,
} from '@patternfly/react-table';
import {useInfiniteQuery} from '@tanstack/react-query';
import RequestError from 'src/components/errors/RequestError';
import {getLogs} from 'src/hooks/UseUsageLogs';
@ -92,17 +100,21 @@ export function UsageLogsTable(props: UsageLogsTableProps) {
<Table variant="compact" borders={false} style={{margin: '20px'}}>
<Thead>
<Tr>
<Th>Date & Time</Th>
<Th width={15}>Date & Time</Th>
<Th>Description</Th>
<Th>Performed by</Th>
<Th>IP address</Th>
<Th>IP Address</Th>
</Tr>
</Thead>
<Tbody>
{logs.pages.map((logPage: any) =>
logPage.logs.map((log: any, index: number) => (
<Tr key={index}>
<Td>{new Date(log.datetime).toLocaleString()}</Td>
<Td>
<TableText wrapModifier="truncate">
{new Date(log.datetime).toLocaleString()}
</TableText>
</Td>
<Td>
{logDescriptions[log.kind]
? logDescriptions[log.kind](log.metadata)