mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-03-26 21:33:25 +00:00
1095 lines
48 KiB
Python
1095 lines
48 KiB
Python
#!/usr/bin/python
|
|
# Copyright (c), Entrust Datacard Corporation, 2019
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
DOCUMENTATION = r"""
|
|
module: ecs_certificate
|
|
author:
|
|
- Chris Trufan (@ctrufan)
|
|
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:
|
|
- O(path) must be specified as the output location of the certificate.
|
|
extends_documentation_fragment:
|
|
- community.crypto._attributes
|
|
- community.crypto._attributes.files
|
|
- community.crypto._cryptography_dep.minimum
|
|
- community.crypto._ecs_credential
|
|
attributes:
|
|
check_mode:
|
|
support: partial
|
|
details:
|
|
- Check mode is only supported if O(request_type=new).
|
|
diff_mode:
|
|
support: none
|
|
safe_file_operations:
|
|
support: full
|
|
idempotent:
|
|
support: partial
|
|
details:
|
|
- The module is not idempotent if O(force=true).
|
|
- Under which conditions the module is idempotent still needs to be determined.
|
|
If you are using this module and have more information, please contribute to
|
|
the documentation!
|
|
options:
|
|
backup:
|
|
description:
|
|
- Whether a backup should be made for the certificate in O(path).
|
|
type: bool
|
|
default: false
|
|
force:
|
|
description:
|
|
- If force is used, a certificate is requested regardless of whether O(path) points to an existing valid certificate.
|
|
- If O(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 O(remaining_days) or the return value of RV(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 O(remaining_days)
|
|
calculation.
|
|
- If an existing certificate is being replaced (see O(remaining_days), O(force), and O(tracking_id)), whether a new
|
|
certificate is requested or the existing certificate is renewed or reissued is based on O(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). O(csr) is accepted with or without PEM formatting around the Base-64
|
|
string.
|
|
- If no O(csr) is provided when O(request_type=reissue) or O(request_type=renew), the certificate will be generated
|
|
with the same public key as the certificate being renewed or reissued.
|
|
- If O(subject_alt_name) is specified, it will override the subject alternate names in the CSR.
|
|
- If O(eku) is specified, it will override the extended key usage in the CSR.
|
|
- If O(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 O(org)
|
|
if present, and if not present, the organization tied to O(client_id).
|
|
type: str
|
|
tracking_id:
|
|
description:
|
|
- The tracking ID of the certificate to reissue or renew.
|
|
- O(tracking_id) is invalid if O(request_type=new) or O(request_type=validate_only).
|
|
- If there is a certificate present in O(path) and it is an ECS certificate, O(tracking_id) will be ignored.
|
|
- If there is no certificate present in O(path) or there is but it is from another provider, the certificate represented
|
|
by O(tracking_id) will be renewed or reissued and saved to O(path).
|
|
- If there is no certificate present in O(path) and the O(force) and O(remaining_days) parameters do not indicate a
|
|
new certificate is needed, the certificate referenced by O(tracking_id) certificate will be saved to O(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 O(request_type=renew), O(tracking_id) of an issued
|
|
certificate, and O(path) to a file that does not exist, the first run of a task will download the certificate specified
|
|
by O(tracking_id) (assuming it is still valid). Future runs of the task will (if applicable - see O(force) and O(remaining_days))
|
|
renew the certificate now present in O(path).
|
|
type: int
|
|
remaining_days:
|
|
description:
|
|
- The number of days the certificate must have left being valid. If RV(cert_days) < O(remaining_days) then a new certificate
|
|
will be obtained using O(request_type).
|
|
- If O(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 O(remaining_days) value that is within 30 days of the full lifetime of the certificate being
|
|
acted upon.
|
|
- For example, if you are requesting Certificates with a 90 day lifetime, do not set O(remaining_days) to a value V(60)
|
|
or higher).
|
|
- The O(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 O(tracking_id) references a valid certificate to reissue, or there is already a certificate
|
|
present in O(path) but either O(force) is specified or RV(cert_days) < O(remaining_days).
|
|
- Specifying O(request_type=validate_only) means the request will be validated against the ECS API, but no certificate
|
|
will be issued.
|
|
- Specifying O(request_type=new) means a certificate request will always be submitted and a new certificate issued.
|
|
- Specifying O(request_type=renew) means that an existing certificate (specified by O(tracking_id) if present, otherwise
|
|
O(path)) will be renewed. If there is no certificate to renew, a new certificate is requested.
|
|
- Specifying O(request_type=reissue) means that an existing certificate (specified by O(tracking_id) if present, otherwise
|
|
O(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 V(renew) operation is not a valid operation and will fail.
|
|
- Note that V(reissue) is an operation that will result in the revocation of the certificate that is reissued, be cautious
|
|
with its use.
|
|
- I(check_mode) is only supported if O(request_type=new).
|
|
- For example, setting O(request_type=renew) and O(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 O(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 O(cert_type) with a value of V(STANDARD_SSL),
|
|
V(ADVANTAGE_SSL), V(UC_SSL), V(EV_SSL), V(WILDCARD_SSL), V(PRIVATE_SSL), and V(PD_SSL)).
|
|
- If you are requesting a new SSL certificate, and you pass a O(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 O(request_type) to understand more about SANs during reissues and renewals.
|
|
- In the case of certificates of type V(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 O(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 O(ct_log) is not specified, the certificate uses the account default.
|
|
- If O(ct_log) is specified and the account settings allow it, O(ct_log) overrides the account default.
|
|
- If O(ct_log) is set to V(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 O(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 O(org) is not specified, the organization from the client represented by O(client_id) is used.
|
|
- Unless the O(cert_type) is V(PD_SSL), this field may not be specified if the value of O(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.
|
|
- O(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 O(csr) and the O(ou) parameter are ignored.
|
|
- If both O(csr) and O(ou) are specified, the value in O(ou) will override the OU fields present in the subject distinguished
|
|
name in the O(csr).
|
|
- If neither O(csr) nor O(ou) are specified for a renew or reissue operation, the OU fields in the initial certificate
|
|
are reused.
|
|
- An invalid OU from O(csr) is ignored, but any invalid organizational units in O(ou) will result in an error indicating
|
|
"Unapproved OU". The O(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
|
|
V(CODE_SIGNING) or V(EV_CODE_SIGNING), you must set O(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 O(cert_type) of values V(CODE_SIGNING) and V(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, V(2020-02-23),
|
|
V(2020-02-23T15:00:00.05Z).
|
|
- O(cert_expiry) is only supported for requests of O(request_type=new) or O(request_type=renew). If O(request_type=reissue),
|
|
O(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 O(cert_expiry) or O(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.
|
|
- O(cert_lifetime) is only supported for requests of O(request_type=new) or O(request_type=renew). If O(request_type=reissue),
|
|
O(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 O(cert_type=CDS_INDIVIDUAL), V(CDS_GROUP), V(CDS_ENT_LITE), V(CDS_ENT_PRO), or V(SMIME_ENT)
|
|
for accounts with a pooling inventory model.
|
|
- V(P1Y) is a certificate with a 1 year lifetime.
|
|
- V(P2Y) is a certificate with a 2 year lifetime.
|
|
- V(P3Y) is a certificate with a 3 year lifetime.
|
|
- Only one of O(cert_expiry) or O(cert_lifetime) may be specified.
|
|
type: str
|
|
choices: [P1Y, P2Y, P3Y]
|
|
seealso:
|
|
- module: community.crypto.openssl_privatekey
|
|
description: Can be used to create private keys (both for certificates and accounts).
|
|
- module: community.crypto.openssl_csr
|
|
description: Can be used to create a Certificate Signing Request (CSR).
|
|
- plugin: community.crypto.to_serial
|
|
plugin_type: filter
|
|
"""
|
|
|
|
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.
|
|
community.crypto.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.
|
|
community.crypto.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.
|
|
community.crypto.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.
|
|
community.crypto.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 its Subject Distinguished
|
|
Name use the organization details associated with that client, rather than what is in the CSR.
|
|
community.crypto.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
|
|
community.crypto.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 = r"""
|
|
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 O(backup) is V(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 O(backup) is V(true) and O(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.
|
|
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, such as C(11:22:33),
|
|
you need to convert it to that form with P(community.crypto.to_serial#filter).
|
|
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: V(ACTIVE), V(APPROVED), V(DEACTIVATED), V(DECLINED),
|
|
V(EXPIRED), V(NA), V(PENDING), V(PENDING_QUORUM), V(READY), V(REISSUED), V(REISSUING), V(RENEWED), V(RENEWING), V(REVOKED),
|
|
V(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
|
|
"""
|
|
|
|
import datetime
|
|
import os
|
|
import re
|
|
import time
|
|
import typing as t
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.common.text.converters import to_bytes
|
|
from ansible_collections.community.crypto.plugins.module_utils._crypto.support import (
|
|
load_certificate,
|
|
)
|
|
from ansible_collections.community.crypto.plugins.module_utils._cryptography_dep import (
|
|
COLLECTION_MINIMUM_CRYPTOGRAPHY_VERSION,
|
|
assert_required_cryptography_version,
|
|
)
|
|
from ansible_collections.community.crypto.plugins.module_utils._ecs.api import (
|
|
ECSClient,
|
|
RestOperationException,
|
|
SessionConfigurationException,
|
|
ecs_client_argument_spec,
|
|
)
|
|
from ansible_collections.community.crypto.plugins.module_utils._io import write_file
|
|
|
|
|
|
MINIMAL_CRYPTOGRAPHY_VERSION = COLLECTION_MINIMUM_CRYPTOGRAPHY_VERSION
|
|
|
|
|
|
def validate_cert_expiry(cert_expiry: str) -> bool:
|
|
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: str | None) -> int:
|
|
cert_days = 0
|
|
if expires_after is not None:
|
|
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: AnsibleModule, dict_param_name: str, param_name: str
|
|
) -> dict[str, str]:
|
|
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:
|
|
"""
|
|
Entrust Certificate Services certificate class.
|
|
"""
|
|
|
|
def __init__(self, module: AnsibleModule) -> None:
|
|
self.path: str = module.params["path"]
|
|
self.full_chain_path: str | None = module.params["full_chain_path"]
|
|
self.force: bool = module.params["force"]
|
|
self.backup: bool = module.params["backup"]
|
|
self.request_type: t.Literal["new", "renew", "reissue", "validate_only"] = (
|
|
module.params["request_type"]
|
|
)
|
|
self.csr: str | None = module.params["csr"]
|
|
|
|
# All return values
|
|
self.changed = False
|
|
self.filename: str | None = None
|
|
self.tracking_id: int | None = None
|
|
self.cert_status: str | None = None
|
|
self.serial_number: int | None = None
|
|
self.cert_days: int | None = None
|
|
self.cert_details: dict[str, t.Any] | None = None
|
|
self.backup_file: str | None = None
|
|
self.backup_full_chain_file: str | None = None
|
|
|
|
self.cert = None
|
|
if self.path and os.path.exists(self.path):
|
|
try:
|
|
self.cert = load_certificate(path=self.path)
|
|
except Exception:
|
|
pass
|
|
# 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=f"Failed to initialize Entrust Provider: {e}")
|
|
try:
|
|
self.ecs_client.GetAppVersion() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
except RestOperationException as e:
|
|
module.fail_json(
|
|
msg=f"Please verify credential information. Received exception when testing ECS connection: {e.message}"
|
|
)
|
|
|
|
# Conversion of the fields that go into the 'tracking' parameter of the request object
|
|
def convert_tracking_params(self, module: AnsibleModule) -> dict[str, t.Any]:
|
|
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: AnsibleModule) -> dict[str, t.Any]:
|
|
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: AnsibleModule) -> dict[str, t.Any]:
|
|
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: AnsibleModule) -> dict[str, t.Any]:
|
|
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: AnsibleModule) -> None:
|
|
assert self.cert is not None
|
|
try:
|
|
# Use serial_number to identify if certificate is an Entrust Certificate
|
|
# with an associated tracking ID
|
|
serial_number = f"{self.cert.serial_number:X}"
|
|
cert_results = self.ecs_client.GetCertificates( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
serialNumber=serial_number
|
|
).get(
|
|
"certificates", {}
|
|
)
|
|
if len(cert_results) == 1:
|
|
self.tracking_id = cert_results[0].get("trackingId")
|
|
except RestOperationException:
|
|
# If we fail to find a cert by serial number, that's fine, we just do not set self.tracking_id
|
|
pass
|
|
|
|
def set_cert_details(self, module: AnsibleModule) -> None:
|
|
try:
|
|
self.cert_details = self.ecs_client.GetCertificate( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
trackingId=self.tracking_id
|
|
)
|
|
assert self.cert_details is not None
|
|
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(
|
|
msg=f'Failed to get details of certificate with tracking_id="{self.tracking_id}", Error: {e.message}'
|
|
)
|
|
|
|
def check(self, module: AnsibleModule) -> bool:
|
|
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(
|
|
f'tracking_id parameter of "{module.params["tracking_id"]}" provided, but will be ignored.'
|
|
f' Valid certificate was present in path "{self.path}" with '
|
|
f'tracking_id of "{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)
|
|
assert self.cert_details is not None
|
|
|
|
if self.cert_status in ("EXPIRED", "SUSPENDED", "REVOKED"):
|
|
return False
|
|
if self.cert_days < module.params["remaining_days"]:
|
|
return False
|
|
|
|
return True
|
|
|
|
def request_cert(self, module: AnsibleModule) -> None:
|
|
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", encoding="utf-8") 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(
|
|
f"No existing Entrust certificate found in path={self.path}"
|
|
' and no tracking_id was provided, setting request_type to "new" for this task'
|
|
"run. Future playbook runs that point to the pathination file"
|
|
f" in {self.path} will use request_type={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( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
Body=body
|
|
)
|
|
if self.request_type == "new":
|
|
result = self.ecs_client.NewCertRequest( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
Body=body
|
|
)
|
|
elif self.request_type == "renew":
|
|
result = self.ecs_client.RenewCertRequest( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
trackingId=self.tracking_id, Body=body
|
|
)
|
|
elif self.request_type == "reissue":
|
|
result = self.ecs_client.ReissueCertRequest( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
trackingId=self.tracking_id, Body=body
|
|
)
|
|
else:
|
|
raise AssertionError("Can never be reached") # pragma: no cover
|
|
self.tracking_id = result.get("trackingId")
|
|
self.set_cert_details(module)
|
|
assert self.cert_details is not None
|
|
except RestOperationException as e:
|
|
module.fail_json(
|
|
msg=f"Failed to request new certificate from Entrust (ECS) {e.message}"
|
|
)
|
|
|
|
if self.request_type != "validate_only":
|
|
if self.backup:
|
|
self.backup_file = module.backup_local(self.path)
|
|
write_file(
|
|
module=module,
|
|
content=to_bytes(self.cert_details.get("endEntityCert")),
|
|
)
|
|
chain_certs = self.cert_details.get("chainCerts")
|
|
if self.full_chain_path and chain_certs:
|
|
assert isinstance(chain_certs, list)
|
|
if self.backup:
|
|
self.backup_full_chain_file = module.backup_local(
|
|
self.full_chain_path
|
|
)
|
|
chain_string = "\n".join(chain_certs) + "\n"
|
|
write_file(
|
|
module=module,
|
|
content=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:
|
|
assert self.cert_details is not None
|
|
write_file(
|
|
module=module,
|
|
content=to_bytes(self.cert_details.get("endEntityCert")),
|
|
)
|
|
chain_certs = self.cert_details.get("chainCerts")
|
|
if self.full_chain_path and chain_certs:
|
|
assert isinstance(chain_certs, list)
|
|
chain_string = "\n".join(chain_certs) + "\n"
|
|
write_file(
|
|
module=module,
|
|
content=to_bytes(chain_string),
|
|
path=self.full_chain_path,
|
|
)
|
|
self.changed = True
|
|
|
|
def dump(self) -> dict[str, t.Any]:
|
|
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() -> dict[str, dict[str, str]]:
|
|
return {
|
|
"text1": {"type": "str"},
|
|
"text2": {"type": "str"},
|
|
"text3": {"type": "str"},
|
|
"text4": {"type": "str"},
|
|
"text5": {"type": "str"},
|
|
"text6": {"type": "str"},
|
|
"text7": {"type": "str"},
|
|
"text8": {"type": "str"},
|
|
"text9": {"type": "str"},
|
|
"text10": {"type": "str"},
|
|
"text11": {"type": "str"},
|
|
"text12": {"type": "str"},
|
|
"text13": {"type": "str"},
|
|
"text14": {"type": "str"},
|
|
"text15": {"type": "str"},
|
|
"number1": {"type": "float"},
|
|
"number2": {"type": "float"},
|
|
"number3": {"type": "float"},
|
|
"number4": {"type": "float"},
|
|
"number5": {"type": "float"},
|
|
"date1": {"type": "str"},
|
|
"date2": {"type": "str"},
|
|
"date3": {"type": "str"},
|
|
"date4": {"type": "str"},
|
|
"date5": {"type": "str"},
|
|
"email1": {"type": "str"},
|
|
"email2": {"type": "str"},
|
|
"email3": {"type": "str"},
|
|
"email4": {"type": "str"},
|
|
"email5": {"type": "str"},
|
|
"dropdown1": {"type": "str"},
|
|
"dropdown2": {"type": "str"},
|
|
"dropdown3": {"type": "str"},
|
|
"dropdown4": {"type": "str"},
|
|
"dropdown5": {"type": "str"},
|
|
}
|
|
|
|
|
|
def ecs_certificate_argument_spec() -> dict[str, dict[str, t.Any]]:
|
|
return {
|
|
"backup": {"type": "bool", "default": False},
|
|
"force": {"type": "bool", "default": False},
|
|
"path": {"type": "path", "required": True},
|
|
"full_chain_path": {"type": "path"},
|
|
"tracking_id": {"type": "int"},
|
|
"remaining_days": {"type": "int", "default": 30},
|
|
"request_type": {
|
|
"type": "str",
|
|
"default": "new",
|
|
"choices": ["new", "renew", "reissue", "validate_only"],
|
|
},
|
|
"cert_type": {
|
|
"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": {"type": "str"},
|
|
"subject_alt_name": {"type": "list", "elements": "str"},
|
|
"eku": {
|
|
"type": "str",
|
|
"choices": ["SERVER_AUTH", "CLIENT_AUTH", "SERVER_AND_CLIENT_AUTH"],
|
|
},
|
|
"ct_log": {"type": "bool"},
|
|
"client_id": {"type": "int", "default": 1},
|
|
"org": {"type": "str"},
|
|
"ou": {"type": "list", "elements": "str"},
|
|
"end_user_key_storage_agreement": {"type": "bool"},
|
|
"tracking_info": {"type": "str"},
|
|
"requester_name": {"type": "str", "required": True},
|
|
"requester_email": {"type": "str", "required": True},
|
|
"requester_phone": {"type": "str", "required": True},
|
|
"additional_emails": {"type": "list", "elements": "str"},
|
|
"custom_fields": {"type": "dict", "options": custom_fields_spec()},
|
|
"cert_expiry": {"type": "str"},
|
|
"cert_lifetime": {"type": "str", "choices": ["P1Y", "P2Y", "P3Y"]},
|
|
}
|
|
|
|
|
|
def main() -> t.NoReturn:
|
|
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,
|
|
)
|
|
|
|
assert_required_cryptography_version(
|
|
module, minimum_cryptography_version=MINIMAL_CRYPTOGRAPHY_VERSION
|
|
)
|
|
|
|
# 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=f'The tracking_id field is invalid when request_type="{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".'
|
|
)
|
|
# Reissued or renew request can omit the CSR
|
|
elif module.params["request_type"] != "renew":
|
|
module_params_csr = module.params["csr"]
|
|
if module_params_csr is None:
|
|
module.fail_json(
|
|
msg=f"The csr field is required when request_type={module.params['request_type']}"
|
|
)
|
|
elif not os.path.exists(module_params_csr):
|
|
module.fail_json(
|
|
msg=f"The csr field of {module_params_csr} was not a valid path."
|
|
f" csr is required when request_type={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=f'The "cert_expiry" parameter of "{module.params["cert_expiry"]}" is not a valid date or date-time'
|
|
)
|
|
|
|
certificate = EcsCertificate(module)
|
|
certificate.request_cert(module)
|
|
result = certificate.dump()
|
|
module.exit_json(**result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|