Merge pull request #2687 from ecordell/enable-builds-trust

Re-enable builds and tag modification when signing is enabled
This commit is contained in:
Evan Cordell 2017-06-13 11:20:04 -04:00 committed by GitHub
commit b6d423a50d
15 changed files with 39 additions and 118 deletions

View file

@ -387,23 +387,6 @@ def define_json_response(schema_name):
return wrapper
def disallow_under_trust(func):
""" Disallows the decorated operation for repository when it has trust enabled.
"""
@wraps(func)
def wrapper(self, *args, **kwargs):
if features.SIGNING:
namespace = args[0]
repository = args[1]
repo = model.repository.get_repository(namespace, repository)
if repo is not None and repo.trust_enabled:
raise InvalidRequest('Cannot call this method on a repostory with trust enabled')
return func(self, *args, **kwargs)
return wrapper
import endpoints.api.billing
import endpoints.api.build
import endpoints.api.discovery

View file

@ -19,8 +19,7 @@ from data.buildlogs import BuildStatusRetrievalError
from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
require_repo_read, require_repo_write, validate_json_request,
ApiResource, internal_only, format_date, api, path_param,
require_repo_admin, abort, disallow_for_app_repositories,
disallow_under_trust)
require_repo_admin, abort, disallow_for_app_repositories)
from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException
from endpoints.exception import Unauthorized, NotFound, InvalidRequest
from util.names import parse_robot_username
@ -226,7 +225,6 @@ class RepositoryBuildList(RepositoryParamResource):
@require_repo_write
@nickname('requestRepoBuild')
@disallow_for_app_repositories
@disallow_under_trust
@validate_json_request('RepositoryBuildRequest')
def post(self, namespace, repository):
""" Request that a repository be built and pushed from the specified input. """
@ -363,7 +361,6 @@ class RepositoryBuildResource(RepositoryParamResource):
@require_repo_admin
@nickname('cancelRepoBuild')
@disallow_under_trust
@disallow_for_app_repositories
def delete(self, namespace, repository, build_uuid):
""" Cancels a repository build. """

View file

@ -5,7 +5,7 @@ from flask import request, abort
from endpoints.api import (
resource, nickname, require_repo_read, require_repo_write, RepositoryParamResource, log_action,
validate_json_request, path_param, parse_args, query_param, truthy_bool,
disallow_for_app_repositories, disallow_under_trust)
disallow_for_app_repositories)
from endpoints.exception import NotFound
from endpoints.api.image import image_view
from endpoints.v2.manifest import _generate_and_store_manifest
@ -87,7 +87,6 @@ class RepositoryTag(RepositoryParamResource):
@require_repo_write
@disallow_for_app_repositories
@disallow_under_trust
@nickname('changeTagImage')
@validate_json_request('MoveTag')
def put(self, namespace, repository, tag):
@ -128,7 +127,6 @@ class RepositoryTag(RepositoryParamResource):
@require_repo_write
@disallow_for_app_repositories
@disallow_under_trust
@nickname('deleteFullTag')
def delete(self, namespace, repository, tag):
""" Delete the specified repository tag. """
@ -225,7 +223,6 @@ class RestoreTag(RepositoryParamResource):
@require_repo_write
@disallow_for_app_repositories
@disallow_under_trust
@nickname('restoreTag')
@validate_json_request('RestoreTag')
def post(self, namespace, repository, tag):

View file

@ -1,50 +0,0 @@
import pytest
from data import model
from endpoints.api.build import RepositoryBuildList, RepositoryBuildResource
from endpoints.api.tag import RepositoryTag, RestoreTag
from endpoints.api.trigger import (BuildTrigger, BuildTriggerSubdirs,
BuildTriggerActivate, BuildTriggerAnalyze, ActivateBuildTrigger,
BuildTriggerFieldValues, BuildTriggerSources,
BuildTriggerSourceNamespaces)
from endpoints.api.test.shared import client_with_identity, conduct_api_call
from test.fixtures import *
BUILD_ARGS = {'build_uuid': '1234'}
IMAGE_ARGS = {'imageid': '1234', 'image_id': 1234}
MANIFEST_ARGS = {'manifestref': 'sha256:abcd1234'}
LABEL_ARGS = {'manifestref': 'sha256:abcd1234', 'labelid': '1234'}
NOTIFICATION_ARGS = {'uuid': '1234'}
TAG_ARGS = {'tag': 'foobar'}
TRIGGER_ARGS = {'trigger_uuid': '1234'}
FIELD_ARGS = {'trigger_uuid': '1234', 'field_name': 'foobar'}
@pytest.mark.parametrize('resource, method, params', [
(RepositoryBuildList, 'post', None),
(RepositoryBuildResource, 'delete', BUILD_ARGS),
(RepositoryTag, 'put', TAG_ARGS),
(RepositoryTag, 'delete', TAG_ARGS),
(RestoreTag, 'post', TAG_ARGS),
(BuildTrigger, 'delete', TRIGGER_ARGS),
(BuildTriggerSubdirs, 'post', TRIGGER_ARGS),
(BuildTriggerActivate, 'post', TRIGGER_ARGS),
(BuildTriggerAnalyze, 'post', TRIGGER_ARGS),
(ActivateBuildTrigger, 'post', TRIGGER_ARGS),
(BuildTriggerFieldValues, 'post', FIELD_ARGS),
(BuildTriggerSources, 'post', TRIGGER_ARGS),
(BuildTriggerSourceNamespaces, 'get', TRIGGER_ARGS),
])
def test_disallowed_for_apps(resource, method, params, client):
namespace = 'devtable'
repository = 'somerepo'
devtable = model.user.get_user('devtable')
repo = model.repository.create_repository(namespace, repository, devtable, repo_kind='image')
model.repository.set_trust(repo, True)
params = params or {}
params['repository'] = '%s/%s' % (namespace, repository)
with client_with_identity('devtable', client) as cl:
conduct_api_call(cl, resource, method, params, None, 400)

View file

