PR#2951: kojid: extend SCM.assert_allowed with hub policy
Merges #2951 https://pagure.io/koji/pull-request/2951 Fixes: #2757 https://pagure.io/koji/issue/2757 RFE: scratch build specific allowed_scms?
This commit is contained in:
commit
6d4abedb86
13 changed files with 521 additions and 31 deletions
|
|
@ -1633,7 +1633,17 @@ class BuildMavenTask(BaseBuildTask):
|
|||
self.opts = opts
|
||||
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm_policy_opts = {
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': self.opts.get('scratch')
|
||||
}
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data=scm_policy_opts)
|
||||
repo_id = opts.get('repo_id')
|
||||
if not repo_id:
|
||||
raise koji.BuildError('A repo_id must be provided')
|
||||
|
|
@ -1707,7 +1717,11 @@ class BuildMavenTask(BaseBuildTask):
|
|||
if self.opts.get('patches'):
|
||||
patchlog = self.workdir + '/patches.log'
|
||||
patch_scm = SCM(self.opts.get('patches'))
|
||||
patch_scm.assert_allowed(self.options.allowed_scms)
|
||||
patch_scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data=scm_policy_opts)
|
||||
self.run_callbacks('preSCMCheckout', scminfo=patch_scm.get_info(),
|
||||
build_tag=build_tag, scratch=opts.get('scratch'),
|
||||
buildroot=buildroot)
|
||||
|
|
@ -1990,7 +2004,16 @@ class WrapperRPMTask(BaseBuildTask):
|
|||
assert False # pragma: no cover
|
||||
|
||||
scm = SCM(spec_url)
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': opts.get('scratch')
|
||||
})
|
||||
|
||||
repo_id = opts.get('repo_id')
|
||||
if not repo_id:
|
||||
|
|
@ -2979,7 +3002,16 @@ class ImageTask(BaseTaskHandler):
|
|||
self.logger.debug("ksfile = %s" % ksfile)
|
||||
if self.opts.get('ksurl'):
|
||||
scm = SCM(self.opts['ksurl'])
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': self.opts.get('scratch')
|
||||
})
|
||||
logfile = os.path.join(self.workdir, 'checkout.log')
|
||||
self.run_callbacks('preSCMCheckout', scminfo=scm.get_info(),
|
||||
build_tag=build_tag, scratch=self.opts.get('scratch'),
|
||||
|
|
@ -3453,7 +3485,16 @@ class LiveMediaTask(ImageTask):
|
|||
can find the checked out templates.
|
||||
"""
|
||||
scm = SCM(self.opts['lorax_url'])
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': self.opts.get('scratch')
|
||||
})
|
||||
logfile = os.path.join(self.workdir, 'lorax-templates-checkout.log')
|
||||
checkout_dir = scm.checkout(build_root.tmpdir(),
|
||||
self.session, self.getUploadDir(), logfile)
|
||||
|
|
@ -3700,7 +3741,16 @@ class OzImageTask(BaseTaskHandler):
|
|||
self.logger.debug("ksfile = %s" % ksfile)
|
||||
if self.opts.get('ksurl'):
|
||||
scm = SCM(self.opts['ksurl'])
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': self.opts.get('scratch')
|
||||
})
|
||||
logfile = os.path.join(self.workdir, 'checkout-%s.log' % self.arch)
|
||||
self.run_callbacks('preSCMCheckout', scminfo=scm.get_info(),
|
||||
build_tag=build_tag, scratch=self.opts.get('scratch'))
|
||||
|
|
@ -4527,7 +4577,16 @@ class BuildIndirectionImageTask(OzImageTask):
|
|||
self.logger.debug("filepath = %s" % filepath)
|
||||
if fileurl:
|
||||
scm = SCM(fileurl)
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': self.opts.get('scratch')
|
||||
})
|
||||
self.run_callbacks('preSCMCheckout', scminfo=scm.get_info(),
|
||||
build_tag=build_tag, scratch=self.opts.get('scratch'))
|
||||
logfile = os.path.join(self.workdir, 'checkout.log')
|
||||
|
|
@ -4935,12 +4994,20 @@ class BuildSRPMFromSCMTask(BaseBuildTask):
|
|||
return self.checkHostArch(tag, hostdata)
|
||||
|
||||
def handler(self, url, build_tag, opts=None):
|
||||
# will throw a BuildError if the url is invalid
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
|
||||
if opts is None:
|
||||
opts = {}
|
||||
# will throw a BuildError if the url is invalid
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': opts.get('scratch')
|
||||
})
|
||||
repo_id = opts.get('repo_id')
|
||||
if not repo_id:
|
||||
raise koji.BuildError("A repo id must be provided")
|
||||
|
|
@ -6362,6 +6429,8 @@ def get_options():
|
|||
'mock_bootstrap_image': False,
|
||||
'pkgurl': None,
|
||||
'allowed_scms': '',
|
||||
'allowed_scms_use_config': True,
|
||||
'allowed_scms_use_policy': False,
|
||||
'scm_credentials_dir': None,
|
||||
'support_rpm_source_layout': True,
|
||||
'yum_proxy': None,
|
||||
|
|
@ -6390,7 +6459,7 @@ def get_options():
|
|||
elif name in ['offline_retry', 'use_createrepo_c', 'createrepo_skip_stat',
|
||||
'createrepo_update', 'use_fast_upload', 'support_rpm_source_layout',
|
||||
'build_arch_can_fail', 'no_ssl_verify', 'log_timestamps',
|
||||
'allow_noverifyssl']:
|
||||
'allow_noverifyssl', 'allowed_scms_use_config', 'allowed_scms_use_policy']:
|
||||
defaults[name] = config.getboolean('kojid', name)
|
||||
elif name in ['plugin', 'plugins']:
|
||||
defaults['plugin'] = value.split()
|
||||
|
|
|
|||
|
|
@ -77,6 +77,14 @@ topurl=http://hub.example.com/kojifiles
|
|||
; is run by default.
|
||||
allowed_scms=scm.example.com:/cvs/example git.example.org:/example svn.example.org:/users/*:no
|
||||
|
||||
; If use the option allowed_scms above for allowing / denying SCM, default: true
|
||||
; allowed_scms_use_config = true
|
||||
|
||||
; If use hub policy build_from_scm for allowing / denying SCM, default: false
|
||||
; notice that if both options are enabled, both assertions will be applied, and user_common and
|
||||
; source_cmd will be overridden by the policy's result.
|
||||
; allowed_scms_use_policy = false
|
||||
|
||||
; A directory to bind mount into Source RPM creation so that some
|
||||
; credentials can be supplied when required to fetch sources, e.g.
|
||||
; when the place the sources are fetched from requires all accesses to
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ username/password but it has its limitations which you should be aware of.
|
|||
|
||||
Details can be found at :ref:`auth-config`
|
||||
|
||||
.. _allowed-scms:
|
||||
|
||||
Allowed SCMs
|
||||
============
|
||||
|
||||
|
|
@ -24,6 +26,13 @@ We recommend that every production environment choose a limited set of trusted s
|
|||
|
||||
Details of the ``allowed_scms`` option are covered under :ref:`scm-config`
|
||||
|
||||
We also provides ``build_from_scm`` hub policy for the same purpose, you can choose either/both
|
||||
of the two approaches by the switch options in ``/etc/kojid.conf`` per build:
|
||||
|
||||
* ``allowed_scms_use_config``, default: ``true``
|
||||
* ``allowed_scms_use_policy``, default: ``false``
|
||||
|
||||
For more details of the ``build_from_scm``, please read :doc:`defining_hub_policies`.
|
||||
|
||||
Hub Policies
|
||||
============
|
||||
|
|
@ -43,6 +52,7 @@ Examples of access control polices are:
|
|||
* vm: control which windows build tasks are allowed
|
||||
* dist_repo: control which distRepo tasks are allowed
|
||||
* build_from_srpm: control whether builds from srpm are allowed
|
||||
* build_from_scm: control whether builds from the SCM are allowed and the behavior of the SCM
|
||||
* build_from_repo_id: control whether builds from user-specified repos ids are allowed
|
||||
|
||||
Note that not all policies are access control policies.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ in the system. At present, policy allows you to control:
|
|||
|
||||
* tag/untag/move operations
|
||||
* allowing builds from srpm
|
||||
* allowing builds from SCM, and managing properties/behaviors related to the SCM
|
||||
if it is allowed
|
||||
* allowing builds from expired repos
|
||||
* managing the package list for a tag
|
||||
* managing which channel a task goes to
|
||||
|
|
@ -19,6 +21,11 @@ Policy configuration is optional. If you don't define one, then by default:
|
|||
|
||||
* tag/untag/move operations are governed by tag locks/permissions
|
||||
* builds from srpm are only allowed for admins
|
||||
* builds from any SCM are only allowed for admins. It's used when
|
||||
``allowed_scms_use_policy`` is ``true`` in ``/etc/kojid.conf`` of the builders
|
||||
(``false`` by default). And the SCM's properies: ``use_common`` and
|
||||
``source_cmd`` are set to their default values: ``False`` and
|
||||
``['make', 'source']``
|
||||
* builds from expired repos are only allowed for admins
|
||||
* only admins and users with ``tag`` permission may modify package lists
|
||||
* tasks go to the default channel
|
||||
|
|
@ -126,6 +133,7 @@ The system currently looks for the following policies
|
|||
* ``tag``: checked during tag/untag/move operations
|
||||
* ``build_from_srpm``: checked when a build from srpm (not an SCM reference) is
|
||||
requested.
|
||||
* ``build_from_scm``: checked when a build task from SCM is executing on builder
|
||||
* ``build_from_repo_id``: checked when a build from a specified repo id is
|
||||
requested
|
||||
* ``package_list``: checked when the package list for a tag is modified
|
||||
|
|
@ -193,6 +201,23 @@ different:
|
|||
``adjust -<int>``
|
||||
* decrement default priority
|
||||
|
||||
The **build_from_scm** policy is used to assert if the SCM is allowed or not,
|
||||
like the basic allow/deny one. It is also used to manage the SCM's properties as
|
||||
the same as the ``allowed_scms`` option of the koji builder. The actions could
|
||||
be defined as:
|
||||
|
||||
``allow [use_common] [<source_cmd>]``
|
||||
* allow the SCM
|
||||
* use(clone) the /common repo when ``use_common`` follows ``allow``
|
||||
* ``<source_cmd>`` is a *optional* shell command for preparing the source
|
||||
between checkout and srpm build. If it is omitted, it will follow the
|
||||
default value: ``make source``. The explicit value: ``none`` means **No**
|
||||
``source_cmd`` is defined.
|
||||
|
||||
``deny [<reason>]``
|
||||
* disallow the SCM
|
||||
* ``<reason>`` is the error message which is shown as the task result
|
||||
|
||||
Available tests
|
||||
===============
|
||||
``true``
|
||||
|
|
|
|||
|
|
@ -1216,6 +1216,11 @@ SCM checkout can contain multiple spec files (checkouted or created by
|
|||
``source_cmd``). In such case spec file named same as a checkout directory will
|
||||
be selected.
|
||||
|
||||
.. note::
|
||||
We provide ``build_from_scm`` hub policy as an equivalent in version 1.26.0.
|
||||
|
||||
For more details, please refer to :ref:`allowed-scms` and
|
||||
:doc:`Defining Hub Policies <defining_hub_policies>`.
|
||||
|
||||
Add the host to the createrepo channel
|
||||
--------------------------------------
|
||||
|
|
|
|||
|
|
@ -460,12 +460,21 @@ If you your build needs to check out code from a Source Control Manager
|
|||
``koji/daemon.py``. They take a specially formed URL as an argument to
|
||||
the constructor. Here's an example use. The second line is important, it
|
||||
makes sure the SCM is in the whitelist of SCMs allowed in
|
||||
``/etc/kojid/kojid.conf``.
|
||||
``/etc/kojid/kojid.conf`` or in ``build_from_scm`` section of hub policy.
|
||||
|
||||
::
|
||||
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': opts.get('scratch')
|
||||
}))
|
||||
directory = scm.checkout('/checkout/path', session, uploaddir, logfile)
|
||||
|
||||
Checking out takes 4 arguments: where to checkout, a session object
|
||||
|
|
|
|||
|
|
@ -10099,6 +10099,20 @@ def check_policy(name, data, default='deny', strict=False, force=False):
|
|||
raise koji.ActionNotAllowed(err_str)
|
||||
|
||||
|
||||
def eval_policy(name, data):
|
||||
"""Evaluate named policy with given data and return the result
|
||||
|
||||
:param str name: the policy name
|
||||
:param dict data: the policy data
|
||||
:returns the action as a string
|
||||
:raises koji.GenericError if the policy is empty or not found
|
||||
"""
|
||||
ruleset = context.policy.get(name)
|
||||
if not ruleset:
|
||||
raise koji.GenericError("no such policy: %s" % name)
|
||||
return ruleset.apply(data)
|
||||
|
||||
|
||||
def policy_data_from_task(task_id):
|
||||
"""Calculate policy data from task id
|
||||
|
||||
|
|
@ -10653,6 +10667,8 @@ class RootExports(object):
|
|||
q += """ ORDER BY id DESC LIMIT 1"""
|
||||
return _singleRow(q, values, fields, strict=True)
|
||||
|
||||
evalPolicy = staticmethod(eval_policy)
|
||||
|
||||
def makeTask(self, *args, **opts):
|
||||
"""Creates task manually. This is mainly for debugging, only an admin
|
||||
can make arbitrary tasks. You need to supply all *args and **opts
|
||||
|
|
@ -14622,10 +14638,7 @@ class HostExports(object):
|
|||
"""Evaluate named policy with given data and return the result"""
|
||||
host = Host()
|
||||
host.verify()
|
||||
ruleset = context.policy.get(name)
|
||||
if not ruleset:
|
||||
raise koji.GenericError("no such policy: %s" % name)
|
||||
return ruleset.apply(data)
|
||||
return eval_policy(name, data)
|
||||
|
||||
def newBuildRoot(self, repo, arch, task_id=None):
|
||||
host = Host()
|
||||
|
|
|
|||
|
|
@ -524,6 +524,13 @@ _default_policies = {
|
|||
has_perm admin :: allow
|
||||
all :: deny
|
||||
''',
|
||||
'build_from_scm': '''
|
||||
has_perm admin :: allow
|
||||
# match scm_type CVS CVS+SSH && match scm_host scm.example.com && match scm_repository /cvs/example :: allow
|
||||
# match scm_type GIT GIT+SSH && match scm_host git.example.org && match scm_repository /example :: allow
|
||||
# match scm_type SVN SVN+SSH && match scm_host svn.example.org && match scm_repository /users/* :: allow
|
||||
all :: deny
|
||||
''', # noqa: E501
|
||||
'package_list': '''
|
||||
has_perm admin :: allow
|
||||
has_perm tag :: allow
|
||||
|
|
|
|||
101
koji/daemon.py
101
koji/daemon.py
|
|
@ -26,6 +26,7 @@ import errno
|
|||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -327,7 +328,36 @@ class SCM(object):
|
|||
# return parsed values
|
||||
return (scheme, user, netloc, path, query, fragment)
|
||||
|
||||
def assert_allowed(self, allowed):
|
||||
def assert_allowed(self, allowed='', session=None, by_config=True, by_policy=False,
|
||||
policy_data=None):
|
||||
"""
|
||||
Check whether this scm is allowed and apply options by either/both approach below:
|
||||
|
||||
- "allowed_scms" from config file, see
|
||||
:func:`~koji.daemon.SCM.assert_allowed_by_config`
|
||||
- "build_from_scm" hub policy, see :func:`~koji.daemon.SCM.assert_allowed_by_policy`
|
||||
|
||||
When both approaches are applied, the config one will be applied, then the policy one.
|
||||
|
||||
:param str allowed: The allowed_scms config content which is used for by-config approach
|
||||
:param koji.ClientSession session: The allowed_scms config content which is used for
|
||||
by-policy approach
|
||||
:param bool by_config: Using config or not, Default: True
|
||||
:param bool by_policy: Using policy or not, Default: False
|
||||
:param dict policy_data: The policy data which will be merged with the generated scm info,
|
||||
then will be passed to hub call for policy assertion when using
|
||||
policy.
|
||||
:raises koji.BuildError: if the scm is denied.
|
||||
"""
|
||||
if by_config:
|
||||
self.assert_allowed_by_config(allowed or '')
|
||||
if by_policy:
|
||||
if session is None:
|
||||
raise koji.ConfigurationError(
|
||||
'When allowed SCM assertion is by policy, session must be passed in.')
|
||||
self.assert_allowed_by_policy(session, **(policy_data or {}))
|
||||
|
||||
def assert_allowed_by_config(self, allowed):
|
||||
"""
|
||||
Check this scm against allowed list and apply options
|
||||
|
||||
|
|
@ -396,6 +426,75 @@ class SCM(object):
|
|||
raise koji.BuildError(
|
||||
'%s:%s is not in the list of allowed SCMs' % (self.host, self.repository))
|
||||
|
||||
def assert_allowed_by_policy(self, session, **extra_data):
|
||||
"""
|
||||
Check this scm against hub policy: build_from_scm and apply options
|
||||
|
||||
The policy data is the combination of scminfo with scm_ prefix and kwargs.
|
||||
It should at least contain following keys:
|
||||
|
||||
- scm_url
|
||||
- scm_scheme
|
||||
- scm_user
|
||||
- scm_host
|
||||
- scm_repository
|
||||
- scm_module
|
||||
- scm_revision
|
||||
- scm_type
|
||||
|
||||
More keys could be added as kwargs(extra_data). You can pass any reasonable data which
|
||||
could be handled by policy tests, like:
|
||||
|
||||
- scratch (if the task is scratch)
|
||||
- channel (which channel the task is assigned)
|
||||
- user_id (the task owner)
|
||||
|
||||
If the key in extra_data is one of scm_* listed above, it will override the one generated
|
||||
from scminfo.
|
||||
|
||||
The format of the action returned from build_from_scm could be one of following forms::
|
||||
|
||||
allow [use_common] [<source_cmd>]
|
||||
deny [<reason>]
|
||||
|
||||
If use_common is not set, use_common property is False.
|
||||
If source_cmd is none, it will be parsed as None. If it not set, the default value:
|
||||
['make', 'sources'], or the value set by :func:`~koji.daemon.SCM.assert_allowed_by_config`
|
||||
will be set.
|
||||
|
||||
Policy example:
|
||||
|
||||
build_from_scm =
|
||||
bool scratch :: allow none
|
||||
match scm_host scm.example.com :: allow use_common make sources
|
||||
match scm_host scm2.example.com :: allow
|
||||
all :: deny
|
||||
|
||||
|
||||
:param koji.ClientSession session: the session object to call hub xmlrpc APIs.
|
||||
It should be a host session.
|
||||
|
||||
:raises koji.BuildError: if the scm is denied.
|
||||
"""
|
||||
policy_data = {}
|
||||
for k, v in six.iteritems(self.get_info()):
|
||||
policy_data[re.sub(r'^(scm_?)?', 'scm_', k)] = v
|
||||
policy_data.update(extra_data)
|
||||
result = (session.evalPolicy('build_from_scm', policy_data) or '').split()
|
||||
is_allowed = result and result[0].lower() in ('yes', 'true', 'allow', 'allowed')
|
||||
if not is_allowed:
|
||||
raise koji.BuildError(
|
||||
'SCM: %s:%s is not allowed, reason: %s' % (self.host, self.repository,
|
||||
' '.join(result[1:]) or None))
|
||||
# Apply options when it's allowed
|
||||
applied = result[1:]
|
||||
self.use_common = len(applied) != 0 and applied[0].lower() == 'use_common'
|
||||
idx = 1 if self.use_common else 0
|
||||
self.source_cmd = applied[idx:] or self.source_cmd
|
||||
if self.source_cmd is not None and len(self.source_cmd) > 0 \
|
||||
and self.source_cmd[0].lower() == 'none':
|
||||
self.source_cmd = None
|
||||
|
||||
def checkout(self, scmdir, session=None, uploadpath=None, logfile=None):
|
||||
"""
|
||||
Checkout the module from SCM. Accepts the following parameters:
|
||||
|
|
|
|||
|
|
@ -312,6 +312,7 @@ class BaseTaskHandler(object):
|
|||
self.workdir = workdir
|
||||
self.logger = logging.getLogger("koji.build.BaseTaskHandler")
|
||||
self.manager = None
|
||||
self.taskinfo = None
|
||||
|
||||
def setManager(self, manager):
|
||||
"""Set the manager attribute
|
||||
|
|
@ -597,15 +598,20 @@ class BaseTaskHandler(object):
|
|||
|
||||
def run_callbacks(self, plugin, *args, **kwargs):
|
||||
if 'taskinfo' not in kwargs:
|
||||
try:
|
||||
taskinfo = self.taskinfo
|
||||
except AttributeError:
|
||||
self.taskinfo = self.session.getTaskInfo(self.id, request=True)
|
||||
taskinfo = self.taskinfo
|
||||
kwargs['taskinfo'] = taskinfo
|
||||
kwargs['taskinfo'] = self.taskinfo
|
||||
kwargs['session'] = self.session
|
||||
koji.plugin.run_callbacks(plugin, *args, **kwargs)
|
||||
|
||||
@property
|
||||
def taskinfo(self):
|
||||
if not getattr(self, '_taskinfo', None):
|
||||
self._taskinfo = self.session.getTaskInfo(self.id, request=True, strict=True)
|
||||
return self._taskinfo
|
||||
|
||||
@taskinfo.setter
|
||||
def taskinfo(self, taskinfo):
|
||||
self._taskinfo = taskinfo
|
||||
|
||||
|
||||
class FakeTask(BaseTaskHandler):
|
||||
Methods = ['someMethod']
|
||||
|
|
|
|||
|
|
@ -8,9 +8,40 @@ import unittest
|
|||
|
||||
import koji
|
||||
import koji.daemon
|
||||
import koji.policy
|
||||
|
||||
from koji.daemon import SCM
|
||||
|
||||
policy = {
|
||||
'one': '''
|
||||
match scm_host goodserver :: allow none
|
||||
match scm_host badserver :: deny
|
||||
match scm_host maybeserver && match scm_repository /badpath/* :: deny
|
||||
all :: allow
|
||||
''',
|
||||
'two': '''
|
||||
match scm_host default :: allow
|
||||
match scm_host nocommon :: allow
|
||||
match scm_host common :: allow use_common
|
||||
match scm_host srccmd :: allow fedpkg sources
|
||||
match scm_host nosrc :: allow none
|
||||
match scm_host mixed && match scm_repository /foo/* :: allow
|
||||
match scm_host mixed && match scm_repository /bar/* :: allow use_common
|
||||
match scm_host mixed && match scm_repository /baz/* :: allow fedpkg sources
|
||||
match scm_host mixed && match scm_repository /foobar/* :: allow use_common fedpkg sources
|
||||
match scm_host mixed && match scm_repository /foobaz/* :: allow use_common none
|
||||
'''
|
||||
}
|
||||
|
||||
class FakePolicy(object):
|
||||
|
||||
def __init__(self, rule):
|
||||
base_tests = koji.policy.findSimpleTests(vars(koji.policy))
|
||||
self.ruleset = koji.policy.SimpleRuleSet(rule.splitlines(), base_tests)
|
||||
|
||||
def evalPolicy(self, name, data):
|
||||
return self.ruleset.apply(data)
|
||||
|
||||
|
||||
class TestSCM(unittest.TestCase):
|
||||
|
||||
|
|
@ -67,8 +98,22 @@ class TestSCM(unittest.TestCase):
|
|||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
self.assertEqual(scm.scmtype, 'GIT')
|
||||
|
||||
def test_assert_allowed_basic(self):
|
||||
scm = SCM("git://scm.example.com/path1#1234")
|
||||
|
||||
# session must be passed
|
||||
with self.assertRaises(koji.GenericError) as cm:
|
||||
scm.assert_allowed(session=None, by_config=False, by_policy=True)
|
||||
self.assertEqual(str(cm.exception),
|
||||
'When allowed SCM assertion is by policy, session must be passed in.')
|
||||
|
||||
# allowed could not be None
|
||||
scm.assert_allowed_by_config = mock.MagicMock()
|
||||
scm.assert_allowed(allowed=None, by_config=True, by_policy=False)
|
||||
scm.assert_allowed_by_config.assert_called_once_with('')
|
||||
|
||||
@mock.patch('logging.getLogger')
|
||||
def test_allowed(self, getLogger):
|
||||
def test_allowed_by_config(self, getLogger):
|
||||
config = '''
|
||||
goodserver:*:no
|
||||
!badserver:*
|
||||
|
|
@ -104,7 +149,7 @@ class TestSCM(unittest.TestCase):
|
|||
raise AssertionError("allowed bad url: %s" % url)
|
||||
|
||||
@mock.patch('logging.getLogger')
|
||||
def test_badrule(self, getLogger):
|
||||
def test_badrule_by_config(self, getLogger):
|
||||
config = '''
|
||||
bogus-entry-should-be-ignored
|
||||
goodserver:*:no
|
||||
|
|
@ -115,7 +160,7 @@ class TestSCM(unittest.TestCase):
|
|||
scm.assert_allowed(config)
|
||||
|
||||
@mock.patch('logging.getLogger')
|
||||
def test_opts(self, getLogger):
|
||||
def test_opts_by_config(self, getLogger):
|
||||
config = '''
|
||||
default:*
|
||||
nocommon:*:no
|
||||
|
|
@ -196,6 +241,181 @@ class TestSCM(unittest.TestCase):
|
|||
with self.assertRaises(koji.BuildError):
|
||||
scm.assert_allowed(config)
|
||||
|
||||
def test_allowed_by_policy(self):
|
||||
good = [
|
||||
"git://goodserver/path1#1234",
|
||||
"git+ssh://maybeserver/path1#1234",
|
||||
]
|
||||
bad = [
|
||||
"cvs://badserver/projects/42#ref",
|
||||
"svn://badserver/projects/42#ref",
|
||||
"git://maybeserver/badpath/project#1234",
|
||||
"git://maybeserver//badpath/project#1234",
|
||||
"git://maybeserver////badpath/project#1234",
|
||||
"git://maybeserver/./badpath/project#1234",
|
||||
"git://maybeserver//.//badpath/project#1234",
|
||||
"git://maybeserver/goodpath/../badpath/project#1234",
|
||||
"git://maybeserver/goodpath/..//badpath/project#1234",
|
||||
"git://maybeserver/..//badpath/project#1234",
|
||||
]
|
||||
session = mock.MagicMock()
|
||||
session.evalPolicy.side_effect = FakePolicy(policy['one']).evalPolicy
|
||||
for url in good:
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed(session=session, by_config=False, by_policy=True)
|
||||
for url in bad:
|
||||
scm = SCM(url)
|
||||
with self.assertRaises(koji.BuildError) as cm:
|
||||
scm.assert_allowed(session=session, by_config=False, by_policy=True)
|
||||
self.assertRegexpMatches(str(cm.exception), '^SCM: .* is not allowed, reason: None$')
|
||||
|
||||
def test_opts_by_policy(self):
|
||||
session = mock.MagicMock()
|
||||
session.evalPolicy.side_effect = FakePolicy(policy['two']).evalPolicy
|
||||
|
||||
url = "git://default/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://nocommon/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://common/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://srccmd/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['fedpkg', 'sources'])
|
||||
|
||||
url = "git://nosrc/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, None)
|
||||
|
||||
url = "git://mixed/foo/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://mixed/bar/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://mixed/baz/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['fedpkg', 'sources'])
|
||||
|
||||
url = "git://mixed/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
with self.assertRaises(koji.BuildError):
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
|
||||
url = "git://mixed/foo/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://mixed/bar/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://mixed/baz/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['fedpkg', 'sources'])
|
||||
|
||||
url = "git://mixed/foobar/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertEqual(scm.source_cmd, ['fedpkg', 'sources'])
|
||||
|
||||
url = "git://mixed/foobaz/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertIsNone(scm.source_cmd)
|
||||
|
||||
url = "git://nomatch/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
with self.assertRaises(koji.BuildError):
|
||||
scm.assert_allowed_by_policy(session=session)
|
||||
|
||||
def test_assert_allowed_by_both(self):
|
||||
config = '''
|
||||
default:*:no:
|
||||
mixed:/foo/*:yes
|
||||
mixed:/bar/*:no
|
||||
mixed:/baz/*:no:centpkg,sources
|
||||
mixed:/foobar/*:no:
|
||||
mixed:/foobaz/*:no:centpkg,sources
|
||||
'''
|
||||
|
||||
session = mock.MagicMock()
|
||||
session.evalPolicy.side_effect = FakePolicy(policy['two']).evalPolicy
|
||||
|
||||
url = "git://default/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
# match scm_host default :: allow
|
||||
scm.assert_allowed(allowed=config, session=session, by_config=True, by_policy=True)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertIsNone(scm.source_cmd)
|
||||
|
||||
url = "git://mixed/foo/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
# match scm_host mixed && match scm_repository /foo/* :: allow
|
||||
scm.assert_allowed(allowed=config, session=session, by_config=True, by_policy=True)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://mixed/bar/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
# match scm_host mixed && match scm_repository /bar/* :: allow use_common
|
||||
scm.assert_allowed(allowed=config, session=session, by_config=True, by_policy=True)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertEqual(scm.source_cmd, ['make', 'sources'])
|
||||
|
||||
url = "git://mixed/baz/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
# match scm_host mixed && match scm_repository /baz/* :: allow fedpkg sources
|
||||
scm.assert_allowed(allowed=config, session=session, by_config=True, by_policy=True)
|
||||
self.assertEqual(scm.use_common, False)
|
||||
self.assertEqual(scm.source_cmd, ['fedpkg', 'sources'])
|
||||
|
||||
url = "git://mixed/foobar/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
# match scm_host mixed && match scm_repository /foobar/* :: allow use_common fedpkg sources
|
||||
scm.assert_allowed(allowed=config, session=session, by_config=True, by_policy=True)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertEqual(scm.source_cmd, ['fedpkg', 'sources'])
|
||||
|
||||
url = "git://mixed/foobaz/koji.git#1234"
|
||||
scm = SCM(url)
|
||||
# match scm_host mixed && match scm_repository /foobaz/* :: allow use_common none
|
||||
scm.assert_allowed(allowed=config, session=session, by_config=True, by_policy=True)
|
||||
self.assertEqual(scm.use_common, True)
|
||||
self.assertIsNone(scm.source_cmd)
|
||||
|
||||
|
||||
class TestSCMCheckouts(unittest.TestCase):
|
||||
|
||||
|
|
|
|||
17
vm/kojivmd
17
vm/kojivmd
|
|
@ -138,6 +138,8 @@ def get_options():
|
|||
'offline_retry': True,
|
||||
'offline_retry_interval': 120,
|
||||
'allowed_scms': '',
|
||||
'allowed_scms_by_config': True,
|
||||
'allowed_scms_by_policy': False,
|
||||
'cert': None,
|
||||
'serverca': None}
|
||||
if config.has_section('kojivmd'):
|
||||
|
|
@ -149,7 +151,8 @@ def get_options():
|
|||
defaults[name] = int(value)
|
||||
except ValueError:
|
||||
quit("value for %s option must be a valid integer" % name)
|
||||
elif name in ['offline_retry', 'no_ssl_verify']:
|
||||
elif name in ['offline_retry', 'no_ssl_verify', 'allowed_scms_by_config',
|
||||
'allowed_scms_by_policy']:
|
||||
defaults[name] = config.getboolean('kojivmd', name)
|
||||
elif name in ['plugin', 'plugins']:
|
||||
defaults['plugin'] = value.split()
|
||||
|
|
@ -325,8 +328,16 @@ class WinBuildTask(MultiPlatformTask):
|
|||
# verify the urls before passing them to the VM
|
||||
for url in [source_url] + koji.util.to_list(subopts.values()):
|
||||
scm = SCM(url)
|
||||
scm.assert_allowed(self.options.allowed_scms)
|
||||
|
||||
scm.assert_allowed(allowed=self.options.allowed_scms,
|
||||
session=self.session,
|
||||
by_config=self.options.allowed_scms_use_config,
|
||||
by_policy=self.options.allowed_scms_use_policy,
|
||||
policy_data={
|
||||
'user_id': self.taskinfo['owner'],
|
||||
'channel': self.session.getChannel(self.taskinfo['channel_id'],
|
||||
strict=True)['name'],
|
||||
'scratch': opts.get('scratch')
|
||||
})
|
||||
task_info = self.session.getTaskInfo(self.id)
|
||||
target_info = self.session.getBuildTarget(target)
|
||||
if not target_info:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,14 @@ server=http://hub.example.com/kojihub
|
|||
; dir, and will raise an exception if it cannot.
|
||||
allowed_scms=scm.example.com:/cvs/example git.example.org:/example svn.example.org:/users/*:no
|
||||
|
||||
; If use the option allowed_scms above for allowing / denying SCM, default: true
|
||||
; allowed_scms_use_config = true
|
||||
|
||||
; If use hub policy: build_from_scm for allowing / denying SCM, default: false
|
||||
; notice that if both options are enabled, both assertions will be applied, and user_common
|
||||
; will be overridden by the policy's result.
|
||||
; allowed_scms_use_policy = false
|
||||
|
||||
; The mail host to use for sending email notifications
|
||||
smtphost=example.com
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue