diff options
Diffstat (limited to 'lib/ansible/modules/crypto/entrust/ecs_certificate.py')
-rw-r--r-- | lib/ansible/modules/crypto/entrust/ecs_certificate.py | 952 |
1 files changed, 0 insertions, 952 deletions
diff --git a/lib/ansible/modules/crypto/entrust/ecs_certificate.py b/lib/ansible/modules/crypto/entrust/ecs_certificate.py deleted file mode 100644 index dd1330653e..0000000000 --- a/lib/ansible/modules/crypto/entrust/ecs_certificate.py +++ /dev/null @@ -1,952 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright (c), Entrust Datacard Corporation, 2019 -# 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: ecs_certificate -author: - - Chris Trufan (@ctrufan) -version_added: '2.9' -short_description: Request SSL/TLS certificates with the Entrust Certificate Services (ECS) API -description: - - Create, reissue, and renew certificates with the Entrust Certificate Services (ECS) API. - - Requires credentials for the L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API. - - In order to request a certificate, the domain and organization used in the certificate signing request must be already - validated in the ECS system. It is I(not) the responsibility of this module to perform those steps. -notes: - - C(path) must be specified as the output location of the certificate. -requirements: - - cryptography >= 1.6 -options: - backup: - description: - - Whether a backup should be made for the certificate in I(path). - type: bool - default: false - force: - description: - - If force is used, a certificate is requested regardless of whether I(path) points to an existing valid certificate. - - If C(request_type=renew), a forced renew will fail if the certificate being renewed has been issued within the past 30 days, regardless of the - value of I(remaining_days) or the return value of I(cert_days) - the ECS API does not support the "renew" operation for certificates that are not - at least 30 days old. - type: bool - default: false - path: - description: - - The destination path for the generated certificate as a PEM encoded cert. - - If the certificate at this location is not an Entrust issued certificate, a new certificate will always be requested even if the current - certificate is technically valid. - - If there is already an Entrust certificate at this location, whether it is replaced is depends on the I(remaining_days) calculation. - - If an existing certificate is being replaced (see I(remaining_days), I(force), and I(tracking_id)), whether a new certificate is requested - or the existing certificate is renewed or reissued is based on I(request_type). - type: path - required: true - full_chain_path: - description: - - The destination path for the full certificate chain of the certificate, intermediates, and roots. - type: path - csr: - description: - - Base-64 encoded Certificate Signing Request (CSR). I(csr) is accepted with or without PEM formatting around the Base-64 string. - - If no I(csr) is provided when C(request_type=reissue) or C(request_type=renew), the certificate will be generated with the same public key as - the certificate being renewed or reissued. - - If I(subject_alt_name) is specified, it will override the subject alternate names in the CSR. - - If I(eku) is specified, it will override the extended key usage in the CSR. - - If I(ou) is specified, it will override the organizational units "ou=" present in the subject distinguished name of the CSR, if any. - - The organization "O=" field from the CSR will not be used. It will be replaced in the issued certificate by I(org) if present, and if not present, - the organization tied to I(client_id). - type: str - tracking_id: - description: - - The tracking ID of the certificate to reissue or renew. - - I(tracking_id) is invalid if C(request_type=new) or C(request_type=validate_only). - - If there is a certificate present in I(path) and it is an ECS certificate, I(tracking_id) will be ignored. - - If there is no certificate present in I(path) or there is but it is from another provider, the certificate represented by I(tracking_id) will - be renewed or reissued and saved to I(path). - - If there is no certificate present in I(path) and the I(force) and I(remaining_days) parameters do not indicate a new certificate is needed, - the certificate referenced by I(tracking_id) certificate will be saved to I(path). - - This can be used when a known certificate is not currently present on a server, but you want to renew or reissue it to be managed by an ansible - playbook. For example, if you specify C(request_type=renew), I(tracking_id) of an issued certificate, and I(path) to a file that does not exist, - the first run of a task will download the certificate specified by I(tracking_id) (assuming it is still valid). Future runs of the task will - (if applicable - see I(force) and I(remaining_days)) renew the certificate now present in I(path). - type: int - remaining_days: - description: - - The number of days the certificate must have left being valid. If C(cert_days < remaining_days) then a new certificate will be - obtained using I(request_type). - - If C(request_type=renew), a renewal will fail if the certificate being renewed has been issued within the past 30 days, so do not set a - I(remaining_days) value that is within 30 days of the full lifetime of the certificate being acted upon. (e.g. if you are requesting Certificates - with a 90 day lifetime, do not set remaining_days to a value C(60) or higher). - - The I(force) option may be used to ensure that a new certificate is always obtained. - type: int - default: 30 - request_type: - description: - - The operation performed if I(tracking_id) references a valid certificate to reissue, or there is already a certificate present in I(path) but - either I(force) is specified or C(cert_days < remaining_days). - - Specifying C(request_type=validate_only) means the request will be validated against the ECS API, but no certificate will be issued. - - Specifying C(request_type=new) means a certificate request will always be submitted and a new certificate issued. - - Specifying C(request_type=renew) means that an existing certificate (specified by I(tracking_id) if present, otherwise I(path)) will be renewed. - If there is no certificate to renew, a new certificate is requested. - - Specifying C(request_type=reissue) means that an existing certificate (specified by I(tracking_id) if present, otherwise I(path)) will be - reissued. - If there is no certificate to reissue, a new certificate is requested. - - If a certificate was issued within the past 30 days, the 'renew' operation is not a valid operation and will fail. - - Note that C(reissue) is an operation that will result in the revocation of the certificate that is reissued, be cautious with it's use. - - I(check_mode) is only supported if C(request_type=new) - - For example, setting C(request_type=renew) and C(remaining_days=30) and pointing to the same certificate on multiple playbook runs means that on - the first run new certificate will be requested. It will then be left along on future runs until it is within 30 days of expiry, then the - ECS "renew" operation will be performed. - type: str - choices: [ 'new', 'renew', 'reissue', 'validate_only'] - default: new - cert_type: - description: - - Specify the type of certificate requested. - - If a certificate is being reissued or renewed, this parameter is ignored, and the C(cert_type) of the initial certificate is used. - type: str - choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CODE_SIGNING', 'EV_CODE_SIGNING', - 'CDS_INDIVIDUAL', 'CDS_GROUP', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ] - subject_alt_name: - description: - - The subject alternative name identifiers, as an array of values (applies to I(cert_type) with a value of C(STANDARD_SSL), C(ADVANTAGE_SSL), - C(UC_SSL), C(EV_SSL), C(WILDCARD_SSL), C(PRIVATE_SSL), and C(PD_SSL)). - - If you are requesting a new SSL certificate, and you pass a I(subject_alt_name) parameter, any SAN names in the CSR are ignored. - If no subjectAltName parameter is passed, the SAN names in the CSR are used. - - See I(request_type) to understand more about SANs during reissues and renewals. - - In the case of certificates of type C(STANDARD_SSL) certificates, if the CN of the certificate is <domain>.<tld> only the www.<domain>.<tld> value - is accepted. If the CN of the certificate is www.<domain>.<tld> only the <domain>.<tld> value is accepted. - type: list - elements: str - eku: - description: - - If specified, overrides the key usage in the I(csr). - type: str - choices: [ SERVER_AUTH, CLIENT_AUTH, SERVER_AND_CLIENT_AUTH ] - ct_log: - description: - - In compliance with browser requirements, this certificate may be posted to the Certificate Transparency (CT) logs. This is a best practice - technique that helps domain owners monitor certificates issued to their domains. Note that not all certificates are eligible for CT logging. - - If I(ct_log) is not specified, the certificate uses the account default. - - If I(ct_log) is specified and the account settings allow it, I(ct_log) overrides the account default. - - If I(ct_log) is set to C(false), but the account settings are set to "always log", the certificate generation will fail. - type: bool - client_id: - description: - - The client ID to submit the Certificate Signing Request under. - - If no client ID is specified, the certificate will be submitted under the primary client with ID of 1. - - When using a client other than the primary client, the I(org) parameter cannot be specified. - - The issued certificate will have an organization value in the subject distinguished name represented by the client. - type: int - default: 1 - org: - description: - - Organization "O=" to include in the certificate. - - If I(org) is not specified, the organization from the client represented by I(client_id) is used. - - Unless the I(cert_type) is C(PD_SSL), this field may not be specified if the value of I(client_id) is not "1" (the primary client). - non-primary clients, certificates may only be issued with the organization of that client. - type: str - ou: - description: - - Organizational unit "OU=" to include in the certificate. - - I(ou) behavior is dependent on whether organizational units are enabled for your account. If organizational unit support is disabled for your - account, organizational units from the I(csr) and the I(ou) parameter are ignored. - - If both I(csr) and I(ou) are specified, the value in I(ou) will override the OU fields present in the subject distinguished name in the I(csr) - - If neither I(csr) nor I(ou) are specified for a renew or reissue operation, the OU fields in the initial certificate are reused. - - An invalid OU from I(csr) is ignored, but any invalid organizational units in I(ou) will result in an error indicating "Unapproved OU". The I(ou) - parameter can be used to force failure if an unapproved organizational unit is provided. - - A maximum of one OU may be specified for current products. Multiple OUs are reserved for future products. - type: list - elements: str - end_user_key_storage_agreement: - description: - - The end user of the Code Signing certificate must generate and store the private key for this request on cryptographically secure - hardware to be compliant with the Entrust CSP and Subscription agreement. If requesting a certificate of type C(CODE_SIGNING) or - C(EV_CODE_SIGNING), you must set I(end_user_key_storage_agreement) to true if and only if you acknowledge that you will inform the user of this - requirement. - - Applicable only to I(cert_type) of values C(CODE_SIGNING) and C(EV_CODE_SIGNING). - type: bool - tracking_info: - description: Free form tracking information to attach to the record for the certificate. - type: str - requester_name: - description: The requester name to associate with certificate tracking information. - type: str - required: true - requester_email: - description: The requester email to associate with certificate tracking information and receive delivery and expiry notices for the certificate. - type: str - required: true - requester_phone: - description: The requester phone number to associate with certificate tracking information. - type: str - required: true - additional_emails: - description: A list of additional email addresses to receive the delivery notice and expiry notification for the certificate. - type: list - elements: str - custom_fields: - description: - - Mapping of custom fields to associate with the certificate request and certificate. - - Only supported if custom fields are enabled for your account. - - Each custom field specified must be a custom field you have defined for your account. - type: dict - suboptions: - text1: - description: Custom text field (maximum 500 characters) - type: str - text2: - description: Custom text field (maximum 500 characters) - type: str - text3: - description: Custom text field (maximum 500 characters) - type: str - text4: - description: Custom text field (maximum 500 characters) - type: str - text5: - description: Custom text field (maximum 500 characters) - type: str - text6: - description: Custom text field (maximum 500 characters) - type: str - text7: - description: Custom text field (maximum 500 characters) - type: str - text8: - description: Custom text field (maximum 500 characters) - type: str - text9: - description: Custom text field (maximum 500 characters) - type: str - text10: - description: Custom text field (maximum 500 characters) - type: str - text11: - description: Custom text field (maximum 500 characters) - type: str - text12: - description: Custom text field (maximum 500 characters) - type: str - text13: - description: Custom text field (maximum 500 characters) - type: str - text14: - description: Custom text field (maximum 500 characters) - type: str - text15: - description: Custom text field (maximum 500 characters) - type: str - number1: - description: Custom number field. - type: float - number2: - description: Custom number field. - type: float - number3: - description: Custom number field. - type: float - number4: - description: Custom number field. - type: float - number5: - description: Custom number field. - type: float - date1: - description: Custom date field. - type: str - date2: - description: Custom date field. - type: str - date3: - description: Custom date field. - type: str - date4: - description: Custom date field. - type: str - date5: - description: Custom date field. - type: str - email1: - description: Custom email field. - type: str - email2: - description: Custom email field. - type: str - email3: - description: Custom email field. - type: str - email4: - description: Custom email field. - type: str - email5: - description: Custom email field. - type: str - dropdown1: - description: Custom dropdown field. - type: str - dropdown2: - description: Custom dropdown field. - type: str - dropdown3: - description: Custom dropdown field. - type: str - dropdown4: - description: Custom dropdown field. - type: str - dropdown5: - description: Custom dropdown field. - type: str - cert_expiry: - description: - - The date the certificate should be set to expire, in RFC3339 compliant date or date-time format. For example, - C(2020-02-23), C(2020-02-23T15:00:00.05Z). - - I(cert_expiry) is only supported for requests of C(request_type=new) or C(request_type=renew). If C(request_type=reissue), - I(cert_expiry) will be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial - certificate. - - A reissued certificate will always have the same expiry as the original certificate. - - Note that only the date (day, month, year) is supported for specifying the expiry date. If you choose to specify an expiry time with the expiry - date, the time will be adjusted to Eastern Standard Time (EST). This could have the unintended effect of moving your expiry date to the previous - day. - - Applies only to accounts with a pooling inventory model. - - Only one of I(cert_expiry) or I(cert_lifetime) may be specified. - type: str - cert_lifetime: - description: - - The lifetime of the certificate. - - Applies to all certificates for accounts with a non-pooling inventory model. - - I(cert_lifetime) is only supported for requests of C(request_type=new) or C(request_type=renew). If C(request_type=reissue), I(cert_lifetime) will - be used for the first certificate issuance, but subsequent issuances will have the same expiry as the initial certificate. - - Applies to certificates of I(cert_type)=C(CDS_INDIVIDUAL, CDS_GROUP, CDS_ENT_LITE, CDS_ENT_PRO, SMIME_ENT) for accounts with a pooling inventory - model. - - C(P1Y) is a certificate with a 1 year lifetime. - - C(P2Y) is a certificate with a 2 year lifetime. - - C(P3Y) is a certificate with a 3 year lifetime. - - Only one of I(cert_expiry) or I(cert_lifetime) may be specified. - type: str - choices: [ P1Y, P2Y, P3Y ] -seealso: - - module: openssl_privatekey - description: Can be used to create private keys (both for certificates and accounts). - - module: openssl_csr - description: Can be used to create a Certificate Signing Request (CSR). -extends_documentation_fragment: - - ecs_credential -''' - -EXAMPLES = r''' -- name: Request a new certificate from Entrust with bare minimum parameters. - Will request a new certificate if current one is valid but within 30 - days of expiry. If replacing an existing file in path, will back it up. - ecs_certificate: - backup: true - path: /etc/ssl/crt/ansible.com.crt - full_chain_path: /etc/ssl/crt/ansible.com.chain.crt - csr: /etc/ssl/csr/ansible.com.csr - cert_type: EV_SSL - requester_name: Jo Doe - requester_email: jdoe@ansible.com - requester_phone: 555-555-5555 - entrust_api_user: apiusername - entrust_api_key: a^lv*32!cd9LnT - entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt - entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key - -- name: If there is no certificate present in path, request a new certificate - of type EV_SSL. Otherwise, if there is an Entrust managed certificate - in path and it is within 63 days of expiration, request a renew of that - certificate. - ecs_certificate: - path: /etc/ssl/crt/ansible.com.crt - csr: /etc/ssl/csr/ansible.com.csr - cert_type: EV_SSL - cert_expiry: '2020-08-20' - request_type: renew - remaining_days: 63 - requester_name: Jo Doe - requester_email: jdoe@ansible.com - requester_phone: 555-555-5555 - entrust_api_user: apiusername - entrust_api_key: a^lv*32!cd9LnT - entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt - entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key - -- name: If there is no certificate present in path, download certificate - specified by tracking_id if it is still valid. Otherwise, if the - certificate is within 79 days of expiration, request a renew of that - certificate and save it in path. This can be used to "migrate" a - certificate to be Ansible managed. - ecs_certificate: - path: /etc/ssl/crt/ansible.com.crt - csr: /etc/ssl/csr/ansible.com.csr - tracking_id: 2378915 - request_type: renew - remaining_days: 79 - entrust_api_user: apiusername - entrust_api_key: a^lv*32!cd9LnT - entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt - entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key - -- name: Force a reissue of the certificate specified by tracking_id. - ecs_certificate: - path: /etc/ssl/crt/ansible.com.crt - force: true - tracking_id: 2378915 - request_type: reissue - entrust_api_user: apiusername - entrust_api_key: a^lv*32!cd9LnT - entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt - entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key - -- name: Request a new certificate with an alternative client. Note that the - issued certificate will have it's Subject Distinguished Name use the - organization details associated with that client, rather than what is - in the CSR. - ecs_certificate: - path: /etc/ssl/crt/ansible.com.crt - csr: /etc/ssl/csr/ansible.com.csr - client_id: 2 - requester_name: Jo Doe - requester_email: jdoe@ansible.com - requester_phone: 555-555-5555 - entrust_api_user: apiusername - entrust_api_key: a^lv*32!cd9LnT - entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt - entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key - -- name: Request a new certificate with a number of CSR parameters overridden - and tracking information - ecs_certificate: - path: /etc/ssl/crt/ansible.com.crt - full_chain_path: /etc/ssl/crt/ansible.com.chain.crt - csr: /etc/ssl/csr/ansible.com.csr - subject_alt_name: - - ansible.testcertificates.com - - www.testcertificates.com - eku: SERVER_AND_CLIENT_AUTH - ct_log: true - org: Test Organization Inc. - ou: - - Administration - tracking_info: "Submitted via Ansible" - additional_emails: - - itsupport@testcertificates.com - - jsmith@ansible.com - custom_fields: - text1: Admin - text2: Invoice 25 - number1: 342 - date1: '2018-01-01' - email1: sales@ansible.testcertificates.com - dropdown1: red - cert_expiry: '2020-08-15' - requester_name: Jo Doe - requester_email: jdoe@ansible.com - requester_phone: 555-555-5555 - entrust_api_user: apiusername - entrust_api_key: a^lv*32!cd9LnT - entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt - entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key - -''' - -RETURN = ''' -filename: - description: The destination path for the generated certificate. - returned: changed or success - type: str - sample: /etc/ssl/crt/www.ansible.com.crt -backup_file: - description: Name of backup file created for the certificate. - returned: changed and if I(backup) is C(true) - type: str - sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~ -backup_full_chain_file: - description: Name of the backup file created for the certificate chain. - returned: changed and if I(backup) is C(true) and I(full_chain_path) is set. - type: str - sample: /path/to/ca.chain.crt.2019-03-09@11:22~ -tracking_id: - description: The tracking ID to reference and track the certificate in ECS. - returned: success - type: int - sample: 380079 -serial_number: - description: The serial number of the issued certificate. - returned: success - type: int - sample: 1235262234164342 -cert_days: - description: The number of days the certificate remains valid. - returned: success - type: int - sample: 253 -cert_status: - description: - - The certificate status in ECS. - - 'Current possible values (which may be expanded in the future) are: C(ACTIVE), C(APPROVED), C(DEACTIVATED), C(DECLINED), C(EXPIRED), C(NA), - C(PENDING), C(PENDING_QUORUM), C(READY), C(REISSUED), C(REISSUING), C(RENEWED), C(RENEWING), C(REVOKED), C(SUSPENDED)' - returned: success - type: str - sample: ACTIVE -cert_details: - description: - - The full response JSON from the Get Certificate call of the ECS API. - - 'While the response contents are guaranteed to be forwards compatible with new ECS API releases, Entrust recommends that you do not make any - playbooks take actions based on the content of this field. However it may be useful for debugging, logging, or auditing purposes.' - returned: success - type: dict - -''' - -from ansible.module_utils.ecs.api import ( - ecs_client_argument_spec, - ECSClient, - RestOperationException, - SessionConfigurationException, -) - -import datetime -import json -import os -import re -import time -import traceback -from distutils.version import LooseVersion - -from ansible.module_utils import crypto as crypto_utils -from ansible.module_utils.basic import AnsibleModule, missing_required_lib -from ansible.module_utils._text import to_native, to_bytes - -CRYPTOGRAPHY_IMP_ERR = None -try: - import cryptography - CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) -except ImportError: - CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() - CRYPTOGRAPHY_FOUND = False -else: - CRYPTOGRAPHY_FOUND = True - -MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' - - -def validate_cert_expiry(cert_expiry): - search_string_partial = re.compile(r'^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\Z') - search_string_full = re.compile(r'^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):' - r'([0-5][0-9]|60)(.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))\Z') - if search_string_partial.match(cert_expiry) or search_string_full.match(cert_expiry): - return True - return False - - -def calculate_cert_days(expires_after): - cert_days = 0 - if expires_after: - expires_after_datetime = datetime.datetime.strptime(expires_after, '%Y-%m-%dT%H:%M:%SZ') - cert_days = (expires_after_datetime - datetime.datetime.now()).days - return cert_days - - -# Populate the value of body[dict_param_name] with the JSON equivalent of -# module parameter of param_name if that parameter is present, otherwise leave field -# out of resulting dict -def convert_module_param_to_json_bool(module, dict_param_name, param_name): - body = {} - if module.params[param_name] is not None: - if module.params[param_name]: - body[dict_param_name] = 'true' - else: - body[dict_param_name] = 'false' - return body - - -class EcsCertificate(object): - ''' - Entrust Certificate Services certificate class. - ''' - - def __init__(self, module): - self.path = module.params['path'] - self.full_chain_path = module.params['full_chain_path'] - self.force = module.params['force'] - self.backup = module.params['backup'] - self.request_type = module.params['request_type'] - self.csr = module.params['csr'] - - # All return values - self.changed = False - self.filename = None - self.tracking_id = None - self.cert_status = None - self.serial_number = None - self.cert_days = None - self.cert_details = None - self.backup_file = None - self.backup_full_chain_file = None - - self.cert = None - self.ecs_client = None - if self.path and os.path.exists(self.path): - try: - self.cert = crypto_utils.load_certificate(self.path, backend='cryptography') - except Exception as dummy: - self.cert = None - # Instantiate the ECS client and then try a no-op connection to verify credentials are valid - try: - self.ecs_client = ECSClient( - entrust_api_user=module.params['entrust_api_user'], - entrust_api_key=module.params['entrust_api_key'], - entrust_api_cert=module.params['entrust_api_client_cert_path'], - entrust_api_cert_key=module.params['entrust_api_client_cert_key_path'], - entrust_api_specification_path=module.params['entrust_api_specification_path'] - ) - except SessionConfigurationException as e: - module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e))) - try: - self.ecs_client.GetAppVersion() - except RestOperationException as e: - module.fail_json(msg='Please verify credential information. Received exception when testing ECS connection: {0}'.format(to_native(e.message))) - - # Conversion of the fields that go into the 'tracking' parameter of the request object - def convert_tracking_params(self, module): - body = {} - tracking = {} - if module.params['requester_name']: - tracking['requesterName'] = module.params['requester_name'] - if module.params['requester_email']: - tracking['requesterEmail'] = module.params['requester_email'] - if module.params['requester_phone']: - tracking['requesterPhone'] = module.params['requester_phone'] - if module.params['tracking_info']: - tracking['trackingInfo'] = module.params['tracking_info'] - if module.params['custom_fields']: - # Omit custom fields from submitted dict if not present, instead of submitting them with value of 'null' - # The ECS API does technically accept null without error, but it complicates debugging user escalations and is unnecessary bandwidth. - custom_fields = {} - for k, v in module.params['custom_fields'].items(): - if v is not None: - custom_fields[k] = v - tracking['customFields'] = custom_fields - if module.params['additional_emails']: - tracking['additionalEmails'] = module.params['additional_emails'] - body['tracking'] = tracking - return body - - def convert_cert_subject_params(self, module): - body = {} - if module.params['subject_alt_name']: - body['subjectAltName'] = module.params['subject_alt_name'] - if module.params['org']: - body['org'] = module.params['org'] - if module.params['ou']: - body['ou'] = module.params['ou'] - return body - - def convert_general_params(self, module): - body = {} - if module.params['eku']: - body['eku'] = module.params['eku'] - if self.request_type == 'new': - body['certType'] = module.params['cert_type'] - body['clientId'] = module.params['client_id'] - body.update(convert_module_param_to_json_bool(module, 'ctLog', 'ct_log')) - body.update(convert_module_param_to_json_bool(module, 'endUserKeyStorageAgreement', 'end_user_key_storage_agreement')) - return body - - def convert_expiry_params(self, module): - body = {} - if module.params['cert_lifetime']: - body['certLifetime'] = module.params['cert_lifetime'] - elif module.params['cert_expiry']: - body['certExpiryDate'] = module.params['cert_expiry'] - # If neither cerTLifetime or certExpiryDate was specified and the request type is new, default to 365 days - elif self.request_type != 'reissue': - gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())) - expiry = gmt_now + datetime.timedelta(days=365) - body['certExpiryDate'] = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") - return body - - def set_tracking_id_by_serial_number(self, module): - try: - # Use serial_number to identify if certificate is an Entrust Certificate - # with an associated tracking ID - serial_number = "{0:X}".format(self.cert.serial_number) - cert_results = self.ecs_client.GetCertificates(serialNumber=serial_number).get('certificates', {}) - if len(cert_results) == 1: - self.tracking_id = cert_results[0].get('trackingId') - except RestOperationException as dummy: - # If we fail to find a cert by serial number, that's fine, we just don't set self.tracking_id - return - - def set_cert_details(self, module): - try: - self.cert_details = self.ecs_client.GetCertificate(trackingId=self.tracking_id) - self.cert_status = self.cert_details.get('status') - self.serial_number = self.cert_details.get('serialNumber') - self.cert_days = calculate_cert_days(self.cert_details.get('expiresAfter')) - except RestOperationException as e: - module.fail_json('Failed to get details of certificate with tracking_id="{0}", Error: '.format(self.tracking_id), to_native(e.message)) - - def check(self, module): - if self.cert: - # We will only treat a certificate as valid if it is found as a managed entrust cert. - # We will only set updated tracking ID based on certificate in "path" if it is managed by entrust. - self.set_tracking_id_by_serial_number(module) - - if module.params['tracking_id'] and self.tracking_id and module.params['tracking_id'] != self.tracking_id: - module.warn('tracking_id parameter of "{0}" provided, but will be ignored. Valid certificate was present in path "{1}" with ' - 'tracking_id of "{2}".'.format(module.params['tracking_id'], self.path, self.tracking_id)) - - # If we did not end up setting tracking_id based on existing cert, get from module params - if not self.tracking_id: - self.tracking_id = module.params['tracking_id'] - - if not self.tracking_id: - return False - - self.set_cert_details(module) - - if self.cert_status == 'EXPIRED' or self.cert_status == 'SUSPENDED' or self.cert_status == 'REVOKED': - return False - if self.cert_days < module.params['remaining_days']: - return False - - return True - - def request_cert(self, module): - if not self.check(module) or self.force: - body = {} - - # Read the CSR contents - if self.csr and os.path.exists(self.csr): - with open(self.csr, 'r') as csr_file: - body['csr'] = csr_file.read() - - # Check if the path is already a cert - # tracking_id may be set as a parameter or by get_cert_details if an entrust cert is in 'path'. If tracking ID is null - # We will be performing a reissue operation. - if self.request_type != 'new' and not self.tracking_id: - module.warn('No existing Entrust certificate found in path={0} and no tracking_id was provided, setting request_type to "new" for this task' - 'run. Future playbook runs that point to the pathination file in {1} will use request_type={2}' - .format(self.path, self.path, self.request_type)) - self.request_type = 'new' - elif self.request_type == 'new' and self.tracking_id: - module.warn('Existing certificate being acted upon, but request_type is "new", so will be a new certificate issuance rather than a' - 'reissue or renew') - # Use cases where request type is new and no existing certificate, or where request type is reissue/renew and a valid - # existing certificate is found, do not need warnings. - - body.update(self.convert_tracking_params(module)) - body.update(self.convert_cert_subject_params(module)) - body.update(self.convert_general_params(module)) - body.update(self.convert_expiry_params(module)) - - if not module.check_mode: - try: - if self.request_type == 'validate_only': - body['validateOnly'] = 'true' - result = self.ecs_client.NewCertRequest(Body=body) - if self.request_type == 'new': - result = self.ecs_client.NewCertRequest(Body=body) - elif self.request_type == 'renew': - result = self.ecs_client.RenewCertRequest(trackingId=self.tracking_id, Body=body) - elif self.request_type == 'reissue': - result = self.ecs_client.ReissueCertRequest(trackingId=self.tracking_id, Body=body) - self.tracking_id = result.get('trackingId') - self.set_cert_details(module) - except RestOperationException as e: - module.fail_json(msg='Failed to request new certificate from Entrust (ECS) {0}'.format(e.message)) - - if self.request_type != 'validate_only': - if self.backup: - self.backup_file = module.backup_local(self.path) - crypto_utils.write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) - if self.full_chain_path and self.cert_details.get('chainCerts'): - if self.backup: - self.backup_full_chain_file = module.backup_local(self.full_chain_path) - chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n' - crypto_utils.write_file(module, to_bytes(chain_string), path=self.full_chain_path) - self.changed = True - # If there is no certificate present in path but a tracking ID was specified, save it to disk - elif not os.path.exists(self.path) and self.tracking_id: - if not module.check_mode: - crypto_utils.write_file(module, to_bytes(self.cert_details.get('endEntityCert'))) - if self.full_chain_path and self.cert_details.get('chainCerts'): - chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n' - crypto_utils.write_file(module, to_bytes(chain_string), path=self.full_chain_path) - self.changed = True - - def dump(self): - result = { - 'changed': self.changed, - 'filename': self.path, - 'tracking_id': self.tracking_id, - 'cert_status': self.cert_status, - 'serial_number': self.serial_number, - 'cert_days': self.cert_days, - 'cert_details': self.cert_details, - } - if self.backup_file: - result['backup_file'] = self.backup_file - result['backup_full_chain_file'] = self.backup_full_chain_file - return result - - -def custom_fields_spec(): - return dict( - text1=dict(type='str'), - text2=dict(type='str'), - text3=dict(type='str'), - text4=dict(type='str'), - text5=dict(type='str'), - text6=dict(type='str'), - text7=dict(type='str'), - text8=dict(type='str'), - text9=dict(type='str'), - text10=dict(type='str'), - text11=dict(type='str'), - text12=dict(type='str'), - text13=dict(type='str'), - text14=dict(type='str'), - text15=dict(type='str'), - number1=dict(type='float'), - number2=dict(type='float'), - number3=dict(type='float'), - number4=dict(type='float'), - number5=dict(type='float'), - date1=dict(type='str'), - date2=dict(type='str'), - date3=dict(type='str'), - date4=dict(type='str'), - date5=dict(type='str'), - email1=dict(type='str'), - email2=dict(type='str'), - email3=dict(type='str'), - email4=dict(type='str'), - email5=dict(type='str'), - dropdown1=dict(type='str'), - dropdown2=dict(type='str'), - dropdown3=dict(type='str'), - dropdown4=dict(type='str'), - dropdown5=dict(type='str'), - ) - - -def ecs_certificate_argument_spec(): - return dict( - backup=dict(type='bool', default=False), - force=dict(type='bool', default=False), - path=dict(type='path', required=True), - full_chain_path=dict(type='path'), - tracking_id=dict(type='int'), - remaining_days=dict(type='int', default=30), - request_type=dict(type='str', default='new', choices=['new', 'renew', 'reissue', 'validate_only']), - cert_type=dict(type='str', choices=['STANDARD_SSL', - 'ADVANTAGE_SSL', - 'UC_SSL', - 'EV_SSL', - 'WILDCARD_SSL', - 'PRIVATE_SSL', - 'PD_SSL', - 'CODE_SIGNING', - 'EV_CODE_SIGNING', - 'CDS_INDIVIDUAL', - 'CDS_GROUP', - 'CDS_ENT_LITE', - 'CDS_ENT_PRO', - 'SMIME_ENT', - ]), - csr=dict(type='str'), - subject_alt_name=dict(type='list', elements='str'), - eku=dict(type='str', choices=['SERVER_AUTH', 'CLIENT_AUTH', 'SERVER_AND_CLIENT_AUTH']), - ct_log=dict(type='bool'), - client_id=dict(type='int', default=1), - org=dict(type='str'), - ou=dict(type='list', elements='str'), - end_user_key_storage_agreement=dict(type='bool'), - tracking_info=dict(type='str'), - requester_name=dict(type='str', required=True), - requester_email=dict(type='str', required=True), - requester_phone=dict(type='str', required=True), - additional_emails=dict(type='list', elements='str'), - custom_fields=dict(type='dict', default=None, options=custom_fields_spec()), - cert_expiry=dict(type='str'), - cert_lifetime=dict(type='str', choices=['P1Y', 'P2Y', 'P3Y']), - ) - - -def main(): - ecs_argument_spec = ecs_client_argument_spec() - ecs_argument_spec.update(ecs_certificate_argument_spec()) - module = AnsibleModule( - argument_spec=ecs_argument_spec, - required_if=( - ['request_type', 'new', ['cert_type']], - ['request_type', 'validate_only', ['cert_type']], - ['cert_type', 'CODE_SIGNING', ['end_user_key_storage_agreement']], - ['cert_type', 'EV_CODE_SIGNING', ['end_user_key_storage_agreement']], - ), - mutually_exclusive=( - ['cert_expiry', 'cert_lifetime'], - ), - supports_check_mode=True, - ) - - if not CRYPTOGRAPHY_FOUND or CRYPTOGRAPHY_VERSION < LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION): - module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), - exception=CRYPTOGRAPHY_IMP_ERR) - - # If validate_only is used, pointing to an existing tracking_id is an invalid operation - if module.params['tracking_id']: - if module.params['request_type'] == 'new' or module.params['request_type'] == 'validate_only': - module.fail_json(msg='The tracking_id field is invalid when request_type="{0}".'.format(module.params['request_type'])) - - # A reissued request can not specify an expiration date or lifetime - if module.params['request_type'] == 'reissue': - if module.params['cert_expiry']: - module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".') - elif module.params['cert_lifetime']: - module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".') - # Only a reissued request can omit the CSR - else: - module_params_csr = module.params['csr'] - if module_params_csr is None: - module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type'])) - elif not os.path.exists(module_params_csr): - module.fail_json(msg='The csr field of {0} was not a valid path. csr is required when request_type={1}'.format( - module_params_csr, module.params['request_type'])) - - if module.params['ou'] and len(module.params['ou']) > 1: - module.fail_json(msg='Multiple "ou" values are not currently supported.') - - if module.params['end_user_key_storage_agreement']: - if module.params['cert_type'] != 'CODE_SIGNING' and module.params['cert_type'] != 'EV_CODE_SIGNING': - module.fail_json(msg='Parameter "end_user_key_storage_agreement" is valid only for cert_types "CODE_SIGNING" and "EV_CODE_SIGNING"') - - if module.params['org'] and module.params['client_id'] != 1 and module.params['cert_type'] != 'PD_SSL': - module.fail_json(msg='The "org" parameter is not supported when client_id parameter is set to a value other than 1, unless cert_type is "PD_SSL".') - - if module.params['cert_expiry']: - if not validate_cert_expiry(module.params['cert_expiry']): - module.fail_json(msg='The "cert_expiry" parameter of "{0}" is not a valid date or date-time'.format(module.params['cert_expiry'])) - - certificate = EcsCertificate(module) - certificate.request_cert(module) - result = certificate.dump() - module.exit_json(**result) - - -if __name__ == '__main__': - main() |