diff --git a/lib/ansible/module_utils/crypto.py b/lib/ansible/module_utils/crypto.py index 06adfa6109..d9526b8026 100644 --- a/lib/ansible/module_utils/crypto.py +++ b/lib/ansible/module_utils/crypto.py @@ -26,12 +26,14 @@ except ImportError: import hashlib -def get_fingerprint(path): +def get_fingerprint(path, passphrase): """Generate the fingerprint of the public key. """ fingerprint = {} - privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, open(path, 'r').read()) + privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, + open(path, 'rb').read(), + passphrase) try: publickey = crypto.dump_publickey(crypto.FILETYPE_ASN1, privatekey) diff --git a/lib/ansible/modules/crypto/openssl_csr.py b/lib/ansible/modules/crypto/openssl_csr.py index 594d99a9b5..217e40ce1d 100644 --- a/lib/ansible/modules/crypto/openssl_csr.py +++ b/lib/ansible/modules/crypto/openssl_csr.py @@ -50,6 +50,11 @@ options: required: true description: - Path to the privatekey to use when signing the certificate signing request + privatekey_passphrase: + required: false + description: + - The passphrase for the privatekey. + version_added: "2.4" version: required: false default: 3 @@ -114,6 +119,14 @@ EXAMPLES = ''' privatekey_path: /etc/ssl/private/ansible.com.pem commonName: www.ansible.com +# Generate an OpenSSL Certificate Signing Request with a +# passphrase protected private key +- openssl_csr: + path: /etc/ssl/csr/www.ansible.com.csr + privatekey_path: /etc/ssl/private/ansible.com.pem + privatekey_passphrase: ansible + commonName: www.ansible.com + # Generate an OpenSSL Certificate Signing Request with Subject information - openssl_csr: path: /etc/ssl/csr/www.ansible.com.csr @@ -183,6 +196,7 @@ class CertificateSigningRequest(object): self.subjectAltName = module.params['subjectAltName'] self.path = module.params['path'] self.privatekey_path = module.params['privatekey_path'] + self.privatekey_passphrase = module.params['privatekey_passphrase'] self.version = module.params['version'] self.changed = True self.request = None @@ -218,8 +232,9 @@ class CertificateSigningRequest(object): req.add_extensions([crypto.X509Extension(b"subjectAltName", False, self.subjectAltName.encode('ascii'))]) privatekey_content = open(self.privatekey_path).read() - self.privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, privatekey_content) - + self.privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, + privatekey_content, + self.privatekey_passphrase) req.set_pubkey(self.privatekey) req.sign(self.privatekey, self.digest) self.request = req @@ -267,6 +282,7 @@ def main(): state=dict(default='present', choices=['present', 'absent'], type='str'), digest=dict(default='sha256', type='str'), privatekey_path=dict(require=True, type='path'), + privatekey_passphrase=dict(type='str', no_log=True), version=dict(default='3', type='int'), force=dict(default=False, type='bool'), subjectAltName=dict(aliases=['subjectAltName'], type='str'), diff --git a/lib/ansible/modules/crypto/openssl_privatekey.py b/lib/ansible/modules/crypto/openssl_privatekey.py index f2eeed837a..c5efd0bac0 100644 --- a/lib/ansible/modules/crypto/openssl_privatekey.py +++ b/lib/ansible/modules/crypto/openssl_privatekey.py @@ -62,14 +62,30 @@ options: required: true description: - Name of the file in which the generated TLS/SSL private key will be written. It will have 0600 mode. + passphrase: + required: false + description: + - The passphrase for the private key. + version_added: "2.4" + cipher: + required: false + description: + - The cipher to encrypt the private key. (cipher can be found by running `openssl list-cipher-algorithms`) + version_added: "2.4" ''' EXAMPLES = ''' # Generate an OpenSSL private key with the default values (4096 bits, RSA) -# and no public key - openssl_privatekey: path: /etc/ssl/private/ansible.com.pem +# Generate an OpenSSL private key with the default values (4096 bits, RSA) +# and a passphrase +- openssl_privatekey: + path: /etc/ssl/private/ansible.com.pem + passphrase: ansible + cipher: aes256 + # Generate an OpenSSL private key with a different size (2048 bits) - openssl_privatekey: path: /etc/ssl/private/ansible.com.pem @@ -145,13 +161,14 @@ class PrivateKey(object): self.type = module.params['type'] self.force = module.params['force'] self.path = module.params['path'] + self.passphrase = module.params['passphrase'] + self.cipher = module.params['cipher'] self.mode = module.params['mode'] self.changed = True self.privatekey = None self.fingerprint = {} self.check_mode = module.check_mode - def generate(self, module): """Generate a keypair.""" @@ -173,7 +190,11 @@ class PrivateKey(object): os.O_WRONLY | os.O_CREAT | os.O_TRUNC, self.mode) - os.write(privatekey_file, crypto.dump_privatekey(crypto.FILETYPE_PEM, self.privatekey)) + extras = {} + if self.cipher and self.passphrase: + extras = {'cipher': self.cipher, 'passphrase': self.passphrase} + + os.write(privatekey_file, crypto.dump_privatekey(crypto.FILETYPE_PEM, self.privatekey, **extras)) os.close(privatekey_file) except IOError as exc: self.remove() @@ -181,12 +202,11 @@ class PrivateKey(object): else: self.changed = False - self.fingerprint = get_fingerprint(self.path) + self.fingerprint = get_fingerprint(self.path, self.passphrase) file_args = module.load_file_common_arguments(module.params) if module.set_fs_attributes_if_different(file_args, False): self.changed = True - def remove(self): """Remove the private key from the filesystem.""" @@ -198,7 +218,6 @@ class PrivateKey(object): else: self.changed = False - def dump(self): """Serialize the object into a dictionary.""" @@ -222,9 +241,12 @@ def main(): type=dict(default='RSA', choices=['RSA', 'DSA'], type='str'), force=dict(default=False, type='bool'), path=dict(required=True, type='path'), + passphrase=dict(type='str', no_log=True), + cipher=dict(type='str'), ), supports_check_mode = True, add_file_common_args = True, + required_together=[['cipher', 'passphrase']], ) if not pyopenssl_found: diff --git a/lib/ansible/modules/crypto/openssl_publickey.py b/lib/ansible/modules/crypto/openssl_publickey.py index 423a8304ee..fe7cfdd835 100644 --- a/lib/ansible/modules/crypto/openssl_publickey.py +++ b/lib/ansible/modules/crypto/openssl_publickey.py @@ -62,6 +62,11 @@ options: required: true description: - Path to the TLS/SSL private key from which to generate the public key. + privatekey_passphrase: + required: false + description: + - The passphrase for the privatekey. + version_added: "2.4" ''' EXAMPLES = ''' @@ -76,6 +81,13 @@ EXAMPLES = ''' privatekey_path: /etc/ssl/private/ansible.com.pem format: OpenSSH +# Generate an OpenSSL public key with a passphrase protected +# private key +- openssl_publickey: + path: /etc/ssl/public/ansible.com.pem + privatekey_path: /etc/ssl/private/ansible.com.pem + privatekey_passphrase: ansible + # Force regenerate an OpenSSL public key if it already exists - openssl_publickey: path: /etc/ssl/public/ansible.com.pem @@ -150,6 +162,7 @@ class PublicKey(object): self.name = os.path.basename(module.params['path']) self.path = module.params['path'] self.privatekey_path = module.params['privatekey_path'] + self.privatekey_passphrase = module.params['privatekey_passphrase'] self.privatekey = None self.changed = True self.fingerprint = {} @@ -164,7 +177,7 @@ class PublicKey(object): if self.format == 'OpenSSH': key = crypto_serialization.load_pem_private_key(privatekey_content, - password=None, + password=self.privatekey_passphrase, backend=default_backend()) publickey_content = key.public_key().public_bytes( crypto_serialization.Encoding.OpenSSH, @@ -190,7 +203,7 @@ class PublicKey(object): self.changed = False file_args = module.load_file_common_arguments(module.params) - self.fingerprint = get_fingerprint(self.privatekey_path) + self.fingerprint = get_fingerprint(self.privatekey_path, self.privatekey_passphrase) if module.set_fs_attributes_if_different(file_args, False): self.changed = True @@ -228,6 +241,7 @@ def main(): path=dict(required=True, type='path'), privatekey_path=dict(type='path'), format=dict(type='str', choices=['PEM', 'OpenSSH'], default='PEM'), + privatekey_passphrase=dict(type='path', no_log=True), ), supports_check_mode = True, add_file_common_args = True,