@ -19,7 +19,7 @@ from data.model.build import update_build_trigger
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
log_action, request_error, query_param, parse_args, internal_only,
validate_json_request, api, path_param, abort,
disallow_for_app_repositories, disallow_under_trust)
disallow_for_app_repositories)
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
from endpoints.api.trigger_analyzer import TriggerAnalyzer
from endpoints.building import start_build, MaximumBuildsQueuedException
@ -72,7 +72,6 @@ class BuildTrigger(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('deleteBuildTrigger')
def delete(self, namespace_name, repo_name, trigger_uuid):
""" Delete the specified build trigger. """
@ -114,7 +113,6 @@ class BuildTriggerSubdirs(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('listBuildTriggerSubdirs')
@validate_json_request('BuildTriggerSubdirRequest')
def post(self, namespace_name, repo_name, trigger_uuid):
@ -179,7 +177,6 @@ class BuildTriggerActivate(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('activateBuildTrigger')
@validate_json_request('BuildTriggerActivateRequest')
def post(self, namespace_name, repo_name, trigger_uuid):
@ -276,7 +273,6 @@ class BuildTriggerAnalyze(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('analyzeBuildTrigger')
@validate_json_request('BuildTriggerAnalyzeRequest')
def post(self, namespace_name, repo_name, trigger_uuid):
@ -339,7 +335,6 @@ class ActivateBuildTrigger(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('manuallyStartBuildTrigger')
@validate_json_request('RunParameters')
def post(self, namespace_name, repo_name, trigger_uuid):
@ -401,7 +396,6 @@ class BuildTriggerFieldValues(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('listTriggerFieldValues')
def post(self, namespace_name, repo_name, trigger_uuid, field_name):
""" List the field values for a custom run field. """
@ -443,7 +437,6 @@ class BuildTriggerSources(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('listTriggerBuildSources')
@validate_json_request('BuildTriggerSourcesRequest')
def post(self, namespace_name, repo_name, trigger_uuid):
@ -475,7 +468,6 @@ class BuildTriggerSourceNamespaces(RepositoryParamResource):
@require_repo_admin
@disallow_for_app_repositories
@disallow_under_trust
@nickname('listTriggerBuildSourceNamespaces')
def get(self, namespace_name, repo_name, trigger_uuid):
""" List the build sources for the trigger configuration thus far. """

View file

@ -674,8 +674,6 @@ def attach_bitbucket_trigger(namespace_name, repo_name):
abort(404, message=msg)
elif repo.kind.name != 'image':
abort(501)
elif repo.trust_enabled:
abort(400)
trigger = model.build.create_build_trigger(repo, BitbucketBuildTrigger.service_name(), None,
current_user.db_user())
@ -711,8 +709,6 @@ def attach_custom_build_trigger(namespace_name, repo_name):
abort(404, message=msg)
elif repo.kind.name != 'image':
abort(501)
elif repo.trust_enabled:
abort(400)
trigger = model.build.create_build_trigger(repo, CustomBuildTrigger.service_name(),
None, current_user.db_user())

View file

@ -89,8 +89,6 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
if trigger.repository.kind.name != 'image':
abort(501, 'Build triggers cannot be invoked on application repositories')
elif trigger.repository.trust_enabled:
abort(400, 'Build triggers cannot be invoked on repositories with trust enabled')
logger.debug('Passing webhook request to handler %s', handler)
try:

View file

@ -89,14 +89,14 @@
<i class="fa ci-robot"></i> New Robot Account
</a>
</li>
<li role="presentation" class="divider" ng-if="currentPageContext.repository && currentPageContext.repository.can_write && !currentPageContext.repository.trust_enabled"></li>
<li role="presentation" class="divider" ng-if="currentPageContext.repository && currentPageContext.repository.can_write && !currentPageContext.repository.tag_operations_disabled"></li>
<li role="presentation" class="dropdown-header"
ng-if="currentPageContext.repository && currentPageContext.repository.can_write &&
!currentPageContext.repository.trust_enabled">
!currentPageContext.repository.tag_operations_disabled">
Repository {{ currentPageContext.repository.namespace }}/{{ currentPageContext.repository.name }}
</li>
<li ng-if="currentPageContext.repository && currentPageContext.repository.can_write &&
!currentPageContext.repository.trust_enabled">
!currentPageContext.repository.tag_operations_disabled">
<a ng-click="startBuild()">
<i class="fa fa-tasks"></i> New Dockerfile Build
</a>

View file

@ -1,13 +1,13 @@
<div class="repo-panel-builds-element">
<div class="feedback-bar" feedback="feedback"></div>
<div class="tab-header-controls">
<button class="btn btn-primary" ng-click="showNewBuildDialog()" ng-if="!repository.trust_enabled">
<button class="btn btn-primary" ng-click="showNewBuildDialog()" ng-if="!repository.tag_operations_disabled">
<i class="fa fa-play"></i> Start New Build
</button>
</div>
<h3 class="tab-header">Repository Builds</h3>
<div class="co-alert co-alert-info" ng-if="repository.trust_enabled">
<div class="co-alert co-alert-info" ng-if="repository.tag_operations_disabled">
Builds cannot be performed on this repository because Quay Trust is
enabled, which requires that all operations be signed by a user.
</div>
@ -83,7 +83,7 @@
</div> <!-- /Builds -->
<!-- Build Triggers -->
<div class="co-panel" ng-if="repository.can_admin && TriggerService.getTypes().length && !repository.trust_enabled" id="repoBuildTriggers">
<div class="co-panel" ng-if="repository.can_admin && TriggerService.getTypes().length && !repository.tag_operations_disabled" id="repoBuildTriggers">
<!-- Builds header controls -->
<div class="co-panel-heading">
<i class="fa fa-flash"></i>

View file

@ -32,7 +32,7 @@
<!-- No Builds -->
<div class="empty" ng-if="builds && !builds.length">
<div class="empty-primary-msg">No builds have been run for this repository.</div>
<div class="empty-secondary-msg" ng-if="repository.can_write && !repository.trust_enabled">
<div class="empty-secondary-msg" ng-if="repository.can_write && !repository.tag_operations_disabled">
Click on the <i class="fa fa-tasks" style="margin-left: 6px"></i> Builds tab to start a new build.
</div>
</div>

View file

@ -53,7 +53,7 @@
</li>
<li ng-if="repository.can_write">
<a ng-click="askDeleteMultipleTags(checkedTags.checked)"
ng-class="repository.trust_enabled ? 'disabled-option' : ''">
ng-class="repository.tag_operations_disabled ? 'disabled-option' : ''">
<i class="fa fa-times"></i><span class="text">Delete Tags</span>
</a>
</li>
@ -243,7 +243,7 @@
<span bo-if="repository.can_write">
<span class="cor-options-menu">
<span class="cor-option" option-click="askAddTag(tag)"
ng-class="repository.trust_enabled ? 'disabled-option' : ''">
ng-class="repository.tag_operations_disabled ? 'disabled-option' : ''">
<i class="fa fa-plus"></i> Add New Tag
</span>
<span class="cor-option" option-click="showLabelEditor(tag)"
@ -251,7 +251,7 @@
<i class="fa fa-tags"></i> Edit Labels
</span>
<span class="cor-option" option-click="askDeleteTag(tag.name)"
ng-class="repository.trust_enabled ? 'disabled-option' : ''">
ng-class="repository.tag_operations_disabled ? 'disabled-option' : ''">
<i class="fa fa-times"></i> Delete Tag
</span>
</span>

View file

@ -144,16 +144,17 @@
manifest-digest="restoreTagInfo.manifest_digest"></span>?
</div>
<!-- Trust Enabled Dialog -->
<div class="modal fade" id="trustEnabledModal">
<!-- Tag Operations Disabled Dialog -->
<div class="modal fade" id="tagOperationsDisabledModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
Cannot execute with trust enabled
Tag operations have been disabled.
</div>
<div class="modal-body">
The selected operation cannot be performed on this repository because Quay Trust is
enabled, which requires that all operations be signed by a user.
The selected operation cannot be performed on this repository because tag operations have been disabled
by an administrator. <span ng-if="repository.trust_enabled">Trust is enabled for this repo, so any tag changes
should be performed by users with signing keys.</span>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->

View file

@ -18,7 +18,11 @@
Signing is enabled on this repository and all tag operations must be signed via Docker Content Trust.
</p>
<p>
Note that due to this feature being enabled, all UI-based tag operations and all build support is <strong>disabled on this repository</strong>.
When this feature is enabled, it will be possible to use the UI or client tools to change tag data without
signing.
This can make a signed tag point to a different image than the actual tag, and the underlying data could
be garbage collected. It is important to have a strict separation between tags that are signed and tags
that are not.
</p>
<button class="btn btn-danger" ng-click="$ctrl.askChangeTrust(false)">Disable Trust</button>
</div>
@ -43,12 +47,14 @@
dialog-title="Enable Trust"
dialog-action-title="Enable Trust">
<p>Click "Enable Trust" to enable content trust on this repository.</p>
<p>Please note that at this time, having content trust will <strong>disable</strong> the following
features under the repository:
<p>Please note that this will not prevent users from overwriting signed tags without updating signatures.
This means that:
<ul>
<li>Any tag operations in the UI (Add Tag, Delete Tag, Restore Tag)
<li>All build triggers and ability to invoke builds
<li>Any tag operations in the UI or client can cause inconsistency
<li>Builds should not push to signed tags
</ul>
We recommend you maintain a strict separation between signed and unsigned tags to avoid any issues with garbage
collection.
</p>
</div>

View file

@ -35,9 +35,9 @@ angular.module('quay').directive('tagOperationsDialog', function () {
});
};
$scope.alertOnTrust = function() {
if ($scope.repository.trust_enabled) {
$('#trustEnabledModal').modal('show');
$scope.alertOnTagOpsDisabled = function() {
if ($scope.repository.tag_operations_disabled) {
$('#tagOperationsDisabledModal').modal('show');
return true;
}
@ -62,7 +62,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.createOrMoveTag = function(image, tag) {
if (!$scope.repository.can_write) { return; }
if ($scope.alertOnTrust()) {
if ($scope.alertOnTagOpsDisabled()) {
return;
}
@ -242,7 +242,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.actionHandler = {
'askDeleteTag': function(tag) {
if ($scope.alertOnTrust()) {
if ($scope.alertOnTagOpsDisabled()) {
return;
}
@ -252,7 +252,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
},
'askDeleteMultipleTags': function(tags) {
if ($scope.alertOnTrust()) {
if ($scope.alertOnTagOpsDisabled()) {
return;
}
@ -262,7 +262,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
},
'askAddTag': function(image) {
if ($scope.alertOnTrust()) {
if ($scope.alertOnTagOpsDisabled()) {
return;
}
@ -297,7 +297,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
},
'askRestoreTag': function(tag, image_id, opt_manifest_digest) {
if ($scope.alertOnTrust()) {
if ($scope.alertOnTagOpsDisabled()) {
return;
}

View file

@ -81,6 +81,7 @@ export type Repository = {
kind?: string;
namespace?: string;
trust_enabled?: boolean;
tag_operations_disabled?: boolean;
};