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:
Tomas Kopecek 2021-08-18 13:23:30 +02:00
commit 6d4abedb86
13 changed files with 521 additions and 31 deletions

View file

@ -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()

View file

@ -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

View file

@ -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.

View file

@ -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``

View file

@ -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
--------------------------------------

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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:

View file

@ -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']

View file

@ -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):

View file

@ -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:

View file

@ -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