Serve project SSH keys and document
Add documentation about the per-project ssh keys and serve the public keys from the webserver. Change-Id: I7a1aa0bd4adcecde6e1c21f29f7e0a1e88a69f98
This commit is contained in:
parent
dbe1306b36
commit
91424a6ac3
|
@ -597,17 +597,46 @@ executor running the job is available:
|
|||
SSH Keys
|
||||
--------
|
||||
|
||||
Zuul starts each job with an SSH agent running and the key used to
|
||||
access the job's nodes added to that agent. Generally you won't need
|
||||
to be aware of this since Ansible will use this when performing any
|
||||
tasks on remote nodes. However, under some circumstances you may want
|
||||
to interact with the agent. For example, you may wish to add a key
|
||||
provided as a secret to the job in order to access a specific host, or
|
||||
you may want to, in a pre-playbook, replace the key used to log into
|
||||
the assigned nodes in order to further protect it from being abused by
|
||||
untrusted job content.
|
||||
Zuul starts each job with an SSH agent running and at least one key
|
||||
added to that agent. Generally you won't need to be aware of this
|
||||
since Ansible will use this when performing any tasks on remote nodes.
|
||||
However, under some circumstances you may want to interact with the
|
||||
agent. For example, you may wish to add a key provided as a secret to
|
||||
the job in order to access a specific host, or you may want to, in a
|
||||
pre-playbook, replace the key used to log into the assigned nodes in
|
||||
order to further protect it from being abused by untrusted job
|
||||
content.
|
||||
|
||||
.. TODO: describe standard lib and link to published docs for it.
|
||||
A description of each of the keys added to the SSH agent follows.
|
||||
|
||||
Nodepool Key
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This key is supplied by the system administrator. It is expected to
|
||||
be accepted by every node supplied by Nodepool and is generally the
|
||||
key that will be used by Zuul when running jobs. Because of the
|
||||
potential for an unrelated job to add an arbitrary host to the Ansible
|
||||
inventory which might accept this key (e.g., a node for another job,
|
||||
or a static host), the use of the `add-build-sshkey
|
||||
<https://zuul-ci.org/docs/zuul-jobs/roles.html#role-add-build-sshkey>`
|
||||
role is recommended.
|
||||
|
||||
Project Key
|
||||
~~~~~~~~~~~
|
||||
|
||||
Each project in Zuul has its own SSH keypair. This key is added to
|
||||
the SSH agent for all jobs running in a post-review pipeline. If a
|
||||
system administrator trusts that project, they can add the project's
|
||||
public key to systems to allow post-review jobs to access those
|
||||
systems. The systems may be added to the inventory using the
|
||||
``add_host`` Ansible module, or they may be supplied by static nodes
|
||||
in Nodepool.
|
||||
|
||||
Zuul serves each project's public SSH key using its build-in
|
||||
webserver. They can be fetched at the path
|
||||
``/api/tenant/<tenant>/project-ssh-key/<project>.pub`` where
|
||||
``<project>`` is the canonical name of a project and ``<tenant>`` is
|
||||
the name of a tenant with that project.
|
||||
|
||||
.. _return_values:
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- |
|
||||
An SSH keypair is now generated for every project and may be used in
|
||||
post-review jobs to access systems which trust that project.
|
|
@ -0,0 +1 @@
|
|||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZygeifuwUxtep5ENTPJ2sW+nmqkchtkF9o3ywnJpKJgViRTOQxbUpyfWZFVSmzaQGjarO8KMOw6w0YZ1s+cPHCtP6yw3cj7uy1ZWtxzpKH2AUW+3s74XxrUVKk78vcKQ4GFh3+vRMtn9Qrp8Fj/sT2WaeGhelnGm8HOjEYdgH/SiFkbTqjVxYFyYLrC+9qhh5fu51S5abpaXHfYM374gSvPWLCtrI1+Ws7J3jV+CfflEPex1rL17OVAmtq62fKOAl89dYxvNeA83S1ylEEKIWZVFFwjapU8d5dZFLfKE0c9ik5NcIDhahzSJjTwcCJyDzKMgadPwaKxEB++mpFkzT
|
|
@ -385,6 +385,14 @@ class TestWeb(BaseTestWeb):
|
|||
resp = self.get_url("api/tenant/tenant-one/key/org/no-project.pub")
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
||||
with open(os.path.join(FIXTURE_DIR, 'ssh.pub'), 'rb') as f:
|
||||
public_ssh = f.read()
|
||||
|
||||
resp = self.get_url("api/tenant/tenant-one/project-ssh-key/"
|
||||
"org/project.pub")
|
||||
self.assertEqual(resp.content, public_ssh)
|
||||
self.assertIn('text/plain', resp.headers.get('Content-Type'))
|
||||
|
||||
def test_web_404_on_unknown_tenant(self):
|
||||
resp = self.get_url("api/tenant/non-tenant/status")
|
||||
self.assertEqual(404, resp.status_code)
|
||||
|
|
|
@ -167,7 +167,7 @@ class KeyStorage(object):
|
|||
with open(private_key_file, 'r') as f:
|
||||
private_key = f.read()
|
||||
public_key = key.get_base64()
|
||||
return (private_key, public_key)
|
||||
return (private_key, 'ssh-rsa ' + public_key)
|
||||
|
||||
def _createSSHKey(self, fn):
|
||||
key_dir = os.path.dirname(fn)
|
||||
|
|
|
@ -399,8 +399,16 @@ class RPCListener(object):
|
|||
if not project:
|
||||
job.sendWorkComplete("")
|
||||
return
|
||||
job.sendWorkComplete(
|
||||
encryption.serialize_rsa_public_key(project.public_secrets_key))
|
||||
keytype = args.get('key', 'secrets')
|
||||
if keytype == 'secrets':
|
||||
job.sendWorkComplete(
|
||||
encryption.serialize_rsa_public_key(
|
||||
project.public_secrets_key))
|
||||
elif keytype == 'ssh':
|
||||
job.sendWorkComplete(project.public_ssh_key)
|
||||
else:
|
||||
job.sendWorkComplete("")
|
||||
return
|
||||
|
||||
def handle_config_errors_list(self, job):
|
||||
args = json.loads(job.arguments)
|
||||
|
|
|
@ -305,7 +305,8 @@ class ZuulWebAPI(object):
|
|||
@cherrypy.tools.save_params()
|
||||
def key(self, tenant, project):
|
||||
job = self.rpc.submitJob('zuul:key_get', {'tenant': tenant,
|
||||
'project': project})
|
||||
'project': project,
|
||||
'key': 'secrets'})
|
||||
if not job.data:
|
||||
raise cherrypy.HTTPError(
|
||||
404, 'Project %s does not exist.' % project)
|
||||
|
@ -314,6 +315,20 @@ class ZuulWebAPI(object):
|
|||
resp.headers['Content-Type'] = 'text/plain'
|
||||
return job.data[0]
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
def project_ssh_key(self, tenant, project):
|
||||
job = self.rpc.submitJob('zuul:key_get', {'tenant': tenant,
|
||||
'project': project,
|
||||
'key': 'ssh'})
|
||||
if not job.data:
|
||||
raise cherrypy.HTTPError(
|
||||
404, 'Project %s does not exist.' % project)
|
||||
resp = cherrypy.response
|
||||
resp.headers['Access-Control-Allow-Origin'] = '*'
|
||||
resp.headers['Content-Type'] = 'text/plain'
|
||||
return job.data[0] + '\n'
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.save_params()
|
||||
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
|
||||
|
@ -488,6 +503,9 @@ class ZuulWeb(object):
|
|||
controller=api, action='job')
|
||||
route_map.connect('api', '/api/tenant/{tenant}/key/{project:.*}.pub',
|
||||
controller=api, action='key')
|
||||
route_map.connect('api', '/api/tenant/{tenant}/'
|
||||
'project-ssh-key/{project:.*}.pub',
|
||||
controller=api, action='project_ssh_key')
|
||||
route_map.connect('api', '/api/tenant/{tenant}/console-stream',
|
||||
controller=api, action='console_stream')
|
||||
route_map.connect('api', '/api/tenant/{tenant}/builds',
|
||||
|
|
Loading…
Reference in New Issue