Use the registry API for security scanning

when the storage engine doesn't support direct download url
This commit is contained in:
Jake Moshenko 2016-05-04 17:40:09 -04:00
parent 1ef7008d85
commit 9221a515de
9 changed files with 149 additions and 106 deletions

View file

@ -1,15 +1,23 @@
import logging
import requests
from flask import url_for
from urlparse import urljoin
from data.database import CloseForLongOperation
from data import model
from data.model.storage import get_storage_locations
from urlparse import urljoin
from util.secscan.validator import SecurityConfigValidator
from util.security.registry_jwt import generate_jwt_object, build_context_and_subject
from util import get_app_url
TOKEN_VALIDITY_LIFETIME_S = 60 # Amount of time the security scanner has to call the layer URL
logger = logging.getLogger(__name__)
class AnalyzeLayerException(Exception):
""" Exception raised when a layer fails to analyze due to a *client-side* issue. """
@ -26,13 +34,14 @@ _API_METHOD_PING = 'metrics'
class SecurityScannerAPI(object):
""" Helper class for talking to the Security Scan service (Clair). """
def __init__(self, config, storage, client=None, skip_validation=False):
def __init__(self, app, config, storage, client=None, skip_validation=False):
if not skip_validation:
config_validator = SecurityConfigValidator(config)
if not config_validator.valid():
logger.warning('Invalid config provided to SecurityScannerAPI')
return
self._app = app
self._config = config
self._client = client or config['HTTPCLIENT']
self._storage = storage
@ -40,9 +49,10 @@ class SecurityScannerAPI(object):
self._target_version = config.get('SECURITY_SCANNER_ENGINE_VERSION_TARGET', 2)
def _get_image_url(self, image):
""" Gets the download URL for an image and if the storage doesn't exist,
returns None.
def _get_image_url_and_auth(self, image):
""" Returns a tuple of the url and the auth header value that must be used
to fetch the layer data itself. If the image can't be addressed, we return
None.
"""
path = model.storage.get_layer_path(image.storage)
locations = self._default_storage_locations
@ -53,47 +63,60 @@ class SecurityScannerAPI(object):
if not locations or not self._storage.exists(locations, path):
logger.warning('Could not find a valid location to download layer %s.%s out of %s',
image.docker_image_id, image.storage.uuid, locations)
return None
return None, None
uri = self._storage.get_direct_download_url(locations, path)
auth_header = None
if uri is None:
# Handle local storage.
local_storage_enabled = False
for storage_type, _ in self._config.get('DISTRIBUTED_STORAGE_CONFIG', {}).values():
if storage_type == 'LocalStorage':
local_storage_enabled = True
# Use the registry API instead, with a signed JWT giving access
repo_name = image.repository.name
namespace_name = image.repository.namespace_user.username
repository_and_namespace = '/'.join([namespace_name, repo_name])
if local_storage_enabled:
# TODO: fix to use the proper local storage path.
uri = path
else:
logger.warning('Could not get image URL and local storage was not enabled')
return None
# Generate the JWT which will authorize this
audience = 'security_scanner'
context, subject = build_context_and_subject(None, None, None)
access = [{
'type': 'repository',
'name': repository_and_namespace,
'actions': ['pull'],
}]
auth_jwt = generate_jwt_object(audience, subject, context, access, TOKEN_VALIDITY_LIFETIME_S,
self._config)
auth_header = 'Bearer: {}'.format(auth_jwt)
return uri
with self._app.test_request_context('/'):
relative_layer_url = url_for('v2.download_blob', repository=repository_and_namespace,
digest=image.storage.content_checksum)
uri = urljoin(get_app_url(self._config), relative_layer_url)
return uri, auth_header
def _new_analyze_request(self, image):
""" Create the request body to submit the given image for analysis. If the image's URL cannot
be found, returns None.
"""
url = self._get_image_url(image)
url, auth_header = self._get_image_url_and_auth(image)
if url is None:
return None
request = {
'Layer': {
'Name': '%s.%s' % (image.docker_image_id, image.storage.uuid),
'Path': url,
'Format': 'Docker'
}
layer_request = {
'Name': '%s.%s' % (image.docker_image_id, image.storage.uuid),
'Path': url,
'Format': 'Docker',
}
if image.parent.docker_image_id and image.parent.storage.uuid:
request['Layer']['ParentName'] = '%s.%s' % (image.parent.docker_image_id,
image.parent.storage.uuid)
if auth_header is not None:
layer_request['Authorization'] = auth_header
return request
if image.parent.docker_image_id and image.parent.storage.uuid:
layer_request['ParentName'] = '%s.%s' % (image.parent.docker_image_id,
image.parent.storage.uuid)
return {
'Layer': layer_request,
}
def ping(self):