Merge pull request #2547 from coreos-inc/disable-for-trust
Disable certain APIs and build triggers when trust is enabled
This commit is contained in:
commit
e79555ef95
8 changed files with 102 additions and 10 deletions
|
@ -12,6 +12,8 @@ from flask_restful import Resource, abort, Api, reqparse
|
|||
from flask_restful.utils.cors import crossdomain
|
||||
from jsonschema import validate, ValidationError
|
||||
|
||||
import features
|
||||
|
||||
from app import app, metric_queue
|
||||
from data import model
|
||||
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
|
||||
|
@ -373,6 +375,23 @@ 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
|
||||
|
|
|
@ -19,7 +19,8 @@ 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)
|
||||
require_repo_admin, abort, disallow_for_app_repositories,
|
||||
disallow_under_trust)
|
||||
from endpoints.building import start_build, PreparedBuild, MaximumBuildsQueuedException
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest
|
||||
from util.names import parse_robot_username
|
||||
|
@ -225,6 +226,7 @@ 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. """
|
||||
|
@ -361,6 +363,7 @@ 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. """
|
||||
|
|
|
@ -11,12 +11,13 @@ from flask import request, abort
|
|||
|
||||
from app import dockerfile_build_queue, tuf_metadata_api
|
||||
from data import model, oci_model
|
||||
from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request,
|
||||
from endpoints.api import (format_date, nickname, log_action, validate_json_request,
|
||||
require_repo_read, require_repo_write, require_repo_admin,
|
||||
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
||||
request_error, require_scope, path_param, page_support, parse_args,
|
||||
query_param, truthy_bool, disallow_for_app_repositories, show_if)
|
||||
from endpoints.exception import Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException, DownstreamIssue
|
||||
RepositoryParamResource, resource, parse_args, ApiResource,
|
||||
request_error, require_scope, path_param, page_support,
|
||||
query_param, truthy_bool, show_if)
|
||||
from endpoints.exception import (Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException,
|
||||
DownstreamIssue)
|
||||
from endpoints.api.billing import lookup_allowed_private_repos, get_namespace_plan
|
||||
from endpoints.api.subscribe import check_repository_usage
|
||||
|
||||
|
@ -502,7 +503,7 @@ class RepositoryTrust(RepositoryParamResource):
|
|||
|
||||
values = request.get_json()
|
||||
model.repository.set_trust(repo, values['trust_enabled'])
|
||||
|
||||
|
||||
log_action('change_repo_trust', namespace,
|
||||
{'repo': repository, 'namespace': namespace, 'trust_enabled': values['trust_enabled']},
|
||||
repo=repo)
|
||||
|
|
|
@ -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_for_app_repositories, disallow_under_trust)
|
||||
from endpoints.exception import NotFound
|
||||
from endpoints.api.image import image_view
|
||||
from data import model
|
||||
|
@ -85,6 +85,7 @@ 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):
|
||||
|
@ -120,6 +121,7 @@ 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. """
|
||||
|
@ -212,6 +214,7 @@ 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):
|
||||
|
|
50
endpoints/api/test/test_disallow_under_trust.py
Normal file
50
endpoints/api/test/test_disallow_under_trust.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
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 app, appconfig, database_uri, init_db_path, sqlitedb_file
|
||||
|
||||
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)
|
||||
|
|
@ -20,8 +20,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)
|
||||
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
|
||||
disallow_for_app_repositories, disallow_under_trust)
|
||||
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
|
||||
from endpoints.building import start_build, MaximumBuildsQueuedException
|
||||
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
|
||||
|
@ -72,6 +71,7 @@ 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. """
|
||||
|
@ -116,6 +116,7 @@ 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):
|
||||
|
@ -183,6 +184,7 @@ 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):
|
||||
|
@ -283,6 +285,7 @@ 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):
|
||||
|
@ -464,6 +467,7 @@ 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):
|
||||
|
@ -528,6 +532,7 @@ 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. """
|
||||
|
@ -572,6 +577,7 @@ 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):
|
||||
|
@ -606,6 +612,7 @@ 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. """
|
||||
|
|
|
@ -647,6 +647,8 @@ 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())
|
||||
|
@ -682,6 +684,8 @@ 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())
|
||||
|
|
|
@ -87,6 +87,11 @@ def build_trigger_webhook(trigger_uuid, **kwargs):
|
|||
if permission.can():
|
||||
handler = BuildTriggerHandler.get_handler(trigger)
|
||||
|
||||
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:
|
||||
prepared = handler.handle_trigger_request(request)
|
||||
|
|
Reference in a new issue