Allow config projects to override allowed-projects

To handle the case where an untrusted project defines a job with
a secret which another project would like to run, allow a config
project to attach that job to a project-pipeline and have it run
regardless of the allowed-projects setting.

Normally, untrusted jobs with secrets have an implicit and
non-overridable allowed-projects setting of only that project, to
avoid a situation where another project with a trusted post-review
pipeline gains access to the secret by using a Depends-On to a
change which lifts the allowed-projects restriction.  This change
allows a config project to bypass this, in effect saying that the
projects involved trust each other sufficiently (or else, do not
have access to a post-review pipeline which could be used to
obtain secrets).

Change-Id: I52ab193d0e39a37de64c8b3cb6953538e4073b43
This commit is contained in:
James E. Blair
2019-06-20 14:13:44 -07:00
parent 2029ac77ba
commit 9021fdf8bb
10 changed files with 102 additions and 3 deletions

View File

@@ -1117,11 +1117,15 @@ Here is an example of two job definitions:
all projects permitted to use the job. The current project
(where the job is defined) is not automatically included, so if
it should be able to run this job, then it must be explicitly
listed. By default, all projects may use the job.
listed. This setting is ignored by :term:`config projects
<config-project>` -- they may add any job to any project's
pipelines. By default, all projects may use the job.
If a :attr:`job.secrets` is used in a job definition in an
:term:`untrusted-project`, `allowed-projects` is automatically
set to the current project only, and can not be overridden.
However, a :term:`config-project` may still add such a job to
any project's pipeline.
.. warning::
@@ -1519,7 +1523,9 @@ If a job with secrets is unsafe to be used by other projects, the
:attr:`job.allowed-projects` attribute can be used to restrict the
projects which can invoke that job. If a job with secrets is defined
in an `untrusted-project`, `allowed-projects` is automatically set to
that project only, and can not be overridden.
that project only, and can not be overridden (though a
:term:`config-project` may still add the job to any project's pipeline
regardless of this setting).
Secrets, like most configuration items, are unique within a tenant,
though a secret may be defined on multiple branches of the same

View File

@@ -0,0 +1,7 @@
---
features:
- |
Config projects may now add any job to any project's pipelines,
regardless of the setting of allowed-projets (including the implicit
setting of allowed-projects on jobs with secrets in untrusted
projects).

View File

@@ -0,0 +1,2 @@
- hosts: all
tasks: []

View File

@@ -0,0 +1,30 @@
- pipeline:
name: check
manager: independent
post-review: True
trigger:
gerrit:
- event: patchset-created
success:
gerrit:
Verified: 1
failure:
gerrit:
Verified: -1
- job:
name: base
run: playbooks/base.yaml
parent: null
- project:
name: common-config
check:
jobs: []
- project:
name: org/project2
check:
jobs:
- test-project1

View File

@@ -0,0 +1,13 @@
- secret:
name: project1_secret
data: {}
- job:
name: test-project1
secrets: project1_secret
- project:
name: org/project1
check:
jobs:
- test-project1

View File

@@ -0,0 +1,4 @@
- project:
name: org/project2
check:
jobs: []

View File

@@ -0,0 +1,10 @@
- tenant:
name: tenant-one
source:
gerrit:
config-projects:
- common-config
untrusted-projects:
- org/project1
- org/project2
- org/project3

View File

@@ -801,6 +801,22 @@ class TestAllowedProjects(ZuulTestCase):
'to run job test-project2b', B.messages[0])
class TestAllowedProjectsTrusted(ZuulTestCase):
tenant_config_file = 'config/allowed-projects-trusted/main.yaml'
def test_allowed_projects_secret_trusted(self):
# Test that an untrusted job defined in project1 can be used
# in project2, but only if attached by a config project.
A = self.fake_gerrit.addFakeChange('org/project2', 'master', 'A')
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
self.assertEqual(A.reported, 1)
self.assertIn('Build succeeded', A.messages[0])
self.assertHistory([
dict(name='test-project1', result='SUCCESS', changes='1,1'),
], ordered=False)
class TestCentralJobs(ZuulTestCase):
tenant_config_file = 'config/central-jobs/main.yaml'

View File

@@ -670,6 +670,14 @@ class JobParser(object):
job.variant_description = conf.get(
'variant-description', " ".join(as_list(conf.get('branches'))))
if project_pipeline and conf['_source_context'].trusted:
# A config project has attached this job to a
# project-pipeline. In this case, we can ignore
# allowed-projects -- the superuser has stated they want
# it to run. This can be useful to allow untrusted jobs
# with secrets to be run in other untrusted projects.
job.ignore_allowed_projects = True
if 'parent' in conf:
if conf['parent'] is not None:
# Parent job is explicitly specified, so inherit from it.

View File

@@ -1145,6 +1145,8 @@ class Job(ConfigObject):
provides=frozenset(),
requires=frozenset(),
dependencies=frozenset(),
ignore_allowed_projects=None, # internal, but inherited
# in the usual manner
)
# These attributes affect how the job is actually run and more
@@ -3941,7 +3943,8 @@ class Layout(object):
raise Exception("Job %s is abstract and may not be "
"directly run" %
(frozen_job.name,))
if (frozen_job.allowed_projects is not None and
if (not frozen_job.ignore_allowed_projects and
frozen_job.allowed_projects is not None and
change.project.name not in frozen_job.allowed_projects):
raise Exception("Project %s is not allowed to run job %s" %
(change.project.name, frozen_job.name))