Merge pull request #2637 from jzelinskie/audit-apps

Audit Logs for Apps
This commit is contained in:
Jimmy Zelinskie 2017-05-16 17:06:25 -04:00 committed by GitHub
commit 702cdf59ff
12 changed files with 96 additions and 26 deletions

View file

@ -13,6 +13,7 @@ from flask import jsonify, request
from auth.auth_context import get_authenticated_user
from auth.decorators import process_auth
from auth.permissions import (CreateRepositoryPermission, ModifyRepositoryPermission)
from data.interfaces.appr import oci_app_model as model
from endpoints.appr import (appr_bp, require_app_repo_read, require_app_repo_write)
from endpoints.appr.cnr_backend import Blob, Channel, Package, User
from endpoints.appr.decorators import disallow_for_image_repository
@ -102,6 +103,8 @@ def list_packages():
def delete_package(namespace, package_name, release, media_type):
reponame = repo_name(namespace, package_name)
result = cnr_registry.delete_package(reponame, release, media_type, package_class=Package)
model.log_action('delete_tag', namespace, repo_name=package_name,
metadata={'release': release, 'mediatype': media_type})
return jsonify(result)
@ -136,7 +139,7 @@ def show_package_releases(namespace, package_name):
@process_auth
@require_app_repo_read
@anon_protect
def show_package_releasse_manifests(namespace, package_name, release):
def show_package_release_manifests(namespace, package_name, release):
reponame = repo_name(namespace, package_name)
result = cnr_registry.show_package_manifests(reponame, release, package_class=Package)
return jsonify(result)
@ -153,6 +156,8 @@ def pull(namespace, package_name, release, media_type):
reponame = repo_name(namespace, package_name)
logger.info("pull %s", reponame)
data = cnr_registry.pull(reponame, release, media_type, Package, blob_class=Blob)
model.log_action('pull_repo', namespace, repo_name=package_name,
metadata={'release': release, 'mediatype': media_type})
return _pull(data)
@ -178,6 +183,7 @@ def push(namespace, package_name):
{"package": reponame,
"scopes": ['create']})
Package.create_repository(reponame, private, owner)
model.log_action('create_repo', namespace, repo_name=package_name)
if not ModifyRepositoryPermission(namespace, package_name).can():
raise Forbidden("Unauthorized access for: %s" % reponame,
@ -194,6 +200,8 @@ def push(namespace, package_name):
blob = Blob(reponame, values['blob'])
app_release = cnr_registry.push(reponame, release_version, media_type, blob, force,
package_class=Package, user=owner, visibility=private)
model.log_action('push_repo', namespace, repo_name=package_name,
metadata={'release': release_version})
return jsonify(app_release)
@ -246,6 +254,8 @@ def add_channel_release(namespace, package_name, channel_name, release):
reponame = repo_name(namespace, package_name)
result = cnr_registry.add_channel_release(reponame, channel_name, release, channel_class=Channel,
package_class=Package)
model.log_action('create_tag', namespace, repo_name=package_name,
metadata={'channel': channel_name, 'release': release})
return jsonify(result)
@ -254,13 +264,13 @@ def _check_channel_name(channel_name, release=None):
logger.debug('Found invalid channel name CNR add channel release: %s', channel_name)
raise InvalidUsage("Found invalid channelname %s" % release,
{'name': channel_name,
"release": release})
'release': release})
if release is not None and not TAG_REGEX.match(release):
logger.debug('Found invalid release name CNR add channel release: %s', release)
raise InvalidUsage("Found invalid channel release name %s" % release,
raise InvalidUsage('Found invalid channel release name %s' % release,
{'name': channel_name,
"release": release})
'release': release})
@appr_bp.route(
@ -275,6 +285,8 @@ def delete_channel_release(namespace, package_name, channel_name, release):
reponame = repo_name(namespace, package_name)
result = cnr_registry.delete_channel_release(reponame, channel_name, release,
channel_class=Channel, package_class=Package)
model.log_action('delete_tag', namespace, repo_name=package_name,
metadata={'channel': channel_name, 'release': release})
return jsonify(result)
@ -289,4 +301,6 @@ def delete_channel(namespace, package_name, channel_name):
_check_channel_name(channel_name)
reponame = repo_name(namespace, package_name)
result = cnr_registry.delete_channel(reponame, channel_name, channel_class=Channel)
model.log_action('delete_tag', namespace, repo_name=package_name,
metadata={'channel': channel_name})
return jsonify(result)

View file

@ -1,92 +0,0 @@
import logging
import random
from urlparse import urlparse
from flask import request
from app import analytics, userevents
from data import model
from auth.registry_jwt_auth import get_granted_entity
from auth.auth_context import (get_authenticated_user, get_validated_token,
get_validated_oauth_token)
logger = logging.getLogger(__name__)
def track_and_log(event_name, repo_obj, analytics_name=None, analytics_sample=1, **kwargs):
repo_name = repo_obj.name
namespace_name = repo_obj.namespace_name,
metadata = {
'repo': repo_name,
'namespace': namespace_name,
}
metadata.update(kwargs)
analytics_id = 'anonymous'
authenticated_oauth_token = get_validated_oauth_token()
authenticated_user = get_authenticated_user()
authenticated_token = get_validated_token() if not authenticated_user else None
if not authenticated_user and not authenticated_token and not authenticated_oauth_token:
entity = get_granted_entity()
if entity:
authenticated_user = entity.user
authenticated_token = entity.token
authenticated_oauth_token = entity.oauth
logger.debug('Logging the %s to Mixpanel and the log system', event_name)
if authenticated_oauth_token:
metadata['oauth_token_id'] = authenticated_oauth_token.id
metadata['oauth_token_application_id'] = authenticated_oauth_token.application.client_id
metadata['oauth_token_application'] = authenticated_oauth_token.application.name
analytics_id = 'oauth:{0}'.format(authenticated_oauth_token.id)
elif authenticated_user:
metadata['username'] = authenticated_user.username
analytics_id = authenticated_user.username
elif authenticated_token:
metadata['token'] = authenticated_token.friendly_name
metadata['token_code'] = authenticated_token.code
if authenticated_token.kind:
metadata['token_type'] = authenticated_token.kind.name
analytics_id = 'token:{0}'.format(authenticated_token.code)
else:
metadata['public'] = True
analytics_id = 'anonymous'
# Publish the user event (if applicable)
logger.debug('Checking publishing %s to the user events system', event_name)
if authenticated_user and not authenticated_user.robot:
logger.debug('Publishing %s to the user events system', event_name)
user_event_data = {
'action': event_name,
'repository': repo_name,
'namespace': namespace_name,
}
event = userevents.get_event(authenticated_user.username)
event.publish_event_data('docker-cli', user_event_data)
# Save the action to mixpanel.
if random.random() < analytics_sample:
if analytics_name is None:
analytics_name = event_name
logger.debug('Logging the %s to Mixpanel', analytics_name)
request_parsed = urlparse(request.url_root)
extra_params = {
'repository': '%s/%s' % (namespace_name, repo_name),
'user-agent': request.user_agent.string,
'hostname': request_parsed.hostname,
}
analytics.track(analytics_id, analytics_name, extra_params)
# Log the action to the database.
logger.debug('Logging the %s to logs system', event_name)
model.log.log_action(event_name, namespace_name, performer=authenticated_user,
ip=request.remote_addr, metadata=metadata, repository=repo_obj)
logger.debug('Track and log of %s complete', event_name)

View file

@ -6,7 +6,6 @@ from functools import wraps
from flask import request, make_response, jsonify, session
from data.interfaces.v1 import pre_oci_model as model
from app import authentication, userevents, metric_queue
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
from auth.decorators import process_auth
@ -14,13 +13,14 @@ from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
ReadRepositoryPermission, CreateRepositoryPermission,
repository_read_grant, repository_write_grant)
from auth.signedgrant import generate_signed_token
from data.interfaces.v1 import pre_oci_model as model
from endpoints.common import parse_repository_name
from endpoints.decorators import anon_protect, anon_allowed
from endpoints.notificationhelper import spawn_notification
from endpoints.v1 import v1_bp
from util.audit import track_and_log
from util.http import abort
from util.names import REPOSITORY_NAME_REGEX
from endpoints.common import parse_repository_name
from endpoints.v1 import v1_bp
from endpoints.trackhelper import track_and_log
from endpoints.notificationhelper import spawn_notification
from endpoints.decorators import anon_protect, anon_allowed
logger = logging.getLogger(__name__)

View file

@ -4,7 +4,6 @@ import json
from flask import abort, request, jsonify, make_response, session
from util.names import TAG_ERROR, TAG_REGEX
from auth.decorators import process_auth
from auth.permissions import (ReadRepositoryPermission,
ModifyRepositoryPermission)
@ -13,7 +12,8 @@ from data.interfaces.v1 import pre_oci_model as model
from endpoints.common import parse_repository_name
from endpoints.decorators import anon_protect
from endpoints.v1 import v1_bp
from endpoints.trackhelper import track_and_log
from util.audit import track_and_log
from util.names import TAG_ERROR, TAG_REGEX
logger = logging.getLogger(__name__)

View file

@ -15,11 +15,11 @@ from endpoints.decorators import anon_protect
from endpoints.v2 import v2_bp, require_repo_read, require_repo_write
from endpoints.v2.errors import (BlobUnknown, ManifestInvalid, ManifestUnknown, TagInvalid,
NameInvalid)
from endpoints.trackhelper import track_and_log
from endpoints.notificationhelper import spawn_notification
from image.docker import ManifestException
from image.docker.schema1 import DockerSchema1Manifest, DockerSchema1ManifestBuilder
from image.docker.schema2 import DOCKER_SCHEMA2_CONTENT_TYPES
from util.audit import track_and_log
from util.names import VALID_TAG_PATTERN
from util.registry.replication import queue_replication_batch
from util.validation import is_json

View file

@ -13,11 +13,11 @@ from data import database
from data.interfaces.verbs import pre_oci_model as model
from endpoints.common import route_show_if, parse_repository_name
from endpoints.decorators import anon_protect
from endpoints.trackhelper import track_and_log
from endpoints.v2.blob import BLOB_DIGEST_ROUTE
from image.appc import AppCImageFormatter
from image.docker.squashed import SquashedDockerImageFormatter
from storage import Storage
from util.audit import track_and_log
from util.http import exact_abort
from util.registry.filelike import wrap_with_handler
from util.registry.queuefile import QueueFile