Merge "Decrypt secrets and plumb to Ansible" into feature/zuulv3
diff --git a/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml b/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
index 45acb87..3371a20 100644
--- a/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
@@ -6,5 +6,8 @@
- copy:
src: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
dest: "{{zuul._test.test_root}}/{{zuul.uuid}}.copied"
+ - copy:
+ content: "{{test_secret.username}} {{test_secret.password}}"
+ dest: "{{zuul._test.test_root}}/{{zuul.uuid}}.secrets"
roles:
- bare-role
diff --git a/tests/fixtures/config/ansible/git/common-config/zuul.yaml b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
index c9fba3e..dac5f34 100644
--- a/tests/fixtures/config/ansible/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/zuul.yaml
@@ -57,6 +57,9 @@
flagpath: '{{zuul._test.test_root}}/{{zuul.uuid}}.flag'
roles:
- zuul: bare-role
+ auth:
+ secrets:
+ - test_secret
- job:
parent: python27
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index 57ee54b..d2da426 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -19,11 +19,13 @@
import fixtures
import testtools
import yaml
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.backends import default_backend
from zuul import model
from zuul import configloader
-from tests.base import BaseTestCase
+from tests.base import BaseTestCase, FIXTURE_DIR
class TestJob(BaseTestCase):
@@ -31,6 +33,13 @@
def setUp(self):
super(TestJob, self).setUp()
self.project = model.Project('project', None)
+ private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
+ with open(private_key_file, "rb") as f:
+ self.project.private_key = serialization.load_pem_private_key(
+ f.read(),
+ password=None,
+ backend=default_backend()
+ )
self.context = model.SourceContext(self.project, 'master',
'test', True)
self.start_mark = yaml.Mark('name', 0, 0, 0, '', 0)
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index a4442a4..5ff5743 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -290,6 +290,11 @@
build.uuid + '.bare-role.flag')
self.assertTrue(os.path.exists(bare_role_flag_path))
+ secrets_path = os.path.join(self.test_root,
+ build.uuid + '.secrets')
+ with open(secrets_path) as f:
+ self.assertEqual(f.read(), "test-username test-password")
+
class TestBrokenConfig(ZuulTestCase):
# Test that we get an appropriate syntax error if we start with a
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 22ba3fb..cbd7d40 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -24,6 +24,8 @@
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import padding
+from cryptography.hazmat.primitives import hashes
from zuul import model
import zuul.manager.dependent
import zuul.manager.independent
@@ -130,12 +132,32 @@
yaml_loader = yaml.SafeLoader
def __init__(self, ciphertext):
- self.ciphertext = ciphertext
+ self.ciphertext = ciphertext.decode('base64')
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+ def __eq__(self, other):
+ if not isinstance(other, EncryptedPKCS1):
+ return False
+ return (self.ciphertext == other.ciphertext)
@classmethod
def from_yaml(cls, loader, node):
return cls(node.value)
+ def decrypt(self, private_key):
+ # https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/#decryption
+ plaintext = private_key.decrypt(
+ self.ciphertext,
+ padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA1()),
+ algorithm=hashes.SHA1(),
+ label=None
+ )
+ )
+ return plaintext
+
class NodeSetParser(object):
@staticmethod
@@ -272,7 +294,8 @@
"Unable to use secret %s. Secrets must be "
"defined in the same project in which they "
"are used" % secret_name)
- job.auth.secrets.append(secret)
+ job.auth.secrets.append(secret.decrypt(
+ job.source_context.project.private_key))
if 'parent' in conf:
parent = layout.getJob(conf['parent'])
diff --git a/zuul/executor/client.py b/zuul/executor/client.py
index fd92dd9..20bc3ef 100644
--- a/zuul/executor/client.py
+++ b/zuul/executor/client.py
@@ -319,6 +319,9 @@
public_ipv4=node.public_ipv4))
params['nodes'] = nodes
params['vars'] = copy.deepcopy(job.variables)
+ if job.auth:
+ for secret in job.auth.secrets:
+ params['vars'][secret.name] = copy.deepcopy(secret.secret_data)
params['vars']['zuul'] = zuul_params
projects = set()
if job.repos:
diff --git a/zuul/model.py b/zuul/model.py
index 3370112..489f894 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -545,6 +545,20 @@
def __repr__(self):
return '<Secret %s>' % (self.name,)
+ def decrypt(self, private_key):
+ """Return a copy of this secret with any encrypted data decrypted.
+ Note that the original remains encrypted."""
+
+ r = copy.deepcopy(self)
+ decrypted_secret_data = {}
+ for k, v in r.secret_data.items():
+ if hasattr(v, 'decrypt'):
+ decrypted_secret_data[k] = v.decrypt(private_key)
+ else:
+ decrypted_secret_data[k] = v
+ r.secret_data = decrypted_secret_data
+ return r
+
class SourceContext(object):
"""A reference to the branch of a project in configuration.