Skip to content

Commit e238ae9

Browse files
authored
Cyptography pr 20566 rebase (ansible#25560)
Make pyca/cryptography the preferred backend for cryptographic needs (mainly vault) falling back to pycrypto pyca/cryptography is already implicitly a dependency in many cases through paramiko (2.0+) as well as the new openssl_publickey module, which requires pyOpenSSL 16.0+. Additionally, pyca/cryptography is an optional dep for better performance with vault already. This commit leverages cryptography's padding, constant time comparisons, and CBC/CTR modes to reduce the amount of code ansible needs to maintain. * Handle wrong password given for VaultAES format * Do not display deprecation warning for cryptography on python-2.6 * Namespace all of the pycrypto imports and always import them Makes unittests better and the code less likely to get stupid mistakes (like using HMAC from cryptogrpahy when the one from pycrypto is needed) * Add back in atfork since we need pycrypto to reinitialize its RNG just in case we're being used with old paramiko * contrib/inventory/gce: Remove spurious require on pycrypto (cherry picked from commit 9e16b9d) * Add cryptography to ec2_win_password module requirements * Fix python3 bug which would pass text strings to a function which requires byte strings. * Attempt to add pycrypto version to setup deps * Change hacking README for dual pycrypto/cryptography * update dependencies for various CI scripts * additional CI dockerfile/script updates * add paramiko to the windows and sanity requirement set This is needed because ansible lists it as a requirement. Previously the missing dep wasn't enforced, but cryptography imports pkg_resources so you can't ignore a requirement any more * Add integration test cases for old vault and for wrong passwords * helper script for manual testing of pycrypto/cryptography * Skip the pycrypto tests so that users without it installed can still run the unittests * Run unittests for vault with both cryptography and pycrypto backend
1 parent e7e091d commit e238ae9

25 files changed

+456
-242
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ Ansible Changes By Release
6868
* Experimentally added pmrun become method.
6969
* Enable the docker connection plugin to use su as a become method
7070
* Add an encoding parameter for the replace module so that it can operate on non-utf-8 files
71+
* By default, Ansible now uses the cryptography module to implement vault
72+
instead of the older pycrypto module.
7173

7274
#### New Callbacks:
7375
- profile_roles
@@ -92,6 +94,7 @@ Ansible Changes By Release
9294

9395
- The docker_container module has gained a new option, working_dir which allows
9496
specifying the working directory for the command being run in the image.
97+
- The ec2_win_password module now requires the cryptography python module be installed to run
9598

9699
### New Modules
97100

contrib/inventory/gce.py

-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
Version: 0.0.3
7575
'''
7676

77-
__requires__ = ['pycrypto>=2.6']
7877
try:
7978
import pkg_resources
8079
except ImportError:

hacking/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ and do not wish to install them from your operating system package manager, you
1717
can install them from pip
1818

1919
$ easy_install pip # if pip is not already available
20-
$ pip install pyyaml jinja2 nose pytest passlib pycrypto
20+
$ pip install -r requirements.txt
2121

2222
From there, follow ansible instructions on docs.ansible.com as normal.
2323

lib/ansible/executor/process/worker.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626

2727
from jinja2.exceptions import TemplateNotFound
2828

29-
# TODO: not needed if we use the cryptography library with its default RNG
30-
# engine
31-
HAS_ATFORK = True
29+
HAS_PYCRYPTO_ATFORK = False
3230
try:
3331
from Crypto.Random import atfork
34-
except ImportError:
35-
HAS_ATFORK = False
32+
HAS_PYCRYPTO_ATFORK = True
33+
except:
34+
# We only need to call atfork if pycrypto is used because it will need to
35+
# reinitialize its RNG. Since old paramiko could be using pycrypto, we
36+
# need to take charge of calling it.
37+
pass
3638

3739
from ansible.errors import AnsibleConnectionFailure
3840
from ansible.executor.task_executor import TaskExecutor
@@ -99,7 +101,7 @@ def run(self):
99101
# pr = cProfile.Profile()
100102
# pr.enable()
101103

102-
if HAS_ATFORK:
104+
if HAS_PYCRYPTO_ATFORK:
103105
atfork()
104106

105107
try:

lib/ansible/modules/cloud/amazon/ec2_win_password.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@
6060
extends_documentation_fragment:
6161
- aws
6262
- ec2
63+
64+
requirements:
65+
- cryptography
66+
67+
notes:
68+
- As of Ansible 2.4, this module requires the python cryptography module rather than the
69+
older pycrypto module.
6370
'''
6471

6572
EXAMPLES = '''
@@ -95,16 +102,21 @@
95102
'''
96103

97104
from base64 import b64decode
98-
from Crypto.Cipher import PKCS1_v1_5
99-
from Crypto.PublicKey import RSA
105+
from os.path import expanduser
100106
import datetime
107+
from cryptography.hazmat.backends import default_backend
108+
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
109+
from cryptography.hazmat.primitives.serialization import load_pem_private_key
101110

102111
try:
103112
import boto.ec2
104113
HAS_BOTO = True
105114
except ImportError:
106115
HAS_BOTO = False
107116

117+
BACKEND = default_backend()
118+
119+
108120
def main():
109121
argument_spec = ec2_argument_spec()
110122
argument_spec.update(dict(
@@ -122,7 +134,7 @@ def main():
122134

123135
instance_id = module.params.get('instance_id')
124136
key_file = module.params.get('key_file')
125-
key_passphrase = module.params.get('key_passphrase')
137+
b_key_passphrase = to_bytes(module.params.get('key_passphrase'), errors='surrogate_or_strict')
126138
wait = module.params.get('wait')
127139
wait_timeout = int(module.params.get('wait_timeout'))
128140

@@ -147,21 +159,18 @@ def main():
147159
module.fail_json(msg = "wait for password timeout after %d seconds" % wait_timeout)
148160

149161
try:
150-
f = open(key_file, 'r')
162+
f = open(key_file, 'rb')
151163
except IOError as e:
152164
module.fail_json(msg = "I/O error (%d) opening key file: %s" % (e.errno, e.strerror))
153165
else:
154166
try:
155167
with f:
156-
key = RSA.importKey(f.read(), key_passphrase)
157-
except (ValueError, IndexError, TypeError) as e:
168+
key = load_pem_private_key(f.read(), b_key_passphrase, BACKEND)
169+
except (ValueError, TypeError) as e:
158170
module.fail_json(msg = "unable to parse key file")
159171

160-
cipher = PKCS1_v1_5.new(key)
161-
sentinel = 'password decryption failed!!!'
162-
163172
try:
164-
decrypted = cipher.decrypt(decoded, sentinel)
173+
decrypted = key.decrypt(decoded, PKCS1v15())
165174
except ValueError as e:
166175
decrypted = None
167176

0 commit comments

Comments
 (0)