2018-12-07 13:27:16 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# coding: utf-8 -*-
|
|
|
|
|
|
|
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
|
|
|
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
|
|
'status': ['preview'],
|
|
|
|
'supported_by': 'community'}
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: get_certificate
|
|
|
|
author: "John Westcott IV (@john-westcott-iv)"
|
|
|
|
version_added: "2.8"
|
|
|
|
short_description: Get a certificate from a host:port
|
|
|
|
description:
|
|
|
|
- Makes a secure connection and returns information about the presented certificate
|
|
|
|
options:
|
|
|
|
host:
|
|
|
|
description:
|
|
|
|
- The host to get the cert for (IP is fine)
|
2019-02-18 10:24:17 +00:00
|
|
|
type: str
|
|
|
|
required: true
|
2019-03-28 05:19:28 +00:00
|
|
|
ca_cert:
|
2018-12-07 13:27:16 +00:00
|
|
|
description:
|
2019-03-28 05:19:28 +00:00
|
|
|
- A PEM file containing one or more root certificates; if present, the cert will be validated against these root certs.
|
2018-12-07 13:27:16 +00:00
|
|
|
- Note that this only validates the certificate is signed by the chain; not that the cert is valid for the host presenting it.
|
2019-02-18 10:24:17 +00:00
|
|
|
type: path
|
2018-12-07 13:27:16 +00:00
|
|
|
port:
|
|
|
|
description:
|
|
|
|
- The port to connect to
|
2019-02-18 10:24:17 +00:00
|
|
|
type: int
|
|
|
|
required: true
|
2018-12-07 13:27:16 +00:00
|
|
|
timeout:
|
|
|
|
description:
|
|
|
|
- The timeout in seconds
|
2019-02-18 10:24:17 +00:00
|
|
|
type: int
|
2018-12-07 13:27:16 +00:00
|
|
|
default: 10
|
|
|
|
|
|
|
|
notes:
|
2019-03-28 05:19:28 +00:00
|
|
|
- When using ca_cert on OS X it has been reported that in some conditions the validate will always succeed.
|
2018-12-07 13:27:16 +00:00
|
|
|
|
|
|
|
requirements:
|
|
|
|
- "python >= 2.6"
|
|
|
|
- "python-pyOpenSSL >= 0.15"
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = '''
|
|
|
|
cert:
|
|
|
|
description: The certificate retrieved from the port
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2018-12-07 13:27:16 +00:00
|
|
|
expired:
|
|
|
|
description: Boolean indicating if the cert is expired
|
|
|
|
returned: success
|
|
|
|
type: bool
|
|
|
|
extensions:
|
|
|
|
description: Extensions applied to the cert
|
|
|
|
returned: success
|
|
|
|
type: list
|
|
|
|
issuer:
|
|
|
|
description: Information about the issuer of the cert
|
|
|
|
returned: success
|
|
|
|
type: dict
|
|
|
|
not_after:
|
|
|
|
description: Expiration date of the cert
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2018-12-07 13:27:16 +00:00
|
|
|
not_before:
|
|
|
|
description: Issue date of the cert
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2018-12-07 13:27:16 +00:00
|
|
|
serial_number:
|
|
|
|
description: The serial number of the cert
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2018-12-07 13:27:16 +00:00
|
|
|
signature_algorithm:
|
|
|
|
description: The algorithm used to sign the cert
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2018-12-07 13:27:16 +00:00
|
|
|
subject:
|
|
|
|
description: Information about the subject of the cert (OU, CN, etc)
|
|
|
|
returned: success
|
|
|
|
type: dict
|
|
|
|
version:
|
|
|
|
description: The version number of the certificate
|
|
|
|
returned: success
|
2018-12-18 21:25:30 +00:00
|
|
|
type: str
|
2018-12-07 13:27:16 +00:00
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
- name: Get the cert from an RDP port
|
|
|
|
get_certificate:
|
|
|
|
host: "1.2.3.4"
|
|
|
|
port: 3389
|
|
|
|
delegate_to: localhost
|
|
|
|
run_once: true
|
|
|
|
register: cert
|
|
|
|
|
|
|
|
- name: Get a cert from an https port
|
|
|
|
get_certificate:
|
|
|
|
host: "www.google.com"
|
|
|
|
port: 443
|
|
|
|
delegate_to: localhost
|
|
|
|
run_once: true
|
|
|
|
register: cert
|
|
|
|
'''
|
|
|
|
|
2019-02-06 17:39:17 +00:00
|
|
|
import traceback
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
2018-12-07 13:27:16 +00:00
|
|
|
|
|
|
|
from os.path import isfile
|
|
|
|
from ssl import get_server_certificate
|
|
|
|
from socket import setdefaulttimeout
|
|
|
|
|
2019-02-06 17:39:17 +00:00
|
|
|
PYOPENSSL_IMP_ERR = None
|
2018-12-07 13:27:16 +00:00
|
|
|
try:
|
|
|
|
from OpenSSL import crypto
|
|
|
|
except ImportError:
|
2019-02-06 17:39:17 +00:00
|
|
|
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
2018-12-07 13:27:16 +00:00
|
|
|
pyopenssl_found = False
|
|
|
|
else:
|
|
|
|
pyopenssl_found = True
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
2019-03-28 14:38:18 +00:00
|
|
|
ca_cert=dict(type='path'),
|
2019-02-18 10:24:17 +00:00
|
|
|
host=dict(type='str', required=True),
|
|
|
|
port=dict(type='int', required=True),
|
|
|
|
timeout=dict(type='int', default=10),
|
2018-12-07 13:27:16 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2019-03-28 14:38:18 +00:00
|
|
|
ca_cert = module.params.get('ca_cert')
|
2018-12-07 13:27:16 +00:00
|
|
|
host = module.params.get('host')
|
|
|
|
port = module.params.get('port')
|
|
|
|
timeout = module.params.get('timeout')
|
|
|
|
|
|
|
|
result = dict(
|
|
|
|
changed=False,
|
|
|
|
)
|
|
|
|
|
|
|
|
if not pyopenssl_found:
|
2019-02-06 17:39:17 +00:00
|
|
|
module.fail_json(msg=missing_required_lib('pyOpenSSL'), exception=PYOPENSSL_IMP_ERR)
|
2018-12-07 13:27:16 +00:00
|
|
|
|
|
|
|
if timeout:
|
|
|
|
setdefaulttimeout(timeout)
|
|
|
|
|
2019-03-28 14:38:18 +00:00
|
|
|
if ca_cert:
|
|
|
|
if not isfile(ca_cert):
|
2019-03-28 05:19:28 +00:00
|
|
|
module.fail_json(msg="ca_cert file does not exist")
|
2018-12-07 13:27:16 +00:00
|
|
|
|
|
|
|
try:
|
2019-03-28 14:38:18 +00:00
|
|
|
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
2018-12-07 13:27:16 +00:00
|
|
|
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
|
|
|
except Exception as e:
|
|
|
|
module.fail_json(msg="Failed to get cert from port with error: {0}".format(e))
|
|
|
|
|
|
|
|
result['cert'] = cert
|
|
|
|
result['subject'] = {}
|
|
|
|
for component in x509.get_subject().get_components():
|
|
|
|
result['subject'][component[0]] = component[1]
|
|
|
|
|
|
|
|
result['expired'] = x509.has_expired()
|
|
|
|
|
|
|
|
result['extensions'] = []
|
|
|
|
extension_count = x509.get_extension_count()
|
|
|
|
for index in range(0, extension_count):
|
|
|
|
extension = x509.get_extension(index)
|
|
|
|
result['extensions'].append({
|
|
|
|
'critical': extension.get_critical(),
|
|
|
|
'asn1_data': extension.get_data(),
|
|
|
|
'name': extension.get_short_name(),
|
|
|
|
})
|
|
|
|
|
|
|
|
result['issuer'] = {}
|
|
|
|
for component in x509.get_issuer().get_components():
|
|
|
|
result['issuer'][component[0]] = component[1]
|
|
|
|
|
|
|
|
result['not_after'] = x509.get_notAfter()
|
|
|
|
result['not_before'] = x509.get_notBefore()
|
|
|
|
|
|
|
|
result['serial_number'] = x509.get_serial_number()
|
|
|
|
result['signature_algorithm'] = x509.get_signature_algorithm()
|
|
|
|
|
|
|
|
result['version'] = x509.get_version()
|
|
|
|
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|