kojid: extend SCM.assert_allowed with hub policy

This is a simple extention of `SCM.assert_allowed`

- `assert_allowed_by_policy` will set the default "use_common" to False which is different to the old behavior
- `channel`, `user_id`, `scratch` are passed in the `policy_data` with scminfo right now.

This is a prototype for this change, and there are some other solutions could be implemented too

- Use a scmpolicy plugin as `postSCMCheckout` callback, the pro is that we can do more checks after the source is initialized on builder, meanwhile, the con is that the source will be downloaded even it is denied by policy. It might be a potential risk?
- Do the scm check in hub's `make_task`, this looks straightforward, but may lack some builder's information

fixes: #2757
This commit is contained in:
Yu Ming Zhu 2021-07-16 15:33:39 +00:00
parent ec70d21c41
commit 47c4b5d70b
8 changed files with 431 additions and 25 deletions

View file

@ -327,7 +327,16 @@ 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, opts=None):
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, **(opts or {}))
def assert_allowed_by_config(self, allowed):
"""
Check this scm against allowed list and apply options
@ -396,6 +405,74 @@ 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, **opts):
"""
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:
- scmurl
- scmscheme
- scmuser
- scmhost
- scmrepository
- scmmodule
- scmrevision
- scmtype
More keys could be added in opts. 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)
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 scmhost scm.example.com :: allow use_common make sources
match scmhost 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()):
if not k.startswith('scm'):
k = 'scm' + k
policy_data[k] = v
policy_data.update(opts)
result = (session.host.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: