Merge pull request #858 from coreos-inc/vulnerability-tool-updateui
New Quay Sec UI and fix some small bugs
This commit is contained in:
commit
6970b0685e
13 changed files with 307 additions and 115 deletions
|
@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
|
|||
def _call_security_api(relative_url, *args, **kwargs):
|
||||
""" Issues an HTTP call to the sec API at the given relative URL. """
|
||||
try:
|
||||
response = secscan_api.call(relative_url, body=None, *args, **kwargs)
|
||||
response = secscan_api.call(relative_url, None, *args, **kwargs)
|
||||
except requests.exceptions.Timeout:
|
||||
raise DownstreamIssue(payload=dict(message='API call timed out'))
|
||||
except requests.exceptions.ConnectionError:
|
||||
|
@ -40,32 +40,32 @@ def _call_security_api(relative_url, *args, **kwargs):
|
|||
|
||||
|
||||
@show_if(features.SECURITY_SCANNER)
|
||||
@resource('/v1/repository/<repopath:repository>/tag/<tag>/vulnerabilities')
|
||||
@resource('/v1/repository/<repopath:repository>/image/<imageid>/vulnerabilities')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
@path_param('tag', 'The name of the tag')
|
||||
class RepositoryTagVulnerabilities(RepositoryParamResource):
|
||||
""" Operations for managing the vulnerabilities in a repository tag. """
|
||||
@path_param('imageid', 'The image ID')
|
||||
class RepositoryImageVulnerabilities(RepositoryParamResource):
|
||||
""" Operations for managing the vulnerabilities in a repository image. """
|
||||
|
||||
@require_repo_read
|
||||
@nickname('getRepoTagVulnerabilities')
|
||||
@nickname('getRepoImageVulnerabilities')
|
||||
@parse_args
|
||||
@query_param('minimumPriority', 'Minimum vulnerability priority', type=str,
|
||||
default='Low')
|
||||
def get(self, args, namespace, repository, tag):
|
||||
def get(self, args, namespace, repository, imageid):
|
||||
""" Fetches the vulnerabilities (if any) for a repository tag. """
|
||||
try:
|
||||
tag_image = model.tag.get_tag_image(namespace, repository, tag)
|
||||
except model.DataModelException:
|
||||
repo_image = model.image.get_repo_image(namespace, repository, imageid)
|
||||
if repo_image is None:
|
||||
raise NotFound()
|
||||
|
||||
if not tag_image.security_indexed:
|
||||
logger.debug('Image %s for tag %s under repository %s/%s not security indexed',
|
||||
tag_image.docker_image_id, tag, namespace, repository)
|
||||
if not repo_image.security_indexed:
|
||||
logger.debug('Image %s under repository %s/%s not security indexed',
|
||||
repo_image.docker_image_id, namespace, repository)
|
||||
return {
|
||||
'security_indexed': False
|
||||
}
|
||||
|
||||
data = _call_security_api('layers/%s/vulnerabilities', tag_image.docker_image_id,
|
||||
layer_id = '%s.%s' % (repo_image.docker_image_id, repo_image.storage.uuid)
|
||||
data = _call_security_api('layers/%s/vulnerabilities', layer_id,
|
||||
minimumPriority=args.minimumPriority)
|
||||
|
||||
return {
|
||||
|
@ -94,7 +94,8 @@ class RepositoryImagePackages(RepositoryParamResource):
|
|||
'security_indexed': False
|
||||
}
|
||||
|
||||
data = _call_security_api('layers/%s/packages/diff', repo_image.docker_image_id)
|
||||
layer_id = '%s.%s' % (repo_image.docker_image_id, repo_image.storage.uuid)
|
||||
data = _call_security_api('layers/%s/packages', layer_id)
|
||||
|
||||
return {
|
||||
'security_indexed': True,
|
||||
|
|
|
@ -21,5 +21,5 @@ def secscan_notification():
|
|||
if not layer_ids:
|
||||
return make_response('Okay')
|
||||
|
||||
secscan_notification_queue.put(data['Name'], json.dumps(data))
|
||||
secscan_notification_queue.put(['notification', data['Name']], json.dumps(data))
|
||||
return make_response('Okay')
|
||||
|
|
|
@ -85,46 +85,34 @@
|
|||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .fa-flag {
|
||||
.repo-panel-tags-element .security-scan-col span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .vuln-name {
|
||||
|
||||
.repo-panel-tags-element .security-scan-col i.fa {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .vuln-description {
|
||||
color: #aaa;
|
||||
font-size: 10px;
|
||||
white-space: normal;
|
||||
.repo-panel-tags-element .security-scan-col .scanning {
|
||||
color: #9B9B9B;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .fa-flag.None {
|
||||
color: #00CA00;
|
||||
.repo-panel-tags-element .security-scan-col .no-vulns a {
|
||||
color: #2FC98E;
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .fa-flag.Medium {
|
||||
color: orange;
|
||||
.repo-panel-tags-element .security-scan-col .vuln-link,
|
||||
.repo-panel-tags-element .security-scan-col .vuln-link span {
|
||||
text-decoration: none !important
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .fa-flag.High {
|
||||
color: red;
|
||||
.repo-panel-tags-element .security-scan-col .has-vulns.Critical .highest-vuln,
|
||||
.repo-panel-tags-element .security-scan-col .has-vulns.Defcon1 .highest-vuln {
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .vuln-dropdown ul {
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
@keyframes flickerAnimation { /* flame pulses */
|
||||
0% { opacity:1; }
|
||||
50% { opacity:0; }
|
||||
100% { opacity:1; }
|
||||
}
|
||||
|
||||
.repo-panel-tags-element .fa-flag.Critical {
|
||||
color: red;
|
||||
opacity:1;
|
||||
animation: flickerAnimation 1s infinite;
|
||||
.repo-panel-tags-element .other-vulns {
|
||||
color: black;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
|
|
|
@ -15,4 +15,35 @@
|
|||
margin-right: 10px;
|
||||
margin-bottom: 10px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.filter-box.floating {
|
||||
float: right;
|
||||
min-width: 300px;
|
||||
margin-top: 0px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.filter-box.floating .filter-message {
|
||||
position: absolute;
|
||||
left: -200px;
|
||||
top: 7px;
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.filter-box.floating {
|
||||
float: none;
|
||||
width: 100%;
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.filter-box.floating .form-control {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.filter-box.floating .filter-message {
|
||||
display: none;
|
||||
}
|
||||
}
|
19
static/css/directives/ui/vulnerability-priority-view.css
Normal file
19
static/css/directives/ui/vulnerability-priority-view.css
Normal file
|
@ -0,0 +1,19 @@
|
|||
.vulnerability-priority-view-element i.fa {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.vulnerability-priority-view-element.Unknown,
|
||||
.vulnerability-priority-view-element.Low,
|
||||
.vulnerability-priority-view-element.Negligable {
|
||||
color: #9B9B9B;
|
||||
}
|
||||
|
||||
.vulnerability-priority-view-element.Medium {
|
||||
color: #FCA657;
|
||||
}
|
||||
|
||||
.vulnerability-priority-view-element.High,
|
||||
.vulnerability-priority-view-element.Critical,
|
||||
.vulnerability-priority-view-element.Defcon1 {
|
||||
color: #D64456;
|
||||
}
|
|
@ -23,3 +23,23 @@
|
|||
.image-view .co-tab-content h3 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.image-view .fa-bug {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.image-view .co-filter-box {
|
||||
float: right;
|
||||
min-width: 300px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.image-view .co-filter-box .current-filtered {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.image-view .co-filter-box input {
|
||||
display: inline-block;
|
||||
}
|
|
@ -81,17 +81,17 @@
|
|||
style="min-width: 120px;">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('last_modified_datetime')">Last Modified</a>
|
||||
</td>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('security_scanned', options.predicate, options.reverse)"
|
||||
style="min-width: 120px;"
|
||||
quay-require="['SECURITY_SCANNER']">
|
||||
Security Scan
|
||||
</td>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('size', options.predicate, options.reverse)"
|
||||
style="min-width: 62px;">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('size')">Size</a>
|
||||
</td>
|
||||
<td ng-class="tablePredicateClass('vuln_level', options.predicate, options.reverse)"
|
||||
style="width: 60px;">
|
||||
<a href="javascript:void(0)" ng-click="orderBy('vuln_level')">
|
||||
<i class="fa fa-flag"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td class="hidden-xs"
|
||||
ng-class="tablePredicateClass('image_id', options.predicate, options.reverse)"
|
||||
colspan="{{ imageTracks.length + 1 }}"
|
||||
|
@ -113,43 +113,51 @@
|
|||
<span am-time-ago="tag.last_modified" bo-if="tag.last_modified"></span>
|
||||
<span bo-if="!tag.last_modified">Unknown</span>
|
||||
</td>
|
||||
<td class="hidden-xs" bo-text="tag.size | bytes"></td>
|
||||
<td>
|
||||
<td quay-require="['SECURITY_SCANNER']" class="security-scan-col">
|
||||
<span class="cor-loader-inline" ng-if="getTagVulnerabilities(tag).loading"></span>
|
||||
<span ng-if="!getTagVulnerabilities(tag).loading">
|
||||
<i class="fa fa-flag-o" ng-if="!getTagVulnerabilities(tag).indexed"
|
||||
data-title="Image is currently being checked for vulnerabilities"
|
||||
bs-tooltip>
|
||||
</i>
|
||||
<i class="fa fa-flag None"
|
||||
ng-if="getTagVulnerabilities(tag).indexed && !getTagVulnerabilities(tag).hasVulnerabilities"
|
||||
data-title="Image has no vulnerabilities"
|
||||
bs-tooltip>
|
||||
</i>
|
||||
<div class="dropdown vuln-dropdown" style="text-align: left;"
|
||||
ng-if="getTagVulnerabilities(tag).indexed && getTagVulnerabilities(tag).hasVulnerabilities">
|
||||
<i class="fa fa-flag"
|
||||
data-title="Image has vulnerabilities"
|
||||
data-toggle="dropdown"
|
||||
ng-class="getTagVulnerabilities(tag).highestVulnerability.Priority"
|
||||
bs-tooltip>
|
||||
</i>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li ng-repeat="vuln in getTagVulnerabilities(tag).vulnerabilities">
|
||||
<a href="{{ vuln.Link }}" target="_new">
|
||||
<div class="vuln-name">
|
||||
<i class="fa fa-flag" bo-class="vuln.Priority"></i>
|
||||
{{ vuln.ID }}
|
||||
</div>
|
||||
<div class="vuln-description">
|
||||
{{ vuln.Description }}
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- Scanning -->
|
||||
<span class="scanning" ng-if="!getTagVulnerabilities(tag).security_indexed"
|
||||
data-title="The image for this tag is queued to be scanned for vulnerabilities"
|
||||
bs-tooltip>Queued for scan</span>
|
||||
|
||||
<!-- No Vulns -->
|
||||
<span class="no-vulns"
|
||||
ng-if="getTagVulnerabilities(tag).security_indexed && !getTagVulnerabilities(tag).hasVulnerabilities"
|
||||
data-title="The image for this tag has no vulnerabilities as found in our database"
|
||||
bs-tooltip
|
||||
bindonce>
|
||||
<a bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}?tab=security">
|
||||
<i class="fa fa-check-circle"></i>
|
||||
Passed
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<!-- Vulns -->
|
||||
<span ng-if="getTagVulnerabilities(tag).security_indexed && getTagVulnerabilities(tag).hasVulnerabilities"
|
||||
ng-class="getTagVulnerabilities(tag).highestVulnerability.Priority"
|
||||
class="has-vulns" bindonce>
|
||||
<a class="vuln-link" bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}?tab=security"
|
||||
data-title="The image for this tag has {{ getTagVulnerabilities(tag).highestVulnerability.Count }} {{ getTagVulnerabilities(tag).highestVulnerability.Priority }} level vulnerabilities"
|
||||
bs-tooltip>
|
||||
<span class="highest-vuln">
|
||||
<span class="vulnerability-priority-view" priority="getTagVulnerabilities(tag).highestVulnerability.Priority">
|
||||
{{ getTagVulnerabilities(tag).highestVulnerability.Count }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span ng-if="getTagVulnerabilities(tag).vulnerabilities.length - getTagVulnerabilities(tag).highestVulnerability.Count > 0"
|
||||
class="other-vulns">
|
||||
+ {{ getTagVulnerabilities(tag).vulnerabilities.length - getTagVulnerabilities(tag).highestVulnerability.Count }} others
|
||||
</span>
|
||||
</a>
|
||||
<a bo-href-i="/repository/{{ repository.namespace }}/{{ repository.name }}/image/{{ tag.image_id }}?tab=security" style="display: inline-block; margin-left: 6px;">
|
||||
More Info
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="hidden-xs" bo-text="tag.size | bytes"></td>
|
||||
<td class="hidden-xs image-id-col">
|
||||
<span class="image-link" repository="repository" image-id="tag.image_id"></span>
|
||||
</td>
|
||||
|
|
5
static/directives/vulnerability-priority-view.html
Normal file
5
static/directives/vulnerability-priority-view.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<span class="vulnerability-priority-view-element" ng-class="priority">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<span ng-transclude/>
|
||||
{{ priority }}
|
||||
</span>
|
|
@ -34,8 +34,8 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
$scope.tagHistory = {};
|
||||
$scope.tagActionHandler = null;
|
||||
$scope.showingHistory = false;
|
||||
$scope.tagsPerPage = 50;
|
||||
$scope.tagVulnerabilities = {};
|
||||
$scope.tagsPerPage = 25;
|
||||
$scope.imageVulnerabilities = {};
|
||||
|
||||
var setTagState = function() {
|
||||
if (!$scope.repository || !$scope.selectedTags) { return; }
|
||||
|
@ -57,7 +57,7 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
|
||||
allTags.push(tagInfo);
|
||||
|
||||
if (!$scope.options.tagFilter || tag.indexOf($scope.options.tagFilter) >= 0 ||
|
||||
if (!$scope.options.tagFilter || tagfOf($scope.options.tagFilter) >= 0 ||
|
||||
tagInfo.image_id.indexOf($scope.options.tagFilter) >= 0) {
|
||||
tags.push(tagInfo);
|
||||
}
|
||||
|
@ -150,51 +150,66 @@ angular.module('quay').directive('repoPanelTags', function () {
|
|||
setTagState();
|
||||
});
|
||||
|
||||
$scope.loadTagVulnerabilities = function(tag, tagData) {
|
||||
$scope.loadImageVulnerabilities = function(image_id, imageData) {
|
||||
var params = {
|
||||
'tag': tag.name,
|
||||
'imageid': image_id,
|
||||
'repository': $scope.repository.namespace + '/' + $scope.repository.name,
|
||||
};
|
||||
|
||||
ApiService.getRepoTagVulnerabilities(null, params).then(function(resp) {
|
||||
tagData.indexed = resp.security_indexed;
|
||||
tagData.loading = false;
|
||||
ApiService.getRepoImageVulnerabilities(null, params).then(function(resp) {
|
||||
imageData.security_indexed = resp.security_indexed;
|
||||
imageData.loading = false;
|
||||
|
||||
if (resp.security_indexed) {
|
||||
tagData.hasVulnerabilities = !!resp.data.Vulnerabilities.length;
|
||||
tagData.vulnerabilities = resp.data.Vulnerabilities;
|
||||
if (imageData.security_indexed) {
|
||||
var vulnerabilities = resp.data.Vulnerabilities;
|
||||
|
||||
imageData.hasVulnerabilities = !!vulnerabilities.length;
|
||||
imageData.vulnerabilities = vulnerabilities;
|
||||
|
||||
var highest = {
|
||||
'Priority': 'Unknown',
|
||||
'Count': 0,
|
||||
'index': 100000
|
||||
};
|
||||
|
||||
var highest = null;
|
||||
resp.data.Vulnerabilities.forEach(function(v) {
|
||||
if (highest == null ||
|
||||
VulnerabilityService.LEVELS[v.Priority].index < VulnerabilityService.LEVELS[highest.Priority].index) {
|
||||
highest = v;
|
||||
if (VulnerabilityService.LEVELS[v.Priority].index < highest.index) {
|
||||
highest = {
|
||||
'Priority': v.Priority,
|
||||
'Count': 1,
|
||||
'index': VulnerabilityService.LEVELS[v.Priority].index
|
||||
}
|
||||
} else if (VulnerabilityService.LEVELS[v.Priority].index == highest.index) {
|
||||
highest['Count']++;
|
||||
}
|
||||
});
|
||||
|
||||
tagData.highestVulnerability = highest;
|
||||
imageData.highestVulnerability = highest;
|
||||
}
|
||||
}, function() {
|
||||
tagData.loading = false;
|
||||
tagData.hasError = true;
|
||||
imageData.loading = false;
|
||||
imageData.hasError = true;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getTagVulnerabilities = function(tag) {
|
||||
return $scope.getImageVulnerabilities(tag.image_id);
|
||||
};
|
||||
|
||||
$scope.getImageVulnerabilities = function(image_id) {
|
||||
if (!$scope.repository) {
|
||||
return
|
||||
}
|
||||
|
||||
var tagName = tag.name;
|
||||
if (!$scope.tagVulnerabilities[tagName]) {
|
||||
$scope.tagVulnerabilities[tagName] = {
|
||||
if (!$scope.imageVulnerabilities[image_id]) {
|
||||
$scope.imageVulnerabilities[image_id] = {
|
||||
'loading': true
|
||||
};
|
||||
|
||||
$scope.loadTagVulnerabilities(tag, $scope.tagVulnerabilities[tagName]);
|
||||
$scope.loadImageVulnerabilities(image_id, $scope.imageVulnerabilities[image_id]);
|
||||
}
|
||||
|
||||
return $scope.tagVulnerabilities[tagName];
|
||||
return $scope.imageVulnerabilities[image_id];
|
||||
};
|
||||
|
||||
$scope.clearSelectedTags = function() {
|
||||
|
|
18
static/js/directives/ui/vulnerability-priority-view.js
Normal file
18
static/js/directives/ui/vulnerability-priority-view.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* An element which displays a priority triangle for vulnerabilities.
|
||||
*/
|
||||
angular.module('quay').directive('vulnerabilityPriorityView', function () {
|
||||
var directiveDefinitionObject = {
|
||||
priority: 0,
|
||||
templateUrl: '/static/directives/vulnerability-priority-view.html',
|
||||
replace: false,
|
||||
transclude: true,
|
||||
restrict: 'C',
|
||||
scope: {
|
||||
'priority': '=priority'
|
||||
},
|
||||
controller: function($scope, $element) {
|
||||
}
|
||||
};
|
||||
return directiveDefinitionObject;
|
||||
});
|
|
@ -10,11 +10,16 @@
|
|||
})
|
||||
}]);
|
||||
|
||||
function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService) {
|
||||
function ImageViewCtrl($scope, $routeParams, $rootScope, $timeout, ApiService, ImageMetadataService, VulnerabilityService, Features) {
|
||||
var namespace = $routeParams.namespace;
|
||||
var name = $routeParams.name;
|
||||
var imageid = $routeParams.image;
|
||||
|
||||
$scope.options = {
|
||||
'vulnFilter': '',
|
||||
'packageFilter': ''
|
||||
};
|
||||
|
||||
var loadImage = function() {
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
|
@ -41,7 +46,7 @@
|
|||
loadRepository();
|
||||
|
||||
$scope.downloadPackages = function() {
|
||||
if ($scope.packagesResource) { return; }
|
||||
if (!Features.SECURITY_SCANNER || $scope.packagesResource) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
|
@ -53,6 +58,26 @@
|
|||
});
|
||||
};
|
||||
|
||||
$scope.loadImageVulnerabilities = function() {
|
||||
if (!Features.SECURITY_SCANNER || $scope.vulnerabilitiesResource) { return; }
|
||||
|
||||
var params = {
|
||||
'repository': namespace + '/' + name,
|
||||
'imageid': imageid
|
||||
};
|
||||
|
||||
$scope.vulnerabilitiesResource = ApiService.getRepoImageVulnerabilitiesAsResource(params).get(function(resp) {
|
||||
$scope.vulerabilityInfo = resp;
|
||||
$scope.vulnerabilities = [];
|
||||
|
||||
resp.data.Vulnerabilities.forEach(function(vuln) {
|
||||
vuln_copy = jQuery.extend({}, vuln);
|
||||
vuln_copy['index'] = VulnerabilityService.LEVELS[vuln['Priority']]['index'];
|
||||
$scope.vulnerabilities.push(vuln_copy);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
$scope.downloadChanges = function() {
|
||||
if ($scope.changesResource) { return; }
|
||||
|
||||
|
|
|
@ -25,8 +25,14 @@
|
|||
tab-init="downloadChanges()">
|
||||
<i class="fa fa-code-fork"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Security Scan" tab-target="#security"
|
||||
tab-init="loadImageVulnerabilities()"
|
||||
quay-show="Features.SECURITY_SCANNER">
|
||||
<i class="fa fa-bug"></i>
|
||||
</span>
|
||||
<span class="cor-tab" tab-title="Packages" tab-target="#packages"
|
||||
tab-init="downloadPackages()">
|
||||
tab-init="downloadPackages()"
|
||||
quay-show="Features.SECURITY_SCANNER">
|
||||
<i class="fa ci-package"></i>
|
||||
</span>
|
||||
</div> <!-- /cor-tabs -->
|
||||
|
@ -58,9 +64,57 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security -->
|
||||
<div id="security" class="tab-pane" quay-require="['SECURITY_SCANNER']">
|
||||
<div class="resource-view" resource="vulnerabilitiesResource" error-message="'Could not load security information for image'">
|
||||
<div class="filter-box floating" collection="vulnerabilities" filter-model="options.vulnFilter" filter-name="Vulnerabilities" ng-if="vulerabilityInfo.security_indexed && vulnerabilities.length"></div>
|
||||
|
||||
<h3>Image Security</h3>
|
||||
<div class="empty" ng-if="!vulerabilityInfo.security_indexed">
|
||||
<div class="empty-primary-msg">This image has not been indexed yet</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Please try again in a few minutes.
|
||||
</div>
|
||||
</div>
|
||||
<div class="empty" ng-if="vulerabilityInfo.security_indexed && !vulnerabilities.length">
|
||||
<div class="empty-primary-msg">This image contains no recognized security vulnerabilities</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Quay currently indexes Debian, Red Hat and Ubuntu packages.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="vulerabilityInfo.security_indexed && vulnerabilities.length">
|
||||
<table class="co-table">
|
||||
<thead>
|
||||
<td style="width: 200px;">Vulnerability</td>
|
||||
<td style="width: 200px;">Priority</td>
|
||||
<td>Description</td>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="vulnerability in vulnerabilities | filter:options.vulnFilter | orderBy:'index'">
|
||||
<td><a href="{{ vulnerability.Link }}" target="_blank">{{ vulnerability.ID }}</a></td>
|
||||
<td>
|
||||
<span class="vulnerability-priority-view" priority="vulnerability.Priority"></span>
|
||||
<td>{{ vulnerability.Description }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="empty" ng-if="(vulnerabilities | filter:options.vulnFilter).length == 0"
|
||||
style="margin-top: 20px;">
|
||||
<div class="empty-primary-msg">No matching vulnerabilities found</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Please adjust your filter above.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Packages -->
|
||||
<div id="packages" class="tab-pane">
|
||||
<div id="packages" class="tab-pane" quay-require="['SECURITY_SCANNER']">
|
||||
<div class="resource-view" resource="packagesResource" error-message="'Could not load image packages'">
|
||||
<div class="filter-box floating" collection="packages.data.Packages" filter-model="options.packageFilter" filter-name="Packages" ng-if="packages.security_indexed && packages.data.Packages.length"></div>
|
||||
|
||||
<h3>Image Packages</h3>
|
||||
<div class="empty" ng-if="!packages.security_indexed">
|
||||
<div class="empty-primary-msg">This image has not been indexed yet</div>
|
||||
|
@ -75,19 +129,27 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table" ng-if="packages.security_indexed && packages.data.Packages.length">
|
||||
<table class="co-table" ng-if="packages.security_indexed && packages.data.Packages.length">
|
||||
<thead>
|
||||
<th>Package Name</th>
|
||||
<th>Package Version</th>
|
||||
<th>OS</th>
|
||||
<td>Package Name</td>
|
||||
<td>Package Version</td>
|
||||
<td>OS</td>
|
||||
</thead>
|
||||
|
||||
<tr ng-repeat="package in packages.data.Packages | orderBy:'Name'">
|
||||
<tr ng-repeat="package in packages.data.Packages | filter:options.packageFilter | orderBy:'Name'">
|
||||
<td>{{ package.Name }}</td>
|
||||
<td>{{ package.Version }}</td>
|
||||
<td>{{ package.OS }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="empty" ng-if="(packages.data.Packages | filter:options.packageFilter).length == 0"
|
||||
style="margin-top: 20px;">
|
||||
<div class="empty-primary-msg">No matching packages found</div>
|
||||
<div class="empty-secondary-msg">
|
||||
Please adjust your filter above.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -256,7 +256,7 @@ class SecurityWorker(Worker):
|
|||
# callback code, etc.
|
||||
try:
|
||||
logger.debug('Loading vulnerabilities for layer %s', img['image_id'])
|
||||
response = secscan_api.call('layers/%s/vulnerabilities', request['ID'])
|
||||
response = secscan_api.call('layers/%s/vulnerabilities', None, request['ID'])
|
||||
except requests.exceptions.Timeout:
|
||||
logger.debug('Timeout when calling Sec')
|
||||
continue
|
||||
|
|
Reference in a new issue