Add allow-base-jobs tenant config attribute
Admins may want to allow tenants to utilize some of the powers of config-projects without allowing them to define base jobs. Base jobs often perform security-critical actions which should not be omitted under any circumstance. To facilitate this, allow for the prohibition of base jobs within the tenant definition. Change-Id: I2382a35a2d683175508fbb658609e0035028b69e
This commit is contained in:
+12
-2
@@ -139,8 +139,8 @@ configuration. Some examples of tenant definitions are:
|
||||
|
||||
.. attr:: <project>
|
||||
|
||||
The config-projects have an additional config option that
|
||||
may be specified optionally.
|
||||
The config-projects have the following optional additional
|
||||
config options:
|
||||
|
||||
.. attr:: load-branch
|
||||
:default: master
|
||||
@@ -149,6 +149,16 @@ configuration. Some examples of tenant definitions are:
|
||||
default config projects load Zuul configuration only
|
||||
from the master branch.
|
||||
|
||||
.. attr:: allow-base-jobs
|
||||
:type: bool
|
||||
:default: true
|
||||
|
||||
Set to ``false`` to disallow base job definitions in this
|
||||
project. Base jobs are typically critical for the
|
||||
security of the entire Zuul system. This setting permits
|
||||
the use of config-projects for features (such as pipeline
|
||||
definition) without the ability to define base jobs.
|
||||
|
||||
.. attr:: untrusted-projects
|
||||
|
||||
A list of projects to be treated as untrusted in this tenant.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The :attr:`tenant.config-projects.<project>.allow-base-jobs`
|
||||
tenant configuration attribute has been added. This facilitates
|
||||
the configuration of a tenant with the ability to use all of the
|
||||
aspects of a config-project other than defining base jobs when the
|
||||
definition of a base job may be critical for system-wide security.
|
||||
@@ -22,3 +22,8 @@
|
||||
parent: null
|
||||
tags:
|
||||
- otherbase
|
||||
|
||||
- project:
|
||||
name: extra-config
|
||||
check:
|
||||
jobs: []
|
||||
|
||||
+2
@@ -5,5 +5,7 @@
|
||||
gerrit:
|
||||
config-projects:
|
||||
- common-config
|
||||
- extra-config:
|
||||
allow-base-jobs: false
|
||||
untrusted-projects:
|
||||
- org/project
|
||||
|
||||
+23
-1
@@ -8353,7 +8353,29 @@ class TestBaseJobs(ZuulTestCase):
|
||||
self.assertEqual(A.reported, 1,
|
||||
"A should report failure")
|
||||
self.assertEqual(A.patchsets[0]['approvals'][0]['value'], "-1")
|
||||
self.assertIn('Base jobs must be defined in config projects',
|
||||
self.assertIn('Base jobs may only be defined',
|
||||
A.messages[0])
|
||||
self.assertHistory([])
|
||||
|
||||
def test_disallowed_base_job(self):
|
||||
# Test that we can disable configuring base jobs even in
|
||||
# config-projects
|
||||
in_repo_conf = textwrap.dedent(
|
||||
"""
|
||||
- job:
|
||||
name: fail-base
|
||||
parent: null
|
||||
""")
|
||||
|
||||
file_dict = {'.zuul.yaml': in_repo_conf}
|
||||
A = self.fake_gerrit.addFakeChange('extra-config', 'master', 'A',
|
||||
files=file_dict)
|
||||
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
|
||||
self.waitUntilSettled()
|
||||
self.assertEqual(A.reported, 1,
|
||||
"A should report failure")
|
||||
self.assertEqual(A.patchsets[0]['approvals'][0]['value'], "-1")
|
||||
self.assertIn('Base jobs may only be defined',
|
||||
A.messages[0])
|
||||
self.assertHistory([])
|
||||
|
||||
|
||||
@@ -2107,11 +2107,14 @@ class TenantParser(object):
|
||||
'allow-circular-dependencies': bool,
|
||||
'implied-branch-matchers': bool,
|
||||
}
|
||||
config_project_dict = {str: inner_config_project_dict}
|
||||
|
||||
inner_untrusted_project_dict = inner_config_project_dict.copy()
|
||||
inner_untrusted_project_dict['configure-projects'] = to_list(str)
|
||||
inner_untrusted_project_dict['allow-reporter-jobs'] = bool
|
||||
|
||||
inner_config_project_dict['allow-base-jobs'] = bool
|
||||
|
||||
config_project_dict = {str: inner_config_project_dict}
|
||||
untrusted_project_dict = {str: inner_untrusted_project_dict}
|
||||
|
||||
config_project = vs.Any(str, config_project_dict)
|
||||
@@ -2404,6 +2407,7 @@ class TenantParser(object):
|
||||
project_implied_branch_matchers = None
|
||||
project_configure_projects = None
|
||||
project_allow_reporter_jobs = None
|
||||
project_allow_base_jobs = None
|
||||
else:
|
||||
project_name = list(conf.keys())[0]
|
||||
project = source.getProject(project_name)
|
||||
@@ -2480,6 +2484,8 @@ class TenantParser(object):
|
||||
project_configure_projects = None
|
||||
project_allow_reporter_jobs = conf[project_name].get(
|
||||
'allow-reporter-jobs', None)
|
||||
project_allow_base_jobs = conf[project_name].get(
|
||||
'allow-base-jobs', None)
|
||||
|
||||
tenant_project_config = model.TenantProjectConfig(project)
|
||||
tenant_project_config.load_classes = frozenset(project_include)
|
||||
@@ -2501,6 +2507,8 @@ class TenantParser(object):
|
||||
project_configure_projects
|
||||
tenant_project_config.allow_reporter_jobs = \
|
||||
project_allow_reporter_jobs
|
||||
tenant_project_config.allow_base_jobs = \
|
||||
project_allow_base_jobs
|
||||
return tenant_project_config
|
||||
|
||||
def _getProjects(self, source, conf, current_include):
|
||||
|
||||
@@ -386,6 +386,17 @@ class ProjectNotPermittedError(ConfigurationSyntaxError):
|
||||
super(ProjectNotPermittedError, self).__init__(message)
|
||||
|
||||
|
||||
class BaseJobNotPermittedError(ConfigurationSyntaxError):
|
||||
zuul_error_name = 'Base Job Not Permitted'
|
||||
|
||||
def __init__(self):
|
||||
message = textwrap.dedent("""\
|
||||
Base jobs may only be defined in config projects, and
|
||||
only if permitted by the tenant configuration.""")
|
||||
message = textwrap.fill(message)
|
||||
super(BaseJobNotPermittedError, self).__init__(message)
|
||||
|
||||
|
||||
class AuthZRuleNotFoundError(ConfigurationSyntaxError):
|
||||
zuul_error_name = 'AuthZ Rule Not Found'
|
||||
|
||||
|
||||
+15
-6
@@ -42,12 +42,13 @@ from zuul import change_matcher
|
||||
from zuul.exceptions import (
|
||||
SEVERITY_ERROR,
|
||||
SEVERITY_WARNING,
|
||||
OIDCIssuerNotAllowedError,
|
||||
BaseJobNotPermittedError,
|
||||
LabelForbiddenError,
|
||||
LabelNotFoundError,
|
||||
MaxOIDCTTLError,
|
||||
MaxTimeoutError,
|
||||
LabelNotFoundError,
|
||||
NodesetNotFoundError,
|
||||
OIDCIssuerNotAllowedError,
|
||||
PreTimeoutExceedsTimeoutError,
|
||||
ProjectNotFoundError,
|
||||
ProjectNotPermittedError,
|
||||
@@ -9677,6 +9678,7 @@ class TenantProjectConfig(object):
|
||||
# to configure
|
||||
self.configure_projects = None
|
||||
self.allow_reporter_jobs = None
|
||||
self.allow_base_jobs = None
|
||||
|
||||
def canConfigureProject(self, other_tpc, validation_only=False):
|
||||
if self.trusted:
|
||||
@@ -9694,6 +9696,13 @@ class TenantProjectConfig(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
def canDefineBaseJobs(self):
|
||||
if not self.trusted:
|
||||
return False
|
||||
if self.allow_base_jobs is False:
|
||||
return False
|
||||
return True
|
||||
|
||||
def isAlwaysDynamicBranch(self, branch):
|
||||
if self.always_dynamic_branches is None:
|
||||
return False
|
||||
@@ -10552,10 +10561,10 @@ class Layout(object):
|
||||
job.post_timeout > self.tenant.max_job_timeout):
|
||||
raise MaxTimeoutError(job, self.tenant)
|
||||
|
||||
if job.isBase() and not self.tenant.isTrusted(
|
||||
job.source_context.project_canonical_name):
|
||||
raise Exception(
|
||||
"Base jobs must be defined in config projects")
|
||||
source_tpc = self.tenant.getTPC(
|
||||
job.source_context.project_canonical_name)
|
||||
if job.isBase() and not source_tpc.canDefineBaseJobs():
|
||||
raise BaseJobNotPermittedError()
|
||||
|
||||
def addJob(self, job):
|
||||
self._checkAddJob(job)
|
||||
|
||||
Reference in New Issue
Block a user