mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-07 05:43:06 +00:00
Reformat everything with black.
I had to undo the u string prefix removals to not drop Python 2 compatibility. That's why black isn't enabled in antsibull-nox.toml yet.
This commit is contained in:
@@ -188,73 +188,88 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec()
|
||||
argument_spec.update_argspec(
|
||||
terms_agreed=dict(type='bool', default=False),
|
||||
state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']),
|
||||
allow_creation=dict(type='bool', default=True),
|
||||
contact=dict(type='list', elements='str', default=[]),
|
||||
new_account_key_src=dict(type='path'),
|
||||
new_account_key_content=dict(type='str', no_log=True),
|
||||
new_account_key_passphrase=dict(type='str', no_log=True),
|
||||
external_account_binding=dict(type='dict', options=dict(
|
||||
kid=dict(type='str', required=True),
|
||||
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
|
||||
key=dict(type='str', required=True, no_log=True),
|
||||
))
|
||||
terms_agreed=dict(type="bool", default=False),
|
||||
state=dict(
|
||||
type="str", required=True, choices=["absent", "present", "changed_key"]
|
||||
),
|
||||
allow_creation=dict(type="bool", default=True),
|
||||
contact=dict(type="list", elements="str", default=[]),
|
||||
new_account_key_src=dict(type="path"),
|
||||
new_account_key_content=dict(type="str", no_log=True),
|
||||
new_account_key_passphrase=dict(type="str", no_log=True),
|
||||
external_account_binding=dict(
|
||||
type="dict",
|
||||
options=dict(
|
||||
kid=dict(type="str", required=True),
|
||||
alg=dict(
|
||||
type="str", required=True, choices=["HS256", "HS384", "HS512"]
|
||||
),
|
||||
key=dict(type="str", required=True, no_log=True),
|
||||
),
|
||||
),
|
||||
)
|
||||
argument_spec.update(
|
||||
mutually_exclusive=(
|
||||
['new_account_key_src', 'new_account_key_content'],
|
||||
),
|
||||
mutually_exclusive=(["new_account_key_src", "new_account_key_content"],),
|
||||
required_if=(
|
||||
# Make sure that for state == changed_key, one of
|
||||
# new_account_key_src and new_account_key_content are specified
|
||||
['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True],
|
||||
[
|
||||
"state",
|
||||
"changed_key",
|
||||
["new_account_key_src", "new_account_key_content"],
|
||||
True,
|
||||
],
|
||||
),
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
backend = create_backend(module, True)
|
||||
|
||||
if module.params['external_account_binding']:
|
||||
if module.params["external_account_binding"]:
|
||||
# Make sure padding is there
|
||||
key = module.params['external_account_binding']['key']
|
||||
key = module.params["external_account_binding"]["key"]
|
||||
if len(key) % 4 != 0:
|
||||
key = key + ('=' * (4 - (len(key) % 4)))
|
||||
key = key + ("=" * (4 - (len(key) % 4)))
|
||||
# Make sure key is Base64 encoded
|
||||
try:
|
||||
base64.urlsafe_b64decode(key)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Key for external_account_binding must be Base64 URL encoded (%s)' % e)
|
||||
module.params['external_account_binding']['key'] = key
|
||||
module.fail_json(
|
||||
msg="Key for external_account_binding must be Base64 URL encoded (%s)"
|
||||
% e
|
||||
)
|
||||
module.params["external_account_binding"]["key"] = key
|
||||
|
||||
try:
|
||||
client = ACMEClient(module, backend)
|
||||
account = ACMEAccount(client)
|
||||
changed = False
|
||||
state = module.params.get('state')
|
||||
state = module.params.get("state")
|
||||
diff_before = {}
|
||||
diff_after = {}
|
||||
if state == 'absent':
|
||||
if state == "absent":
|
||||
created, account_data = account.setup_account(allow_creation=False)
|
||||
if account_data:
|
||||
diff_before = dict(account_data)
|
||||
diff_before['public_account_key'] = client.account_key_data['jwk']
|
||||
diff_before["public_account_key"] = client.account_key_data["jwk"]
|
||||
if created:
|
||||
raise AssertionError('Unwanted account creation')
|
||||
raise AssertionError("Unwanted account creation")
|
||||
if account_data is not None:
|
||||
# Account is not yet deactivated
|
||||
if not module.check_mode:
|
||||
# Deactivate it
|
||||
payload = {
|
||||
'status': 'deactivated'
|
||||
}
|
||||
payload = {"status": "deactivated"}
|
||||
result, info = client.send_signed_request(
|
||||
client.account_uri, payload, error_msg='Failed to deactivate account', expected_status_codes=[200])
|
||||
client.account_uri,
|
||||
payload,
|
||||
error_msg="Failed to deactivate account",
|
||||
expected_status_codes=[200],
|
||||
)
|
||||
changed = True
|
||||
elif state == 'present':
|
||||
allow_creation = module.params.get('allow_creation')
|
||||
contact = [str(v) for v in module.params.get('contact')]
|
||||
terms_agreed = module.params.get('terms_agreed')
|
||||
external_account_binding = module.params.get('external_account_binding')
|
||||
elif state == "present":
|
||||
allow_creation = module.params.get("allow_creation")
|
||||
contact = [str(v) for v in module.params.get("contact")]
|
||||
terms_agreed = module.params.get("terms_agreed")
|
||||
external_account_binding = module.params.get("external_account_binding")
|
||||
created, account_data = account.setup_account(
|
||||
contact,
|
||||
terms_agreed=terms_agreed,
|
||||
@@ -262,77 +277,87 @@ def main():
|
||||
external_account_binding=external_account_binding,
|
||||
)
|
||||
if account_data is None:
|
||||
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||
raise ModuleFailException(
|
||||
msg="Account does not exist or is deactivated."
|
||||
)
|
||||
if created:
|
||||
diff_before = {}
|
||||
else:
|
||||
diff_before = dict(account_data)
|
||||
diff_before['public_account_key'] = client.account_key_data['jwk']
|
||||
diff_before["public_account_key"] = client.account_key_data["jwk"]
|
||||
updated = False
|
||||
if not created:
|
||||
updated, account_data = account.update_account(account_data, contact)
|
||||
changed = created or updated
|
||||
diff_after = dict(account_data)
|
||||
diff_after['public_account_key'] = client.account_key_data['jwk']
|
||||
elif state == 'changed_key':
|
||||
diff_after["public_account_key"] = client.account_key_data["jwk"]
|
||||
elif state == "changed_key":
|
||||
# Parse new account key
|
||||
try:
|
||||
new_key_data = client.parse_key(
|
||||
module.params.get('new_account_key_src'),
|
||||
module.params.get('new_account_key_content'),
|
||||
passphrase=module.params.get('new_account_key_passphrase'),
|
||||
module.params.get("new_account_key_src"),
|
||||
module.params.get("new_account_key_content"),
|
||||
passphrase=module.params.get("new_account_key_passphrase"),
|
||||
)
|
||||
except KeyParsingError as e:
|
||||
raise ModuleFailException("Error while parsing new account key: {msg}".format(msg=e.msg))
|
||||
raise ModuleFailException(
|
||||
"Error while parsing new account key: {msg}".format(msg=e.msg)
|
||||
)
|
||||
# Verify that the account exists and has not been deactivated
|
||||
created, account_data = account.setup_account(allow_creation=False)
|
||||
if created:
|
||||
raise AssertionError('Unwanted account creation')
|
||||
raise AssertionError("Unwanted account creation")
|
||||
if account_data is None:
|
||||
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||
raise ModuleFailException(
|
||||
msg="Account does not exist or is deactivated."
|
||||
)
|
||||
diff_before = dict(account_data)
|
||||
diff_before['public_account_key'] = client.account_key_data['jwk']
|
||||
diff_before["public_account_key"] = client.account_key_data["jwk"]
|
||||
# Now we can start the account key rollover
|
||||
if not module.check_mode:
|
||||
# Compose inner signed message
|
||||
# https://tools.ietf.org/html/rfc8555#section-7.3.5
|
||||
url = client.directory['keyChange']
|
||||
url = client.directory["keyChange"]
|
||||
protected = {
|
||||
"alg": new_key_data['alg'],
|
||||
"jwk": new_key_data['jwk'],
|
||||
"alg": new_key_data["alg"],
|
||||
"jwk": new_key_data["jwk"],
|
||||
"url": url,
|
||||
}
|
||||
payload = {
|
||||
"account": client.account_uri,
|
||||
"newKey": new_key_data['jwk'], # specified in draft 12 and older
|
||||
"newKey": new_key_data["jwk"], # specified in draft 12 and older
|
||||
"oldKey": client.account_jwk, # specified in draft 13 and newer
|
||||
}
|
||||
data = client.sign_request(protected, payload, new_key_data)
|
||||
# Send request and verify result
|
||||
result, info = client.send_signed_request(
|
||||
url, data, error_msg='Failed to rollover account key', expected_status_codes=[200])
|
||||
url,
|
||||
data,
|
||||
error_msg="Failed to rollover account key",
|
||||
expected_status_codes=[200],
|
||||
)
|
||||
if module._diff:
|
||||
client.account_key_data = new_key_data
|
||||
client.account_jws_header['alg'] = new_key_data['alg']
|
||||
client.account_jws_header["alg"] = new_key_data["alg"]
|
||||
diff_after = account.get_account_data()
|
||||
elif module._diff:
|
||||
# Kind of fake diff_after
|
||||
diff_after = dict(diff_before)
|
||||
diff_after['public_account_key'] = new_key_data['jwk']
|
||||
diff_after["public_account_key"] = new_key_data["jwk"]
|
||||
changed = True
|
||||
result = {
|
||||
'changed': changed,
|
||||
'account_uri': client.account_uri,
|
||||
"changed": changed,
|
||||
"account_uri": client.account_uri,
|
||||
}
|
||||
if module._diff:
|
||||
result['diff'] = {
|
||||
'before': diff_before,
|
||||
'after': diff_after,
|
||||
result["diff"] = {
|
||||
"before": diff_before,
|
||||
"after": diff_after,
|
||||
}
|
||||
module.exit_json(**result)
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -226,24 +226,30 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||
|
||||
|
||||
def get_orders_list(module, client, orders_url):
|
||||
'''
|
||||
"""
|
||||
Retrieves orders list (handles pagination).
|
||||
'''
|
||||
"""
|
||||
orders = []
|
||||
while orders_url:
|
||||
# Get part of orders list
|
||||
res, info = client.get_request(orders_url, parse_json_result=True, fail_on_error=True)
|
||||
if not res.get('orders'):
|
||||
res, info = client.get_request(
|
||||
orders_url, parse_json_result=True, fail_on_error=True
|
||||
)
|
||||
if not res.get("orders"):
|
||||
if orders:
|
||||
module.warn('When retrieving orders list part {0}, got empty result list'.format(orders_url))
|
||||
module.warn(
|
||||
"When retrieving orders list part {0}, got empty result list".format(
|
||||
orders_url
|
||||
)
|
||||
)
|
||||
break
|
||||
# Add order URLs to result list
|
||||
orders.extend(res['orders'])
|
||||
orders.extend(res["orders"])
|
||||
# Extract URL of next part of results list
|
||||
new_orders_url = []
|
||||
|
||||
def f(link, relation):
|
||||
if relation == 'next':
|
||||
if relation == "next":
|
||||
new_orders_url.append(link)
|
||||
|
||||
process_links(info, f)
|
||||
@@ -256,16 +262,18 @@ def get_orders_list(module, client, orders_url):
|
||||
|
||||
|
||||
def get_order(client, order_url):
|
||||
'''
|
||||
"""
|
||||
Retrieve order data.
|
||||
'''
|
||||
"""
|
||||
return client.get_request(order_url, parse_json_result=True, fail_on_error=True)[0]
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = create_default_argspec()
|
||||
argument_spec.update_argspec(
|
||||
retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']),
|
||||
retrieve_orders=dict(
|
||||
type="str", default="ignore", choices=["ignore", "url_list", "object_list"]
|
||||
),
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
backend = create_backend(module, True)
|
||||
@@ -280,28 +288,31 @@ def main():
|
||||
remove_account_uri_if_not_exists=True,
|
||||
)
|
||||
if created:
|
||||
raise AssertionError('Unwanted account creation')
|
||||
raise AssertionError("Unwanted account creation")
|
||||
result = {
|
||||
'changed': False,
|
||||
'exists': client.account_uri is not None,
|
||||
'account_uri': client.account_uri,
|
||||
"changed": False,
|
||||
"exists": client.account_uri is not None,
|
||||
"account_uri": client.account_uri,
|
||||
}
|
||||
if client.account_uri is not None:
|
||||
# Make sure promised data is there
|
||||
if 'contact' not in account_data:
|
||||
account_data['contact'] = []
|
||||
account_data['public_account_key'] = client.account_key_data['jwk']
|
||||
result['account'] = account_data
|
||||
if "contact" not in account_data:
|
||||
account_data["contact"] = []
|
||||
account_data["public_account_key"] = client.account_key_data["jwk"]
|
||||
result["account"] = account_data
|
||||
# Retrieve orders list
|
||||
if account_data.get('orders') and module.params['retrieve_orders'] != 'ignore':
|
||||
orders = get_orders_list(module, client, account_data['orders'])
|
||||
result['order_uris'] = orders
|
||||
if module.params['retrieve_orders'] == 'object_list':
|
||||
result['orders'] = [get_order(client, order) for order in orders]
|
||||
if (
|
||||
account_data.get("orders")
|
||||
and module.params["retrieve_orders"] != "ignore"
|
||||
):
|
||||
orders = get_orders_list(module, client, account_data["orders"])
|
||||
result["order_uris"] = orders
|
||||
if module.params["retrieve_orders"] == "object_list":
|
||||
result["orders"] = [get_order(client, order) for order in orders]
|
||||
module.exit_json(**result)
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -112,16 +112,12 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_account=False)
|
||||
argument_spec.update_argspec(
|
||||
certificate_path=dict(type='path'),
|
||||
certificate_content=dict(type='str'),
|
||||
certificate_path=dict(type="path"),
|
||||
certificate_content=dict(type="str"),
|
||||
)
|
||||
argument_spec.update(
|
||||
required_one_of=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
),
|
||||
required_one_of=(["certificate_path", "certificate_content"],),
|
||||
mutually_exclusive=(["certificate_path", "certificate_content"],),
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
backend = create_backend(module, True)
|
||||
@@ -129,10 +125,12 @@ def main():
|
||||
try:
|
||||
client = ACMEClient(module, backend)
|
||||
if not client.directory.has_renewal_info_endpoint():
|
||||
module.fail_json(msg='The ACME endpoint does not support ACME Renewal Information retrieval')
|
||||
module.fail_json(
|
||||
msg="The ACME endpoint does not support ACME Renewal Information retrieval"
|
||||
)
|
||||
renewal_info = client.get_renewal_info(
|
||||
cert_filename=module.params['certificate_path'],
|
||||
cert_content=module.params['certificate_content'],
|
||||
cert_filename=module.params["certificate_path"],
|
||||
cert_content=module.params["certificate_content"],
|
||||
include_retry_after=True,
|
||||
)
|
||||
module.exit_json(renewal_info=renewal_info)
|
||||
@@ -140,5 +138,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -600,76 +600,94 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||
)
|
||||
|
||||
|
||||
NO_CHALLENGE = 'no challenge'
|
||||
NO_CHALLENGE = "no challenge"
|
||||
|
||||
|
||||
class ACMECertificateClient(object):
|
||||
'''
|
||||
"""
|
||||
ACME client class. Uses an ACME account object and a CSR to
|
||||
start and validate ACME challenges and download the respective
|
||||
certificates.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, module, backend):
|
||||
self.module = module
|
||||
self.version = module.params['acme_version']
|
||||
self.challenge = module.params['challenge']
|
||||
self.version = module.params["acme_version"]
|
||||
self.challenge = module.params["challenge"]
|
||||
# We use None instead of a magic string for 'no challenge'
|
||||
if self.challenge == NO_CHALLENGE:
|
||||
self.challenge = None
|
||||
self.csr = module.params['csr']
|
||||
self.csr_content = module.params['csr_content']
|
||||
self.dest = module.params.get('dest')
|
||||
self.fullchain_dest = module.params.get('fullchain_dest')
|
||||
self.chain_dest = module.params.get('chain_dest')
|
||||
self.csr = module.params["csr"]
|
||||
self.csr_content = module.params["csr_content"]
|
||||
self.dest = module.params.get("dest")
|
||||
self.fullchain_dest = module.params.get("fullchain_dest")
|
||||
self.chain_dest = module.params.get("chain_dest")
|
||||
self.client = ACMEClient(module, backend)
|
||||
self.account = ACMEAccount(self.client)
|
||||
self.directory = self.client.directory
|
||||
self.data = module.params['data']
|
||||
self.data = module.params["data"]
|
||||
self.authorizations = None
|
||||
self.cert_days = -1
|
||||
self.order = None
|
||||
self.order_uri = self.data.get('order_uri') if self.data else None
|
||||
self.order_uri = self.data.get("order_uri") if self.data else None
|
||||
self.all_chains = None
|
||||
self.select_chain_matcher = []
|
||||
self.include_renewal_cert_id = module.params['include_renewal_cert_id']
|
||||
self.profile = module.params['profile']
|
||||
self.order_creation_error_strategy = module.params['order_creation_error_strategy']
|
||||
self.order_creation_max_retries = module.params['order_creation_max_retries']
|
||||
self.include_renewal_cert_id = module.params["include_renewal_cert_id"]
|
||||
self.profile = module.params["profile"]
|
||||
self.order_creation_error_strategy = module.params[
|
||||
"order_creation_error_strategy"
|
||||
]
|
||||
self.order_creation_max_retries = module.params["order_creation_max_retries"]
|
||||
|
||||
if self.module.params['select_chain']:
|
||||
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
|
||||
if self.module.params["select_chain"]:
|
||||
for criterium_idx, criterium in enumerate(
|
||||
self.module.params["select_chain"]
|
||||
):
|
||||
try:
|
||||
self.select_chain_matcher.append(
|
||||
self.client.backend.create_chain_matcher(
|
||||
Criterium(criterium, index=criterium_idx)))
|
||||
Criterium(criterium, index=criterium_idx)
|
||||
)
|
||||
)
|
||||
except ValueError as exc:
|
||||
self.module.warn('Error while parsing criterium: {error}. Ignoring criterium.'.format(error=exc))
|
||||
self.module.warn(
|
||||
"Error while parsing criterium: {error}. Ignoring criterium.".format(
|
||||
error=exc
|
||||
)
|
||||
)
|
||||
|
||||
if self.profile is not None:
|
||||
meta_profiles = (self.directory.get('meta') or {}).get('profiles') or {}
|
||||
meta_profiles = (self.directory.get("meta") or {}).get("profiles") or {}
|
||||
if not meta_profiles:
|
||||
raise ModuleFailException(msg='The ACME CA does not support profiles.')
|
||||
raise ModuleFailException(msg="The ACME CA does not support profiles.")
|
||||
if self.profile not in meta_profiles:
|
||||
raise ModuleFailException(msg='The ACME CA does not support selected profile {0!r}.'.format(self.profile))
|
||||
raise ModuleFailException(
|
||||
msg="The ACME CA does not support selected profile {0!r}.".format(
|
||||
self.profile
|
||||
)
|
||||
)
|
||||
|
||||
# Make sure account exists
|
||||
modify_account = module.params['modify_account']
|
||||
modify_account = module.params["modify_account"]
|
||||
if modify_account or self.version > 1:
|
||||
contact = []
|
||||
if module.params['account_email']:
|
||||
contact.append('mailto:' + module.params['account_email'])
|
||||
if module.params["account_email"]:
|
||||
contact.append("mailto:" + module.params["account_email"])
|
||||
created, account_data = self.account.setup_account(
|
||||
contact,
|
||||
agreement=module.params.get('agreement'),
|
||||
terms_agreed=module.params.get('terms_agreed'),
|
||||
agreement=module.params.get("agreement"),
|
||||
terms_agreed=module.params.get("terms_agreed"),
|
||||
allow_creation=modify_account,
|
||||
)
|
||||
if account_data is None:
|
||||
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||
raise ModuleFailException(
|
||||
msg="Account does not exist or is deactivated."
|
||||
)
|
||||
updated = False
|
||||
if not created and account_data and modify_account:
|
||||
updated, account_data = self.account.update_account(account_data, contact)
|
||||
updated, account_data = self.account.update_account(
|
||||
account_data, contact
|
||||
)
|
||||
self.changed = created or updated
|
||||
else:
|
||||
# This happens if modify_account is False and the ACME v1
|
||||
@@ -683,13 +701,15 @@ class ACMECertificateClient(object):
|
||||
raise ModuleFailException("CSR %s not found" % (self.csr))
|
||||
|
||||
# Extract list of identifiers from CSR
|
||||
self.identifiers = self.client.backend.get_ordered_csr_identifiers(csr_filename=self.csr, csr_content=self.csr_content)
|
||||
self.identifiers = self.client.backend.get_ordered_csr_identifiers(
|
||||
csr_filename=self.csr, csr_content=self.csr_content
|
||||
)
|
||||
|
||||
def is_first_step(self):
|
||||
'''
|
||||
"""
|
||||
Return True if this is the first execution of this module, i.e. if a
|
||||
sufficient data object from a first run has not been provided.
|
||||
'''
|
||||
"""
|
||||
if self.data is None:
|
||||
return True
|
||||
if self.version == 1:
|
||||
@@ -701,32 +721,34 @@ class ACMECertificateClient(object):
|
||||
return self.order_uri is None
|
||||
|
||||
def _get_cert_info_or_none(self):
|
||||
if self.module.params.get('dest'):
|
||||
filename = self.module.params['dest']
|
||||
if self.module.params.get("dest"):
|
||||
filename = self.module.params["dest"]
|
||||
else:
|
||||
filename = self.module.params['fullchain_dest']
|
||||
filename = self.module.params["fullchain_dest"]
|
||||
if not os.path.exists(filename):
|
||||
return None
|
||||
return self.client.backend.get_cert_information(cert_filename=filename)
|
||||
|
||||
def start_challenges(self):
|
||||
'''
|
||||
"""
|
||||
Create new authorizations for all identifiers of the CSR,
|
||||
respectively start a new order for ACME v2.
|
||||
'''
|
||||
"""
|
||||
self.authorizations = {}
|
||||
if self.version == 1:
|
||||
for identifier_type, identifier in self.identifiers:
|
||||
if identifier_type != 'dns':
|
||||
raise ModuleFailException('ACME v1 only supports DNS identifiers!')
|
||||
if identifier_type != "dns":
|
||||
raise ModuleFailException("ACME v1 only supports DNS identifiers!")
|
||||
for identifier_type, identifier in self.identifiers:
|
||||
authz = Authorization.create(self.client, identifier_type, identifier)
|
||||
self.authorizations[normalize_combined_identifier(authz.combined_identifier)] = authz
|
||||
self.authorizations[
|
||||
normalize_combined_identifier(authz.combined_identifier)
|
||||
] = authz
|
||||
else:
|
||||
replaces_cert_id = None
|
||||
if (
|
||||
self.include_renewal_cert_id == 'always' or
|
||||
(self.include_renewal_cert_id == 'when_ari_supported' and self.client.directory.has_renewal_info_endpoint())
|
||||
if self.include_renewal_cert_id == "always" or (
|
||||
self.include_renewal_cert_id == "when_ari_supported"
|
||||
and self.client.directory.has_renewal_info_endpoint()
|
||||
):
|
||||
cert_info = self._get_cert_info_or_none()
|
||||
if cert_info is not None:
|
||||
@@ -750,39 +772,46 @@ class ACMECertificateClient(object):
|
||||
self.changed = True
|
||||
|
||||
def get_challenges_data(self, first_step):
|
||||
'''
|
||||
"""
|
||||
Get challenge details for the chosen challenge type.
|
||||
Return a tuple of generic challenge details, and specialized DNS challenge details.
|
||||
'''
|
||||
"""
|
||||
# Get general challenge data
|
||||
data = {}
|
||||
for type_identifier, authz in self.authorizations.items():
|
||||
identifier_type, identifier = split_identifier(type_identifier)
|
||||
# Skip valid authentications: their challenges are already valid
|
||||
# and do not need to be returned
|
||||
if authz.status == 'valid':
|
||||
if authz.status == "valid":
|
||||
continue
|
||||
# We drop the type from the key to preserve backwards compatibility
|
||||
data[authz.identifier] = authz.get_challenge_data(self.client)
|
||||
if first_step and self.challenge is not None and self.challenge not in data[authz.identifier]:
|
||||
raise ModuleFailException("Found no challenge of type '{0}' for identifier {1}!".format(
|
||||
self.challenge, type_identifier))
|
||||
if (
|
||||
first_step
|
||||
and self.challenge is not None
|
||||
and self.challenge not in data[authz.identifier]
|
||||
):
|
||||
raise ModuleFailException(
|
||||
"Found no challenge of type '{0}' for identifier {1}!".format(
|
||||
self.challenge, type_identifier
|
||||
)
|
||||
)
|
||||
# Get DNS challenge data
|
||||
data_dns = {}
|
||||
if self.challenge == 'dns-01':
|
||||
if self.challenge == "dns-01":
|
||||
for identifier, challenges in data.items():
|
||||
if self.challenge in challenges:
|
||||
values = data_dns.get(challenges[self.challenge]['record'])
|
||||
values = data_dns.get(challenges[self.challenge]["record"])
|
||||
if values is None:
|
||||
values = []
|
||||
data_dns[challenges[self.challenge]['record']] = values
|
||||
values.append(challenges[self.challenge]['resource_value'])
|
||||
data_dns[challenges[self.challenge]["record"]] = values
|
||||
values.append(challenges[self.challenge]["resource_value"])
|
||||
return data, data_dns
|
||||
|
||||
def finish_challenges(self):
|
||||
'''
|
||||
"""
|
||||
Verify challenges for all identifiers of the CSR.
|
||||
'''
|
||||
"""
|
||||
self.authorizations = {}
|
||||
|
||||
# Step 1: obtain challenge information
|
||||
@@ -791,7 +820,9 @@ class ACMECertificateClient(object):
|
||||
# will be returned instead.
|
||||
for identifier_type, identifier in self.identifiers:
|
||||
authz = Authorization.create(self.client, identifier_type, identifier)
|
||||
self.authorizations[combine_identifier(identifier_type, identifier)] = authz
|
||||
self.authorizations[combine_identifier(identifier_type, identifier)] = (
|
||||
authz
|
||||
)
|
||||
else:
|
||||
# For ACME v2, we obtain the order object by fetching the
|
||||
# order URI, and extract the information from there.
|
||||
@@ -802,12 +833,12 @@ class ACMECertificateClient(object):
|
||||
# Step 2: validate pending challenges
|
||||
authzs_to_wait_for = []
|
||||
for type_identifier, authz in self.authorizations.items():
|
||||
if authz.status == 'pending':
|
||||
if authz.status == "pending":
|
||||
if self.challenge is not None:
|
||||
authz.call_validate(self.client, self.challenge, wait=False)
|
||||
authzs_to_wait_for.append(authz)
|
||||
# If there is no challenge, we must check whether the authz is valid
|
||||
elif authz.status != 'valid':
|
||||
elif authz.status != "valid":
|
||||
authz.raise_error(
|
||||
'Status is not "valid", even though no challenge should be necessary',
|
||||
module=self.client.module,
|
||||
@@ -823,7 +854,11 @@ class ACMECertificateClient(object):
|
||||
try:
|
||||
alt_cert = CertificateChain.download(self.client, alternate)
|
||||
except ModuleFailException as e:
|
||||
self.module.warn('Error while downloading alternative certificate {0}: {1}'.format(alternate, e))
|
||||
self.module.warn(
|
||||
"Error while downloading alternative certificate {0}: {1}".format(
|
||||
alternate, e
|
||||
)
|
||||
)
|
||||
continue
|
||||
alternate_chains.append(alt_cert)
|
||||
return alternate_chains
|
||||
@@ -832,35 +867,52 @@ class ACMECertificateClient(object):
|
||||
for criterium_idx, matcher in enumerate(self.select_chain_matcher):
|
||||
for chain in chains:
|
||||
if matcher.match(chain):
|
||||
self.module.debug('Found matching chain for criterium {0}'.format(criterium_idx))
|
||||
self.module.debug(
|
||||
"Found matching chain for criterium {0}".format(criterium_idx)
|
||||
)
|
||||
return chain
|
||||
return None
|
||||
|
||||
def get_certificate(self):
|
||||
'''
|
||||
"""
|
||||
Request a new certificate and write it to the destination file.
|
||||
First verifies whether all authorizations are valid; if not, aborts
|
||||
with an error.
|
||||
'''
|
||||
"""
|
||||
for identifier_type, identifier in self.identifiers:
|
||||
authz = self.authorizations.get(normalize_combined_identifier(combine_identifier(identifier_type, identifier)))
|
||||
authz = self.authorizations.get(
|
||||
normalize_combined_identifier(
|
||||
combine_identifier(identifier_type, identifier)
|
||||
)
|
||||
)
|
||||
if authz is None:
|
||||
raise ModuleFailException('Found no authorization information for "{identifier}"!'.format(
|
||||
identifier=combine_identifier(identifier_type, identifier)))
|
||||
if authz.status != 'valid':
|
||||
authz.raise_error('Status is "{status}" and not "valid"'.format(status=authz.status), module=self.module)
|
||||
raise ModuleFailException(
|
||||
'Found no authorization information for "{identifier}"!'.format(
|
||||
identifier=combine_identifier(identifier_type, identifier)
|
||||
)
|
||||
)
|
||||
if authz.status != "valid":
|
||||
authz.raise_error(
|
||||
'Status is "{status}" and not "valid"'.format(status=authz.status),
|
||||
module=self.module,
|
||||
)
|
||||
|
||||
if self.version == 1:
|
||||
cert = retrieve_acme_v1_certificate(self.client, pem_to_der(self.csr, self.csr_content))
|
||||
cert = retrieve_acme_v1_certificate(
|
||||
self.client, pem_to_der(self.csr, self.csr_content)
|
||||
)
|
||||
else:
|
||||
self.order.finalize(self.client, pem_to_der(self.csr, self.csr_content))
|
||||
cert = CertificateChain.download(self.client, self.order.certificate_uri)
|
||||
if self.module.params['retrieve_all_alternates'] or self.select_chain_matcher:
|
||||
if (
|
||||
self.module.params["retrieve_all_alternates"]
|
||||
or self.select_chain_matcher
|
||||
):
|
||||
# Retrieve alternate chains
|
||||
alternate_chains = self.download_alternate_chains(cert)
|
||||
|
||||
# Prepare return value for all alternate chains
|
||||
if self.module.params['retrieve_all_alternates']:
|
||||
if self.module.params["retrieve_all_alternates"]:
|
||||
self.all_chains = [cert.to_json()]
|
||||
for alt_chain in alternate_chains:
|
||||
self.all_chains.append(alt_chain.to_json())
|
||||
@@ -871,89 +923,122 @@ class ACMECertificateClient(object):
|
||||
if matching_chain:
|
||||
cert = matching_chain
|
||||
else:
|
||||
self.module.debug('Found no matching alternative chain')
|
||||
self.module.debug("Found no matching alternative chain")
|
||||
|
||||
if cert.cert is not None:
|
||||
pem_cert = cert.cert
|
||||
chain = cert.chain
|
||||
|
||||
if self.dest and write_file(self.module, self.dest, pem_cert.encode('utf8')):
|
||||
if self.dest and write_file(
|
||||
self.module, self.dest, pem_cert.encode("utf8")
|
||||
):
|
||||
self.cert_days = self.client.backend.get_cert_days(self.dest)
|
||||
self.changed = True
|
||||
|
||||
if self.fullchain_dest and write_file(self.module, self.fullchain_dest, (pem_cert + "\n".join(chain)).encode('utf8')):
|
||||
if self.fullchain_dest and write_file(
|
||||
self.module,
|
||||
self.fullchain_dest,
|
||||
(pem_cert + "\n".join(chain)).encode("utf8"),
|
||||
):
|
||||
self.cert_days = self.client.backend.get_cert_days(self.fullchain_dest)
|
||||
self.changed = True
|
||||
|
||||
if self.chain_dest and write_file(self.module, self.chain_dest, ("\n".join(chain)).encode('utf8')):
|
||||
if self.chain_dest and write_file(
|
||||
self.module, self.chain_dest, ("\n".join(chain)).encode("utf8")
|
||||
):
|
||||
self.changed = True
|
||||
|
||||
def deactivate_authzs(self):
|
||||
'''
|
||||
"""
|
||||
Deactivates all valid authz's. Does not raise exceptions.
|
||||
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
|
||||
https://tools.ietf.org/html/rfc8555#section-7.5.2
|
||||
'''
|
||||
"""
|
||||
for authz in self.authorizations.values():
|
||||
try:
|
||||
authz.deactivate(self.client)
|
||||
except Exception:
|
||||
# ignore errors
|
||||
pass
|
||||
if authz.status != 'deactivated':
|
||||
self.module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url))
|
||||
if authz.status != "deactivated":
|
||||
self.module.warn(
|
||||
warning="Could not deactivate authz object {0}.".format(authz.url)
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_certificate=True)
|
||||
argument_spec.argument_spec['csr']['aliases'] = ['src']
|
||||
argument_spec.argument_spec["csr"]["aliases"] = ["src"]
|
||||
argument_spec.update_argspec(
|
||||
modify_account=dict(type='bool', default=True),
|
||||
account_email=dict(type='str'),
|
||||
agreement=dict(type='str'),
|
||||
terms_agreed=dict(type='bool', default=False),
|
||||
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]),
|
||||
data=dict(type='dict'),
|
||||
dest=dict(type='path', aliases=['cert']),
|
||||
fullchain_dest=dict(type='path', aliases=['fullchain']),
|
||||
chain_dest=dict(type='path', aliases=['chain']),
|
||||
remaining_days=dict(type='int', default=10),
|
||||
deactivate_authzs=dict(type='bool', default=False),
|
||||
force=dict(type='bool', default=False),
|
||||
retrieve_all_alternates=dict(type='bool', default=False),
|
||||
select_chain=dict(type='list', elements='dict', options=dict(
|
||||
test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']),
|
||||
issuer=dict(type='dict'),
|
||||
subject=dict(type='dict'),
|
||||
subject_key_identifier=dict(type='str'),
|
||||
authority_key_identifier=dict(type='str'),
|
||||
)),
|
||||
include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
|
||||
profile=dict(type='str'),
|
||||
order_creation_error_strategy=dict(type='str', default='auto', choices=['auto', 'always', 'fail', 'retry_without_replaces_cert_id']),
|
||||
order_creation_max_retries=dict(type='int', default=3),
|
||||
modify_account=dict(type="bool", default=True),
|
||||
account_email=dict(type="str"),
|
||||
agreement=dict(type="str"),
|
||||
terms_agreed=dict(type="bool", default=False),
|
||||
challenge=dict(
|
||||
type="str",
|
||||
default="http-01",
|
||||
choices=["http-01", "dns-01", "tls-alpn-01", NO_CHALLENGE],
|
||||
),
|
||||
data=dict(type="dict"),
|
||||
dest=dict(type="path", aliases=["cert"]),
|
||||
fullchain_dest=dict(type="path", aliases=["fullchain"]),
|
||||
chain_dest=dict(type="path", aliases=["chain"]),
|
||||
remaining_days=dict(type="int", default=10),
|
||||
deactivate_authzs=dict(type="bool", default=False),
|
||||
force=dict(type="bool", default=False),
|
||||
retrieve_all_alternates=dict(type="bool", default=False),
|
||||
select_chain=dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=dict(
|
||||
test_certificates=dict(
|
||||
type="str", default="all", choices=["first", "last", "all"]
|
||||
),
|
||||
issuer=dict(type="dict"),
|
||||
subject=dict(type="dict"),
|
||||
subject_key_identifier=dict(type="str"),
|
||||
authority_key_identifier=dict(type="str"),
|
||||
),
|
||||
),
|
||||
include_renewal_cert_id=dict(
|
||||
type="str",
|
||||
choices=["never", "when_ari_supported", "always"],
|
||||
default="never",
|
||||
),
|
||||
profile=dict(type="str"),
|
||||
order_creation_error_strategy=dict(
|
||||
type="str",
|
||||
default="auto",
|
||||
choices=["auto", "always", "fail", "retry_without_replaces_cert_id"],
|
||||
),
|
||||
order_creation_max_retries=dict(type="int", default=3),
|
||||
)
|
||||
argument_spec.update(
|
||||
required_one_of=[
|
||||
['dest', 'fullchain_dest'],
|
||||
["dest", "fullchain_dest"],
|
||||
],
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
backend = create_backend(module, False)
|
||||
|
||||
try:
|
||||
if module.params.get('dest'):
|
||||
cert_days = backend.get_cert_days(module.params['dest'])
|
||||
if module.params.get("dest"):
|
||||
cert_days = backend.get_cert_days(module.params["dest"])
|
||||
else:
|
||||
cert_days = backend.get_cert_days(module.params['fullchain_dest'])
|
||||
cert_days = backend.get_cert_days(module.params["fullchain_dest"])
|
||||
|
||||
if module.params['force'] or cert_days < module.params['remaining_days']:
|
||||
if module.params["force"] or cert_days < module.params["remaining_days"]:
|
||||
# If checkmode is active, base the changed state solely on the status
|
||||
# of the certificate file as all other actions (accessing an account, checking
|
||||
# the authorization status...) would lead to potential changes of the current
|
||||
# state
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, authorizations={}, challenge_data={}, cert_days=cert_days)
|
||||
module.exit_json(
|
||||
changed=True,
|
||||
authorizations={},
|
||||
challenge_data={},
|
||||
cert_days=cert_days,
|
||||
)
|
||||
else:
|
||||
client = ACMECertificateClient(module, backend)
|
||||
client.cert_days = cert_days
|
||||
@@ -968,9 +1053,9 @@ def main():
|
||||
client.finish_challenges()
|
||||
client.get_certificate()
|
||||
if client.all_chains is not None:
|
||||
other['all_chains'] = client.all_chains
|
||||
other["all_chains"] = client.all_chains
|
||||
finally:
|
||||
if module.params['deactivate_authzs']:
|
||||
if module.params["deactivate_authzs"]:
|
||||
client.deactivate_authzs()
|
||||
data, data_dns = client.get_challenges_data(first_step=is_first_step)
|
||||
auths = dict()
|
||||
@@ -994,5 +1079,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -73,11 +73,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.orders impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec()
|
||||
argument_spec.update_argspec(
|
||||
order_uri=dict(type='str', required=True),
|
||||
order_uri=dict(type="str", required=True),
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
if module.params['acme_version'] == 1:
|
||||
module.fail_json('The module does not support acme_version=1')
|
||||
if module.params["acme_version"] == 1:
|
||||
module.fail_json("The module does not support acme_version=1")
|
||||
|
||||
backend = create_backend(module, False)
|
||||
|
||||
@@ -87,9 +87,9 @@ def main():
|
||||
|
||||
dummy, account_data = account.setup_account(allow_creation=False)
|
||||
if account_data is None:
|
||||
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||
raise ModuleFailException(msg="Account does not exist or is deactivated.")
|
||||
|
||||
order = Order.from_url(client, module.params['order_uri'])
|
||||
order = Order.from_url(client, module.params["order_uri"])
|
||||
order.load_authorizations(client)
|
||||
|
||||
changed = False
|
||||
@@ -104,13 +104,15 @@ def main():
|
||||
except Exception:
|
||||
# ignore errors
|
||||
pass
|
||||
if authz.status != 'deactivated':
|
||||
module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url))
|
||||
if authz.status != "deactivated":
|
||||
module.warn(
|
||||
warning="Could not deactivate authz object {0}.".format(authz.url)
|
||||
)
|
||||
|
||||
module.exit_json(changed=changed)
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
module: acme_certificate_order_create
|
||||
author: Felix Fontein (@felixfontein)
|
||||
version_added: 2.24.0
|
||||
@@ -158,9 +158,9 @@ options:
|
||||
for at most the specified amount of times.
|
||||
type: int
|
||||
default: 3
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
---
|
||||
### Example with HTTP-01 challenge ###
|
||||
|
||||
@@ -253,9 +253,9 @@ EXAMPLES = r'''
|
||||
cert_dest: /etc/httpd/ssl/sample.com.crt
|
||||
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
|
||||
chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
RETURN = """
|
||||
challenge_data:
|
||||
description:
|
||||
- For every identifier, provides the challenge information.
|
||||
@@ -377,7 +377,7 @@ account_uri:
|
||||
description: ACME account URI.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
"""
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
create_backend,
|
||||
@@ -394,37 +394,51 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_certificate=True)
|
||||
argument_spec.update_argspec(
|
||||
deactivate_authzs=dict(type='bool', default=True),
|
||||
replaces_cert_id=dict(type='str'),
|
||||
profile=dict(type='str'),
|
||||
order_creation_error_strategy=dict(type='str', default='auto', choices=['auto', 'always', 'fail', 'retry_without_replaces_cert_id']),
|
||||
order_creation_max_retries=dict(type='int', default=3),
|
||||
deactivate_authzs=dict(type="bool", default=True),
|
||||
replaces_cert_id=dict(type="str"),
|
||||
profile=dict(type="str"),
|
||||
order_creation_error_strategy=dict(
|
||||
type="str",
|
||||
default="auto",
|
||||
choices=["auto", "always", "fail", "retry_without_replaces_cert_id"],
|
||||
),
|
||||
order_creation_max_retries=dict(type="int", default=3),
|
||||
)
|
||||
module = argument_spec.create_ansible_module()
|
||||
if module.params['acme_version'] == 1:
|
||||
module.fail_json('The module does not support acme_version=1')
|
||||
if module.params["acme_version"] == 1:
|
||||
module.fail_json("The module does not support acme_version=1")
|
||||
|
||||
backend = create_backend(module, False)
|
||||
|
||||
try:
|
||||
client = ACMECertificateClient(module, backend)
|
||||
|
||||
profile = module.params['profile']
|
||||
profile = module.params["profile"]
|
||||
if profile is not None:
|
||||
meta_profiles = (client.client.directory.get('meta') or {}).get('profiles') or {}
|
||||
meta_profiles = (client.client.directory.get("meta") or {}).get(
|
||||
"profiles"
|
||||
) or {}
|
||||
if not meta_profiles:
|
||||
raise ModuleFailException(msg='The ACME CA does not support profiles. Please omit the "profile" option.')
|
||||
raise ModuleFailException(
|
||||
msg='The ACME CA does not support profiles. Please omit the "profile" option.'
|
||||
)
|
||||
if profile not in meta_profiles:
|
||||
raise ModuleFailException(msg='The ACME CA does not support selected profile {0!r}.'.format(profile))
|
||||
raise ModuleFailException(
|
||||
msg="The ACME CA does not support selected profile {0!r}.".format(
|
||||
profile
|
||||
)
|
||||
)
|
||||
|
||||
order = None
|
||||
done = False
|
||||
try:
|
||||
order = client.create_order(replaces_cert_id=module.params['replaces_cert_id'], profile=profile)
|
||||
order = client.create_order(
|
||||
replaces_cert_id=module.params["replaces_cert_id"], profile=profile
|
||||
)
|
||||
client.check_that_authorizations_can_be_used(order)
|
||||
done = True
|
||||
finally:
|
||||
if module.params['deactivate_authzs'] and order and not done:
|
||||
if module.params["deactivate_authzs"] and order and not done:
|
||||
client.deactivate_authzs(order)
|
||||
data, data_dns = client.get_challenges_data(order)
|
||||
module.exit_json(
|
||||
@@ -438,5 +452,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
module: acme_certificate_order_finalize
|
||||
author: Felix Fontein (@felixfontein)
|
||||
version_added: 2.24.0
|
||||
@@ -170,9 +170,9 @@ options:
|
||||
- "The identifier must be of the form
|
||||
V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
|
||||
type: str
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
---
|
||||
### Example with HTTP-01 challenge ###
|
||||
|
||||
@@ -265,9 +265,9 @@ EXAMPLES = r'''
|
||||
cert_dest: /etc/httpd/ssl/sample.com.crt
|
||||
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
|
||||
chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
RETURN = """
|
||||
account_uri:
|
||||
description: ACME account URI.
|
||||
returned: success
|
||||
@@ -323,7 +323,7 @@ selected_chain:
|
||||
as concatenated PEM certificates.
|
||||
type: str
|
||||
returned: always
|
||||
'''
|
||||
"""
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
create_backend,
|
||||
@@ -340,29 +340,39 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_certificate=True)
|
||||
argument_spec.update_argspec(
|
||||
order_uri=dict(type='str', required=True),
|
||||
cert_dest=dict(type='path'),
|
||||
fullchain_dest=dict(type='path'),
|
||||
chain_dest=dict(type='path'),
|
||||
deactivate_authzs=dict(type='str', default='always', choices=['never', 'always', 'on_error', 'on_success']),
|
||||
retrieve_all_alternates=dict(type='bool', default=False),
|
||||
select_chain=dict(type='list', elements='dict', options=dict(
|
||||
test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']),
|
||||
issuer=dict(type='dict'),
|
||||
subject=dict(type='dict'),
|
||||
subject_key_identifier=dict(type='str'),
|
||||
authority_key_identifier=dict(type='str'),
|
||||
)),
|
||||
order_uri=dict(type="str", required=True),
|
||||
cert_dest=dict(type="path"),
|
||||
fullchain_dest=dict(type="path"),
|
||||
chain_dest=dict(type="path"),
|
||||
deactivate_authzs=dict(
|
||||
type="str",
|
||||
default="always",
|
||||
choices=["never", "always", "on_error", "on_success"],
|
||||
),
|
||||
retrieve_all_alternates=dict(type="bool", default=False),
|
||||
select_chain=dict(
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=dict(
|
||||
test_certificates=dict(
|
||||
type="str", default="all", choices=["first", "last", "all"]
|
||||
),
|
||||
issuer=dict(type="dict"),
|
||||
subject=dict(type="dict"),
|
||||
subject_key_identifier=dict(type="str"),
|
||||
authority_key_identifier=dict(type="str"),
|
||||
),
|
||||
),
|
||||
)
|
||||
module = argument_spec.create_ansible_module()
|
||||
if module.params['acme_version'] == 1:
|
||||
module.fail_json('The module does not support acme_version=1')
|
||||
if module.params["acme_version"] == 1:
|
||||
module.fail_json("The module does not support acme_version=1")
|
||||
|
||||
backend = create_backend(module, False)
|
||||
|
||||
try:
|
||||
client = ACMECertificateClient(module, backend)
|
||||
select_chain_matcher = client.parse_select_chain(module.params['select_chain'])
|
||||
select_chain_matcher = client.parse_select_chain(module.params["select_chain"])
|
||||
other = dict()
|
||||
done = False
|
||||
order = None
|
||||
@@ -370,9 +380,12 @@ def main():
|
||||
# Step 1: load order
|
||||
order = client.load_order()
|
||||
|
||||
download_all_chains = len(select_chain_matcher) > 0 or module.params['retrieve_all_alternates']
|
||||
download_all_chains = (
|
||||
len(select_chain_matcher) > 0
|
||||
or module.params["retrieve_all_alternates"]
|
||||
)
|
||||
changed = False
|
||||
if order.status == 'valid':
|
||||
if order.status == "valid":
|
||||
# Step 2 and 3: download certificate(s) and chain(s)
|
||||
cert, alternate_chains = client.download_certificate(
|
||||
order,
|
||||
@@ -395,34 +408,36 @@ def main():
|
||||
# Step 4: pick chain, write certificates, and provide return values
|
||||
if alternate_chains is not None:
|
||||
# Prepare return value for all alternate chains
|
||||
if module.params['retrieve_all_alternates']:
|
||||
if module.params["retrieve_all_alternates"]:
|
||||
all_chains = [cert.to_json()]
|
||||
for alt_chain in alternate_chains:
|
||||
all_chains.append(alt_chain.to_json())
|
||||
other['all_chains'] = all_chains
|
||||
other["all_chains"] = all_chains
|
||||
|
||||
# Try to select alternate chain depending on criteria
|
||||
if select_chain_matcher:
|
||||
matching_chain = client.find_matching_chain([cert] + alternate_chains, select_chain_matcher)
|
||||
matching_chain = client.find_matching_chain(
|
||||
[cert] + alternate_chains, select_chain_matcher
|
||||
)
|
||||
if matching_chain:
|
||||
cert = matching_chain
|
||||
else:
|
||||
module.debug('Found no matching alternative chain')
|
||||
module.debug("Found no matching alternative chain")
|
||||
|
||||
if client.write_cert_chain(
|
||||
cert,
|
||||
cert_dest=module.params['cert_dest'],
|
||||
fullchain_dest=module.params['fullchain_dest'],
|
||||
chain_dest=module.params['chain_dest'],
|
||||
cert_dest=module.params["cert_dest"],
|
||||
fullchain_dest=module.params["fullchain_dest"],
|
||||
chain_dest=module.params["chain_dest"],
|
||||
):
|
||||
changed = True
|
||||
|
||||
done = True
|
||||
finally:
|
||||
if (
|
||||
module.params['deactivate_authzs'] == 'always' or
|
||||
(module.params['deactivate_authzs'] == 'on_success' and done) or
|
||||
(module.params['deactivate_authzs'] == 'on_error' and not done)
|
||||
module.params["deactivate_authzs"] == "always"
|
||||
or (module.params["deactivate_authzs"] == "on_success" and done)
|
||||
or (module.params["deactivate_authzs"] == "on_error" and not done)
|
||||
):
|
||||
if order:
|
||||
client.deactivate_authzs(order)
|
||||
@@ -436,5 +451,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
module: acme_certificate_order_info
|
||||
author: Felix Fontein (@felixfontein)
|
||||
version_added: 2.24.0
|
||||
@@ -57,9 +57,9 @@ options:
|
||||
- The order URI provided by RV(community.crypto.acme_certificate_order_create#module:order_uri).
|
||||
type: str
|
||||
required: true
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
---
|
||||
- name: Create a challenge for sample.com using a account key from a variable
|
||||
community.crypto.acme_certificate_order_create:
|
||||
@@ -76,9 +76,9 @@ EXAMPLES = r'''
|
||||
- name: Show information
|
||||
ansible.builtin.debug:
|
||||
var: order_info
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
RETURN = """
|
||||
account_uri:
|
||||
description: ACME account URI.
|
||||
returned: success
|
||||
@@ -360,7 +360,7 @@ authorizations_by_status:
|
||||
type: list
|
||||
elements: str
|
||||
returned: always
|
||||
'''
|
||||
"""
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
create_backend,
|
||||
@@ -377,11 +377,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_certificate=False)
|
||||
argument_spec.update_argspec(
|
||||
order_uri=dict(type='str', required=True),
|
||||
order_uri=dict(type="str", required=True),
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
if module.params['acme_version'] == 1:
|
||||
module.fail_json('The module does not support acme_version=1')
|
||||
if module.params["acme_version"] == 1:
|
||||
module.fail_json("The module does not support acme_version=1")
|
||||
|
||||
backend = create_backend(module, False)
|
||||
|
||||
@@ -390,12 +390,12 @@ def main():
|
||||
order = client.load_order()
|
||||
authorizations_by_identifier = dict()
|
||||
authorizations_by_status = {
|
||||
'pending': [],
|
||||
'invalid': [],
|
||||
'valid': [],
|
||||
'revoked': [],
|
||||
'deactivated': [],
|
||||
'expired': [],
|
||||
"pending": [],
|
||||
"invalid": [],
|
||||
"valid": [],
|
||||
"revoked": [],
|
||||
"deactivated": [],
|
||||
"expired": [],
|
||||
}
|
||||
for identifier, authz in order.authorizations.items():
|
||||
authorizations_by_identifier[identifier] = authz.to_json()
|
||||
@@ -412,5 +412,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -11,7 +11,7 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = """
|
||||
module: acme_certificate_order_validate
|
||||
author: Felix Fontein (@felixfontein)
|
||||
version_added: 2.24.0
|
||||
@@ -95,9 +95,9 @@ options:
|
||||
concern."
|
||||
type: bool
|
||||
default: true
|
||||
'''
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
---
|
||||
### Example with HTTP-01 challenge ###
|
||||
|
||||
@@ -190,9 +190,9 @@ EXAMPLES = r'''
|
||||
cert_dest: /etc/httpd/ssl/sample.com.crt
|
||||
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
|
||||
chain_dest: /etc/httpd/ssl/sample.com-intermediate.crt
|
||||
'''
|
||||
"""
|
||||
|
||||
RETURN = '''
|
||||
RETURN = """
|
||||
account_uri:
|
||||
description: ACME account URI.
|
||||
returned: success
|
||||
@@ -235,7 +235,7 @@ validating_challenges:
|
||||
- The URL of the challenge object.
|
||||
type: str
|
||||
returned: always
|
||||
'''
|
||||
"""
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
|
||||
create_backend,
|
||||
@@ -252,13 +252,13 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_certificate=False)
|
||||
argument_spec.update_argspec(
|
||||
order_uri=dict(type='str', required=True),
|
||||
challenge=dict(type='str', choices=['http-01', 'dns-01', 'tls-alpn-01']),
|
||||
deactivate_authzs=dict(type='bool', default=True),
|
||||
order_uri=dict(type="str", required=True),
|
||||
challenge=dict(type="str", choices=["http-01", "dns-01", "tls-alpn-01"]),
|
||||
deactivate_authzs=dict(type="bool", default=True),
|
||||
)
|
||||
module = argument_spec.create_ansible_module()
|
||||
if module.params['acme_version'] == 1:
|
||||
module.fail_json('The module does not support acme_version=1')
|
||||
if module.params["acme_version"] == 1:
|
||||
module.fail_json("The module does not support acme_version=1")
|
||||
|
||||
backend = create_backend(module, False)
|
||||
|
||||
@@ -277,34 +277,43 @@ def main():
|
||||
# Step 3: figure out challenges to use
|
||||
challenges = {}
|
||||
for authz in pending_authzs:
|
||||
challenges[authz.combined_identifier] = module.params['challenge']
|
||||
challenges[authz.combined_identifier] = module.params["challenge"]
|
||||
|
||||
missing_challenge_authzs = [k for k, v in challenges.items() if v is None]
|
||||
if missing_challenge_authzs:
|
||||
raise ModuleFailException(
|
||||
'The challenge parameter must be supplied if there are pending authorizations.'
|
||||
' The following authorizations are pending: {missing_challenge_authzs}'.format(
|
||||
missing_challenge_authzs=', '.join(sorted(missing_challenge_authzs)),
|
||||
"The challenge parameter must be supplied if there are pending authorizations."
|
||||
" The following authorizations are pending: {missing_challenge_authzs}".format(
|
||||
missing_challenge_authzs=", ".join(
|
||||
sorted(missing_challenge_authzs)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
bad_challenge_authzs = [
|
||||
authz.combined_identifier for authz in pending_authzs
|
||||
authz.combined_identifier
|
||||
for authz in pending_authzs
|
||||
if authz.find_challenge(challenges[authz.combined_identifier]) is None
|
||||
]
|
||||
if bad_challenge_authzs:
|
||||
raise ModuleFailException(
|
||||
'The following authorizations do not support the selected challenges: {authz_challenges_pairs}'.format(
|
||||
authz_challenges_pairs=', '.join(sorted(
|
||||
'{authz} with {challenge}'.format(authz=authz, challenge=challenges[authz])
|
||||
for authz in bad_challenge_authzs
|
||||
)),
|
||||
"The following authorizations do not support the selected challenges: {authz_challenges_pairs}".format(
|
||||
authz_challenges_pairs=", ".join(
|
||||
sorted(
|
||||
"{authz} with {challenge}".format(
|
||||
authz=authz, challenge=challenges[authz]
|
||||
)
|
||||
for authz in bad_challenge_authzs
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
really_pending_authzs = [
|
||||
authz for authz in pending_authzs
|
||||
if authz.find_challenge(challenges[authz.combined_identifier]).status == 'pending'
|
||||
authz
|
||||
for authz in pending_authzs
|
||||
if authz.find_challenge(challenges[authz.combined_identifier]).status
|
||||
== "pending"
|
||||
]
|
||||
|
||||
# Step 4: validate pending authorizations
|
||||
@@ -316,7 +325,7 @@ def main():
|
||||
|
||||
done = True
|
||||
finally:
|
||||
if order and module.params['deactivate_authzs'] and not done:
|
||||
if order and module.params["deactivate_authzs"] and not done:
|
||||
client.deactivate_authzs(order)
|
||||
module.exit_json(
|
||||
changed=len(authzs_with_challenges_to_wait_for) > 0,
|
||||
@@ -336,5 +345,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -183,110 +183,144 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||
def main():
|
||||
argument_spec = create_default_argspec(with_account=False)
|
||||
argument_spec.update_argspec(
|
||||
certificate_path=dict(type='path'),
|
||||
certificate_content=dict(type='str'),
|
||||
use_ari=dict(type='bool', default=True),
|
||||
ari_algorithm=dict(type='str', choices=['standard', 'start'], default='standard'),
|
||||
remaining_days=dict(type='int'),
|
||||
remaining_percentage=dict(type='float'),
|
||||
now=dict(type='str'),
|
||||
treat_parsing_error_as_non_existing=dict(type='bool', default=False),
|
||||
certificate_path=dict(type="path"),
|
||||
certificate_content=dict(type="str"),
|
||||
use_ari=dict(type="bool", default=True),
|
||||
ari_algorithm=dict(
|
||||
type="str", choices=["standard", "start"], default="standard"
|
||||
),
|
||||
remaining_days=dict(type="int"),
|
||||
remaining_percentage=dict(type="float"),
|
||||
now=dict(type="str"),
|
||||
treat_parsing_error_as_non_existing=dict(type="bool", default=False),
|
||||
)
|
||||
argument_spec.update(
|
||||
mutually_exclusive=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
),
|
||||
mutually_exclusive=(["certificate_path", "certificate_content"],),
|
||||
)
|
||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||
backend = create_backend(module, True)
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
msg='The certificate is still valid and no condition was reached',
|
||||
msg="The certificate is still valid and no condition was reached",
|
||||
exists=False,
|
||||
parsable=False,
|
||||
supports_ari=False,
|
||||
)
|
||||
|
||||
def complete(should_renew, **kwargs):
|
||||
result['should_renew'] = should_renew
|
||||
result["should_renew"] = should_renew
|
||||
result.update(kwargs)
|
||||
module.exit_json(**result)
|
||||
|
||||
if not module.params['certificate_path'] and not module.params['certificate_content']:
|
||||
complete(True, msg='No certificate was specified')
|
||||
if (
|
||||
not module.params["certificate_path"]
|
||||
and not module.params["certificate_content"]
|
||||
):
|
||||
complete(True, msg="No certificate was specified")
|
||||
|
||||
if module.params['certificate_path'] is not None:
|
||||
if not os.path.exists(module.params['certificate_path']):
|
||||
complete(True, msg='The certificate file does not exist')
|
||||
if module.params['treat_parsing_error_as_non_existing']:
|
||||
if module.params["certificate_path"] is not None:
|
||||
if not os.path.exists(module.params["certificate_path"]):
|
||||
complete(True, msg="The certificate file does not exist")
|
||||
if module.params["treat_parsing_error_as_non_existing"]:
|
||||
try:
|
||||
read_file(module.params['certificate_path'])
|
||||
read_file(module.params["certificate_path"])
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
result['exists'] = True
|
||||
result["exists"] = True
|
||||
try:
|
||||
cert_info = backend.get_cert_information(
|
||||
cert_filename=module.params['certificate_path'],
|
||||
cert_content=module.params['certificate_content'],
|
||||
cert_filename=module.params["certificate_path"],
|
||||
cert_content=module.params["certificate_content"],
|
||||
)
|
||||
except ModuleFailException as e:
|
||||
if module.params['treat_parsing_error_as_non_existing']:
|
||||
complete(True, msg='Certificate cannot be parsed: {0}'.format(e.msg))
|
||||
if module.params["treat_parsing_error_as_non_existing"]:
|
||||
complete(True, msg="Certificate cannot be parsed: {0}".format(e.msg))
|
||||
e.do_fail(module)
|
||||
|
||||
result['parsable'] = True
|
||||
result["parsable"] = True
|
||||
try:
|
||||
cert_id = compute_cert_id(backend, cert_info=cert_info, none_if_required_information_is_missing=True)
|
||||
cert_id = compute_cert_id(
|
||||
backend, cert_info=cert_info, none_if_required_information_is_missing=True
|
||||
)
|
||||
if cert_id is not None:
|
||||
result['cert_id'] = cert_id
|
||||
result["cert_id"] = cert_id
|
||||
|
||||
if module.params['now']:
|
||||
now = backend.parse_module_parameter(module.params['now'], 'now')
|
||||
if module.params["now"]:
|
||||
now = backend.parse_module_parameter(module.params["now"], "now")
|
||||
else:
|
||||
now = backend.get_now()
|
||||
|
||||
if now >= cert_info.not_valid_after:
|
||||
complete(True, msg='The certificate has already expired')
|
||||
complete(True, msg="The certificate has already expired")
|
||||
|
||||
client = ACMEClient(module, backend)
|
||||
if cert_id is not None and module.params['use_ari'] and client.directory.has_renewal_info_endpoint():
|
||||
if (
|
||||
cert_id is not None
|
||||
and module.params["use_ari"]
|
||||
and client.directory.has_renewal_info_endpoint()
|
||||
):
|
||||
renewal_info = client.get_renewal_info(cert_id=cert_id)
|
||||
window_start = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['start'])
|
||||
window_end = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['end'])
|
||||
msg_append = ''
|
||||
if 'explanationURL' in renewal_info:
|
||||
msg_append = '. Information on renewal interval: {0}'.format(renewal_info['explanationURL'])
|
||||
result['supports_ari'] = True
|
||||
window_start = backend.parse_acme_timestamp(
|
||||
renewal_info["suggestedWindow"]["start"]
|
||||
)
|
||||
window_end = backend.parse_acme_timestamp(
|
||||
renewal_info["suggestedWindow"]["end"]
|
||||
)
|
||||
msg_append = ""
|
||||
if "explanationURL" in renewal_info:
|
||||
msg_append = ". Information on renewal interval: {0}".format(
|
||||
renewal_info["explanationURL"]
|
||||
)
|
||||
result["supports_ari"] = True
|
||||
if now > window_end:
|
||||
complete(True, msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append))
|
||||
if module.params['ari_algorithm'] == 'start':
|
||||
complete(
|
||||
True,
|
||||
msg="The suggested renewal interval provided by ARI is in the past{0}".format(
|
||||
msg_append
|
||||
),
|
||||
)
|
||||
if module.params["ari_algorithm"] == "start":
|
||||
if now > window_start:
|
||||
complete(True, msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append))
|
||||
complete(
|
||||
True,
|
||||
msg="The suggested renewal interval provided by ARI has begun{0}".format(
|
||||
msg_append
|
||||
),
|
||||
)
|
||||
else:
|
||||
random_time = backend.interpolate_timestamp(window_start, window_end, random.random())
|
||||
random_time = backend.interpolate_timestamp(
|
||||
window_start, window_end, random.random()
|
||||
)
|
||||
if now > random_time:
|
||||
complete(
|
||||
True,
|
||||
msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format(
|
||||
msg="The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}".format(
|
||||
random_time,
|
||||
msg_append,
|
||||
),
|
||||
)
|
||||
|
||||
if module.params['remaining_days'] is not None:
|
||||
if module.params["remaining_days"] is not None:
|
||||
remaining_days = (cert_info.not_valid_after - now).days
|
||||
if remaining_days < module.params['remaining_days']:
|
||||
complete(True, msg='The certificate expires in {0} days'.format(remaining_days))
|
||||
if remaining_days < module.params["remaining_days"]:
|
||||
complete(
|
||||
True,
|
||||
msg="The certificate expires in {0} days".format(remaining_days),
|
||||
)
|
||||
|
||||
if module.params['remaining_percentage'] is not None:
|
||||
timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage'])
|
||||
if module.params["remaining_percentage"] is not None:
|
||||
timestamp = backend.interpolate_timestamp(
|
||||
cert_info.not_valid_before,
|
||||
cert_info.not_valid_after,
|
||||
1 - module.params["remaining_percentage"],
|
||||
)
|
||||
if timestamp < now:
|
||||
complete(
|
||||
True,
|
||||
msg="The remaining percentage {0}% of the certificate's lifespan was reached on {1}".format(
|
||||
module.params['remaining_percentage'] * 100,
|
||||
module.params["remaining_percentage"] * 100,
|
||||
timestamp,
|
||||
),
|
||||
)
|
||||
@@ -296,5 +330,5 @@ def main():
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -137,18 +137,28 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
|
||||
def main():
|
||||
argument_spec = create_default_argspec(require_account_key=False)
|
||||
argument_spec.update_argspec(
|
||||
private_key_src=dict(type='path'),
|
||||
private_key_content=dict(type='str', no_log=True),
|
||||
private_key_passphrase=dict(type='str', no_log=True),
|
||||
certificate=dict(type='path', required=True),
|
||||
revoke_reason=dict(type='int'),
|
||||
private_key_src=dict(type="path"),
|
||||
private_key_content=dict(type="str", no_log=True),
|
||||
private_key_passphrase=dict(type="str", no_log=True),
|
||||
certificate=dict(type="path", required=True),
|
||||
revoke_reason=dict(type="int"),
|
||||
)
|
||||
argument_spec.update(
|
||||
required_one_of=(
|
||||
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
|
||||
[
|
||||
"account_key_src",
|
||||
"account_key_content",
|
||||
"private_key_src",
|
||||
"private_key_content",
|
||||
],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
|
||||
[
|
||||
"account_key_src",
|
||||
"account_key_content",
|
||||
"private_key_src",
|
||||
"private_key_content",
|
||||
],
|
||||
),
|
||||
)
|
||||
module = argument_spec.create_ansible_module()
|
||||
@@ -158,70 +168,86 @@ def main():
|
||||
client = ACMEClient(module, backend)
|
||||
account = ACMEAccount(client)
|
||||
# Load certificate
|
||||
certificate = pem_to_der(module.params.get('certificate'))
|
||||
certificate = pem_to_der(module.params.get("certificate"))
|
||||
certificate = nopad_b64(certificate)
|
||||
# Construct payload
|
||||
payload = {
|
||||
'certificate': certificate
|
||||
}
|
||||
if module.params.get('revoke_reason') is not None:
|
||||
payload['reason'] = module.params.get('revoke_reason')
|
||||
payload = {"certificate": certificate}
|
||||
if module.params.get("revoke_reason") is not None:
|
||||
payload["reason"] = module.params.get("revoke_reason")
|
||||
# Determine endpoint
|
||||
if module.params.get('acme_version') == 1:
|
||||
endpoint = client.directory['revoke-cert']
|
||||
payload['resource'] = 'revoke-cert'
|
||||
if module.params.get("acme_version") == 1:
|
||||
endpoint = client.directory["revoke-cert"]
|
||||
payload["resource"] = "revoke-cert"
|
||||
else:
|
||||
endpoint = client.directory['revokeCert']
|
||||
endpoint = client.directory["revokeCert"]
|
||||
# Get hold of private key (if available) and make sure it comes from disk
|
||||
private_key = module.params.get('private_key_src')
|
||||
private_key_content = module.params.get('private_key_content')
|
||||
private_key = module.params.get("private_key_src")
|
||||
private_key_content = module.params.get("private_key_content")
|
||||
# Revoke certificate
|
||||
if private_key or private_key_content:
|
||||
passphrase = module.params['private_key_passphrase']
|
||||
passphrase = module.params["private_key_passphrase"]
|
||||
# Step 1: load and parse private key
|
||||
try:
|
||||
private_key_data = client.parse_key(private_key, private_key_content, passphrase=passphrase)
|
||||
private_key_data = client.parse_key(
|
||||
private_key, private_key_content, passphrase=passphrase
|
||||
)
|
||||
except KeyParsingError as e:
|
||||
raise ModuleFailException("Error while parsing private key: {msg}".format(msg=e.msg))
|
||||
raise ModuleFailException(
|
||||
"Error while parsing private key: {msg}".format(msg=e.msg)
|
||||
)
|
||||
# Step 2: sign revokation request with private key
|
||||
jws_header = {
|
||||
"alg": private_key_data['alg'],
|
||||
"jwk": private_key_data['jwk'],
|
||||
"alg": private_key_data["alg"],
|
||||
"jwk": private_key_data["jwk"],
|
||||
}
|
||||
result, info = client.send_signed_request(
|
||||
endpoint, payload, key_data=private_key_data, jws_header=jws_header, fail_on_error=False)
|
||||
endpoint,
|
||||
payload,
|
||||
key_data=private_key_data,
|
||||
jws_header=jws_header,
|
||||
fail_on_error=False,
|
||||
)
|
||||
else:
|
||||
# Step 1: get hold of account URI
|
||||
created, account_data = account.setup_account(allow_creation=False)
|
||||
if created:
|
||||
raise AssertionError('Unwanted account creation')
|
||||
raise AssertionError("Unwanted account creation")
|
||||
if account_data is None:
|
||||
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||
raise ModuleFailException(
|
||||
msg="Account does not exist or is deactivated."
|
||||
)
|
||||
# Step 2: sign revokation request with account key
|
||||
result, info = client.send_signed_request(endpoint, payload, fail_on_error=False)
|
||||
if info['status'] != 200:
|
||||
result, info = client.send_signed_request(
|
||||
endpoint, payload, fail_on_error=False
|
||||
)
|
||||
if info["status"] != 200:
|
||||
already_revoked = False
|
||||
# Standardized error from draft 14 on (https://tools.ietf.org/html/rfc8555#section-7.6)
|
||||
if result.get('type') == 'urn:ietf:params:acme:error:alreadyRevoked':
|
||||
if result.get("type") == "urn:ietf:params:acme:error:alreadyRevoked":
|
||||
already_revoked = True
|
||||
else:
|
||||
# Hack for Boulder errors
|
||||
if module.params.get('acme_version') == 1:
|
||||
error_type = 'urn:acme:error:malformed'
|
||||
if module.params.get("acme_version") == 1:
|
||||
error_type = "urn:acme:error:malformed"
|
||||
else:
|
||||
error_type = 'urn:ietf:params:acme:error:malformed'
|
||||
if result.get('type') == error_type and result.get('detail') == 'Certificate already revoked':
|
||||
error_type = "urn:ietf:params:acme:error:malformed"
|
||||
if (
|
||||
result.get("type") == error_type
|
||||
and result.get("detail") == "Certificate already revoked"
|
||||
):
|
||||
# Fallback: boulder returns this in case the certificate was already revoked.
|
||||
already_revoked = True
|
||||
# If we know the certificate was already revoked, we do not fail,
|
||||
# but successfully terminate while indicating no change
|
||||
if already_revoked:
|
||||
module.exit_json(changed=False)
|
||||
raise ACMEProtocolException(module, 'Failed to revoke certificate', info=info, content_json=result)
|
||||
raise ACMEProtocolException(
|
||||
module, "Failed to revoke certificate", info=info, content_json=result
|
||||
)
|
||||
module.exit_json(changed=True)
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -190,7 +190,8 @@ try:
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
import cryptography.x509
|
||||
import cryptography.x509.oid
|
||||
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3'))
|
||||
|
||||
HAS_CRYPTOGRAPHY = LooseVersion(cryptography.__version__) >= LooseVersion("1.3")
|
||||
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -199,121 +200,137 @@ except ImportError:
|
||||
|
||||
# Convert byte string to ASN1 encoded octet string
|
||||
if sys.version_info[0] >= 3:
|
||||
|
||||
def encode_octet_string(octet_string):
|
||||
if len(octet_string) >= 128:
|
||||
raise ModuleFailException('Cannot handle octet strings with more than 128 bytes')
|
||||
raise ModuleFailException(
|
||||
"Cannot handle octet strings with more than 128 bytes"
|
||||
)
|
||||
return bytes([0x4, len(octet_string)]) + octet_string
|
||||
|
||||
else:
|
||||
|
||||
def encode_octet_string(octet_string):
|
||||
if len(octet_string) >= 128:
|
||||
raise ModuleFailException('Cannot handle octet strings with more than 128 bytes')
|
||||
return b'\x04' + chr(len(octet_string)) + octet_string
|
||||
raise ModuleFailException(
|
||||
"Cannot handle octet strings with more than 128 bytes"
|
||||
)
|
||||
return b"\x04" + chr(len(octet_string)) + octet_string
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
challenge=dict(type='str', required=True, choices=['tls-alpn-01']),
|
||||
challenge_data=dict(type='dict', required=True),
|
||||
private_key_src=dict(type='path'),
|
||||
private_key_content=dict(type='str', no_log=True),
|
||||
private_key_passphrase=dict(type='str', no_log=True),
|
||||
),
|
||||
required_one_of=(
|
||||
['private_key_src', 'private_key_content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['private_key_src', 'private_key_content'],
|
||||
challenge=dict(type="str", required=True, choices=["tls-alpn-01"]),
|
||||
challenge_data=dict(type="dict", required=True),
|
||||
private_key_src=dict(type="path"),
|
||||
private_key_content=dict(type="str", no_log=True),
|
||||
private_key_passphrase=dict(type="str", no_log=True),
|
||||
),
|
||||
required_one_of=(["private_key_src", "private_key_content"],),
|
||||
mutually_exclusive=(["private_key_src", "private_key_content"],),
|
||||
)
|
||||
if not HAS_CRYPTOGRAPHY:
|
||||
# Some callbacks die when exception is provided with value None
|
||||
if CRYPTOGRAPHY_IMP_ERR:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= 1.3'), exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= 1.3'))
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("cryptography >= 1.3"),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
module.fail_json(msg=missing_required_lib("cryptography >= 1.3"))
|
||||
|
||||
try:
|
||||
# Get parameters
|
||||
challenge = module.params['challenge']
|
||||
challenge_data = module.params['challenge_data']
|
||||
challenge = module.params["challenge"]
|
||||
challenge_data = module.params["challenge_data"]
|
||||
|
||||
# Get hold of private key
|
||||
private_key_content = module.params.get('private_key_content')
|
||||
private_key_passphrase = module.params.get('private_key_passphrase')
|
||||
private_key_content = module.params.get("private_key_content")
|
||||
private_key_passphrase = module.params.get("private_key_passphrase")
|
||||
if private_key_content is None:
|
||||
private_key_content = read_file(module.params['private_key_src'])
|
||||
private_key_content = read_file(module.params["private_key_src"])
|
||||
else:
|
||||
private_key_content = to_bytes(private_key_content)
|
||||
try:
|
||||
private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
private_key_content,
|
||||
password=to_bytes(private_key_passphrase) if private_key_passphrase is not None else None,
|
||||
backend=_cryptography_backend)
|
||||
private_key = (
|
||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||
private_key_content,
|
||||
password=(
|
||||
to_bytes(private_key_passphrase)
|
||||
if private_key_passphrase is not None
|
||||
else None
|
||||
),
|
||||
backend=_cryptography_backend,
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
raise ModuleFailException('Error while loading private key: {0}'.format(e))
|
||||
raise ModuleFailException("Error while loading private key: {0}".format(e))
|
||||
|
||||
# Some common attributes
|
||||
domain = to_text(challenge_data['resource'])
|
||||
identifier_type, identifier = to_text(challenge_data.get('resource_original', 'dns:' + challenge_data['resource'])).split(':', 1)
|
||||
domain = to_text(challenge_data["resource"])
|
||||
identifier_type, identifier = to_text(
|
||||
challenge_data.get("resource_original", "dns:" + challenge_data["resource"])
|
||||
).split(":", 1)
|
||||
subject = issuer = cryptography.x509.Name([])
|
||||
now = get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||
not_valid_before = now
|
||||
not_valid_after = now + datetime.timedelta(days=10)
|
||||
if identifier_type == 'dns':
|
||||
if identifier_type == "dns":
|
||||
san = cryptography.x509.DNSName(identifier)
|
||||
elif identifier_type == 'ip':
|
||||
elif identifier_type == "ip":
|
||||
san = cryptography.x509.IPAddress(ipaddress.ip_address(identifier))
|
||||
else:
|
||||
raise ModuleFailException('Unsupported identifier type "{0}"'.format(identifier_type))
|
||||
raise ModuleFailException(
|
||||
'Unsupported identifier type "{0}"'.format(identifier_type)
|
||||
)
|
||||
|
||||
# Generate regular self-signed certificate
|
||||
cert_builder = cryptography.x509.CertificateBuilder().subject_name(
|
||||
subject
|
||||
).issuer_name(
|
||||
issuer
|
||||
).public_key(
|
||||
private_key.public_key()
|
||||
).serial_number(
|
||||
cryptography.x509.random_serial_number()
|
||||
).add_extension(
|
||||
cryptography.x509.SubjectAlternativeName([san]),
|
||||
critical=False,
|
||||
cert_builder = (
|
||||
cryptography.x509.CertificateBuilder()
|
||||
.subject_name(subject)
|
||||
.issuer_name(issuer)
|
||||
.public_key(private_key.public_key())
|
||||
.serial_number(cryptography.x509.random_serial_number())
|
||||
.add_extension(
|
||||
cryptography.x509.SubjectAlternativeName([san]),
|
||||
critical=False,
|
||||
)
|
||||
)
|
||||
cert_builder = set_not_valid_before(cert_builder, not_valid_before)
|
||||
cert_builder = set_not_valid_after(cert_builder, not_valid_after)
|
||||
regular_certificate = cert_builder.sign(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.hashes.SHA256(),
|
||||
_cryptography_backend
|
||||
_cryptography_backend,
|
||||
)
|
||||
|
||||
# Process challenge
|
||||
if challenge == 'tls-alpn-01':
|
||||
value = base64.b64decode(challenge_data['resource_value'])
|
||||
cert_builder = cryptography.x509.CertificateBuilder().subject_name(
|
||||
subject
|
||||
).issuer_name(
|
||||
issuer
|
||||
).public_key(
|
||||
private_key.public_key()
|
||||
).serial_number(
|
||||
cryptography.x509.random_serial_number()
|
||||
).add_extension(
|
||||
cryptography.x509.SubjectAlternativeName([san]),
|
||||
critical=False,
|
||||
).add_extension(
|
||||
cryptography.x509.UnrecognizedExtension(
|
||||
cryptography.x509.ObjectIdentifier("1.3.6.1.5.5.7.1.31"),
|
||||
encode_octet_string(value),
|
||||
),
|
||||
critical=True,
|
||||
if challenge == "tls-alpn-01":
|
||||
value = base64.b64decode(challenge_data["resource_value"])
|
||||
cert_builder = (
|
||||
cryptography.x509.CertificateBuilder()
|
||||
.subject_name(subject)
|
||||
.issuer_name(issuer)
|
||||
.public_key(private_key.public_key())
|
||||
.serial_number(cryptography.x509.random_serial_number())
|
||||
.add_extension(
|
||||
cryptography.x509.SubjectAlternativeName([san]),
|
||||
critical=False,
|
||||
)
|
||||
.add_extension(
|
||||
cryptography.x509.UnrecognizedExtension(
|
||||
cryptography.x509.ObjectIdentifier("1.3.6.1.5.5.7.1.31"),
|
||||
encode_octet_string(value),
|
||||
),
|
||||
critical=True,
|
||||
)
|
||||
)
|
||||
cert_builder = set_not_valid_before(cert_builder, not_valid_before)
|
||||
cert_builder = set_not_valid_after(cert_builder, not_valid_after)
|
||||
challenge_certificate = cert_builder.sign(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.hashes.SHA256(),
|
||||
_cryptography_backend
|
||||
_cryptography_backend,
|
||||
)
|
||||
|
||||
module.exit_json(
|
||||
@@ -321,12 +338,16 @@ def main():
|
||||
domain=domain,
|
||||
identifier_type=identifier_type,
|
||||
identifier=identifier,
|
||||
challenge_certificate=challenge_certificate.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM),
|
||||
regular_certificate=regular_certificate.public_bytes(cryptography.hazmat.primitives.serialization.Encoding.PEM)
|
||||
challenge_certificate=challenge_certificate.public_bytes(
|
||||
cryptography.hazmat.primitives.serialization.Encoding.PEM
|
||||
),
|
||||
regular_certificate=regular_certificate.public_bytes(
|
||||
cryptography.hazmat.primitives.serialization.Encoding.PEM
|
||||
),
|
||||
)
|
||||
except ModuleFailException as e:
|
||||
e.do_fail(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -252,17 +252,19 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
def main():
|
||||
argument_spec = create_default_argspec(require_account_key=False)
|
||||
argument_spec.update_argspec(
|
||||
url=dict(type='str'),
|
||||
method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'),
|
||||
content=dict(type='str'),
|
||||
fail_on_acme_error=dict(type='bool', default=True),
|
||||
url=dict(type="str"),
|
||||
method=dict(
|
||||
type="str", choices=["get", "post", "directory-only"], default="get"
|
||||
),
|
||||
content=dict(type="str"),
|
||||
fail_on_acme_error=dict(type="bool", default=True),
|
||||
)
|
||||
argument_spec.update(
|
||||
required_if=(
|
||||
['method', 'get', ['url']],
|
||||
['method', 'post', ['url', 'content']],
|
||||
['method', 'get', ['account_key_src', 'account_key_content'], True],
|
||||
['method', 'post', ['account_key_src', 'account_key_content'], True],
|
||||
["method", "get", ["url"]],
|
||||
["method", "post", ["url", "content"]],
|
||||
["method", "get", ["account_key_src", "account_key_content"], True],
|
||||
["method", "post", ["account_key_src", "account_key_content"], True],
|
||||
),
|
||||
)
|
||||
module = argument_spec.create_ansible_module()
|
||||
@@ -273,31 +275,40 @@ def main():
|
||||
try:
|
||||
# Get hold of ACMEClient and ACMEAccount objects (includes directory)
|
||||
client = ACMEClient(module, backend)
|
||||
method = module.params['method']
|
||||
result['directory'] = client.directory.directory
|
||||
method = module.params["method"]
|
||||
result["directory"] = client.directory.directory
|
||||
# Do we have to do more requests?
|
||||
if method != 'directory-only':
|
||||
url = module.params['url']
|
||||
fail_on_acme_error = module.params['fail_on_acme_error']
|
||||
if method != "directory-only":
|
||||
url = module.params["url"]
|
||||
fail_on_acme_error = module.params["fail_on_acme_error"]
|
||||
# Do request
|
||||
if method == 'get':
|
||||
data, info = client.get_request(url, parse_json_result=False, fail_on_error=False)
|
||||
elif method == 'post':
|
||||
if method == "get":
|
||||
data, info = client.get_request(
|
||||
url, parse_json_result=False, fail_on_error=False
|
||||
)
|
||||
elif method == "post":
|
||||
changed = True # only POSTs can change
|
||||
data, info = client.send_signed_request(
|
||||
url, to_bytes(module.params['content']), parse_json_result=False, encode_payload=False, fail_on_error=False)
|
||||
url,
|
||||
to_bytes(module.params["content"]),
|
||||
parse_json_result=False,
|
||||
encode_payload=False,
|
||||
fail_on_error=False,
|
||||
)
|
||||
# Update results
|
||||
result.update(dict(
|
||||
headers=info,
|
||||
output_text=to_native(data),
|
||||
))
|
||||
result.update(
|
||||
dict(
|
||||
headers=info,
|
||||
output_text=to_native(data),
|
||||
)
|
||||
)
|
||||
# See if we can parse the result as JSON
|
||||
try:
|
||||
result['output_json'] = module.from_json(to_text(data))
|
||||
result["output_json"] = module.from_json(to_text(data))
|
||||
except Exception:
|
||||
pass
|
||||
# Fail if error was returned
|
||||
if fail_on_acme_error and info['status'] >= 400:
|
||||
if fail_on_acme_error and info["status"] >= 400:
|
||||
raise ACMEProtocolException(module, info=info, content=data)
|
||||
# Done!
|
||||
module.exit_json(changed=changed, **result)
|
||||
@@ -305,5 +316,5 @@ def main():
|
||||
e.do_fail(module, **result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -156,7 +156,8 @@ try:
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
import cryptography.x509
|
||||
import cryptography.x509.oid
|
||||
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5'))
|
||||
|
||||
HAS_CRYPTOGRAPHY = LooseVersion(cryptography.__version__) >= LooseVersion("1.5")
|
||||
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -164,44 +165,55 @@ except ImportError:
|
||||
|
||||
|
||||
class Certificate(object):
|
||||
'''
|
||||
"""
|
||||
Stores PEM with parsed certificate.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, pem, cert):
|
||||
if not (pem.endswith('\n') or pem.endswith('\r')):
|
||||
pem = pem + '\n'
|
||||
if not (pem.endswith("\n") or pem.endswith("\r")):
|
||||
pem = pem + "\n"
|
||||
self.pem = pem
|
||||
self.cert = cert
|
||||
|
||||
|
||||
def is_parent(module, cert, potential_parent):
|
||||
'''
|
||||
"""
|
||||
Tests whether the given certificate has been issued by the potential parent certificate.
|
||||
'''
|
||||
"""
|
||||
# Check issuer
|
||||
if cert.cert.issuer != potential_parent.cert.subject:
|
||||
return False
|
||||
# Check signature
|
||||
public_key = potential_parent.cert.public_key()
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
|
||||
if isinstance(
|
||||
public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey
|
||||
):
|
||||
public_key.verify(
|
||||
cert.cert.signature,
|
||||
cert.cert.tbs_certificate_bytes,
|
||||
cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15(),
|
||||
cert.cert.signature_hash_algorithm
|
||||
cert.cert.signature_hash_algorithm,
|
||||
)
|
||||
elif isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
|
||||
elif isinstance(
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey,
|
||||
):
|
||||
public_key.verify(
|
||||
cert.cert.signature,
|
||||
cert.cert.tbs_certificate_bytes,
|
||||
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(cert.cert.signature_hash_algorithm),
|
||||
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(
|
||||
cert.cert.signature_hash_algorithm
|
||||
),
|
||||
)
|
||||
elif CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance(
|
||||
public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey,
|
||||
):
|
||||
public_key.verify(cert.cert.signature, cert.cert.tbs_certificate_bytes)
|
||||
elif CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance(
|
||||
public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
|
||||
public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey
|
||||
):
|
||||
public_key.verify(cert.cert.signature, cert.cert.tbs_certificate_bytes)
|
||||
else:
|
||||
# Unknown public key type
|
||||
@@ -211,24 +223,30 @@ def is_parent(module, cert, potential_parent):
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
return False
|
||||
except cryptography.exceptions.UnsupportedAlgorithm:
|
||||
module.warn('Unsupported algorithm "{0}"'.format(cert.cert.signature_hash_algorithm))
|
||||
module.warn(
|
||||
'Unsupported algorithm "{0}"'.format(cert.cert.signature_hash_algorithm)
|
||||
)
|
||||
return False
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Unknown error on signature validation: {0}'.format(e))
|
||||
module.fail_json(msg="Unknown error on signature validation: {0}".format(e))
|
||||
|
||||
|
||||
def parse_PEM_list(module, text, source, fail_on_error=True):
|
||||
'''
|
||||
"""
|
||||
Parse concatenated PEM certificates. Return list of ``Certificate`` objects.
|
||||
'''
|
||||
"""
|
||||
result = []
|
||||
for cert_pem in split_pem_list(text):
|
||||
# Try to load PEM certificate
|
||||
try:
|
||||
cert = cryptography.x509.load_pem_x509_certificate(to_bytes(cert_pem), _cryptography_backend)
|
||||
cert = cryptography.x509.load_pem_x509_certificate(
|
||||
to_bytes(cert_pem), _cryptography_backend
|
||||
)
|
||||
result.append(Certificate(cert_pem, cert))
|
||||
except Exception as e:
|
||||
msg = 'Cannot parse certificate #{0} from {1}: {2}'.format(len(result) + 1, source, e)
|
||||
msg = "Cannot parse certificate #{0} from {1}: {2}".format(
|
||||
len(result) + 1, source, e
|
||||
)
|
||||
if fail_on_error:
|
||||
module.fail_json(msg=msg)
|
||||
else:
|
||||
@@ -237,14 +255,19 @@ def parse_PEM_list(module, text, source, fail_on_error=True):
|
||||
|
||||
|
||||
def load_PEM_list(module, path, fail_on_error=True):
|
||||
'''
|
||||
"""
|
||||
Load concatenated PEM certificates from file. Return list of ``Certificate`` objects.
|
||||
'''
|
||||
"""
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
return parse_PEM_list(module, f.read().decode('utf-8'), source=path, fail_on_error=fail_on_error)
|
||||
return parse_PEM_list(
|
||||
module,
|
||||
f.read().decode("utf-8"),
|
||||
source=path,
|
||||
fail_on_error=fail_on_error,
|
||||
)
|
||||
except Exception as e:
|
||||
msg = 'Cannot read certificate file {0}: {1}'.format(path, e)
|
||||
msg = "Cannot read certificate file {0}: {1}".format(path, e)
|
||||
if fail_on_error:
|
||||
module.fail_json(msg=msg)
|
||||
else:
|
||||
@@ -253,9 +276,9 @@ def load_PEM_list(module, path, fail_on_error=True):
|
||||
|
||||
|
||||
class CertificateSet(object):
|
||||
'''
|
||||
"""
|
||||
Stores a set of certificates. Allows to search for parent (issuer of a certificate).
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
@@ -273,10 +296,10 @@ class CertificateSet(object):
|
||||
self.certificate_by_cert[cert.cert] = cert
|
||||
|
||||
def load(self, path):
|
||||
'''
|
||||
"""
|
||||
Load lists of PEM certificates from a file or a directory.
|
||||
'''
|
||||
b_path = to_bytes(path, errors='surrogate_or_strict')
|
||||
"""
|
||||
b_path = to_bytes(path, errors="surrogate_or_strict")
|
||||
if os.path.isdir(b_path):
|
||||
for directory, dummy, files in os.walk(b_path, followlinks=True):
|
||||
for file in files:
|
||||
@@ -285,9 +308,9 @@ class CertificateSet(object):
|
||||
self._load_file(b_path)
|
||||
|
||||
def find_parent(self, cert):
|
||||
'''
|
||||
"""
|
||||
Search for the parent (issuer) of a certificate. Return ``None`` if none was found.
|
||||
'''
|
||||
"""
|
||||
potential_parents = self.certificates_by_issuer.get(cert.cert.issuer, [])
|
||||
for potential_parent in potential_parents:
|
||||
if is_parent(self.module, cert, potential_parent):
|
||||
@@ -296,55 +319,62 @@ class CertificateSet(object):
|
||||
|
||||
|
||||
def format_cert(cert):
|
||||
'''
|
||||
"""
|
||||
Return human readable representation of certificate for error messages.
|
||||
'''
|
||||
"""
|
||||
return str(cert.cert)
|
||||
|
||||
|
||||
def check_cycle(module, occured_certificates, next):
|
||||
'''
|
||||
"""
|
||||
Make sure that next is not in occured_certificates so far, and add it.
|
||||
'''
|
||||
"""
|
||||
next_cert = next.cert
|
||||
if next_cert in occured_certificates:
|
||||
module.fail_json(msg='Found cycle while building certificate chain')
|
||||
module.fail_json(msg="Found cycle while building certificate chain")
|
||||
occured_certificates.add(next_cert)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
input_chain=dict(type='str', required=True),
|
||||
root_certificates=dict(type='list', required=True, elements='path'),
|
||||
intermediate_certificates=dict(type='list', default=[], elements='path'),
|
||||
input_chain=dict(type="str", required=True),
|
||||
root_certificates=dict(type="list", required=True, elements="path"),
|
||||
intermediate_certificates=dict(type="list", default=[], elements="path"),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_CRYPTOGRAPHY:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= 1.5'), exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("cryptography >= 1.5"),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
|
||||
# Load chain
|
||||
chain = parse_PEM_list(module, module.params['input_chain'], source='input chain')
|
||||
chain = parse_PEM_list(module, module.params["input_chain"], source="input chain")
|
||||
if len(chain) == 0:
|
||||
module.fail_json(msg='Input chain must contain at least one certificate')
|
||||
module.fail_json(msg="Input chain must contain at least one certificate")
|
||||
|
||||
# Check chain
|
||||
for i, parent in enumerate(chain):
|
||||
if i > 0:
|
||||
if not is_parent(module, chain[i - 1], parent):
|
||||
module.fail_json(msg=('Cannot verify input chain: certificate #{2}: {3} is not issuer ' +
|
||||
'of certificate #{0}: {1}').format(i, format_cert(chain[i - 1]), i + 1, format_cert(parent)))
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot verify input chain: certificate #{2}: {3} is not issuer "
|
||||
+ "of certificate #{0}: {1}"
|
||||
).format(i, format_cert(chain[i - 1]), i + 1, format_cert(parent))
|
||||
)
|
||||
|
||||
# Load intermediate certificates
|
||||
intermediates = CertificateSet(module)
|
||||
for path in module.params['intermediate_certificates']:
|
||||
for path in module.params["intermediate_certificates"]:
|
||||
intermediates.load(path)
|
||||
|
||||
# Load root certificates
|
||||
roots = CertificateSet(module)
|
||||
for path in module.params['root_certificates']:
|
||||
for path in module.params["root_certificates"]:
|
||||
roots.load(path)
|
||||
|
||||
# Try to complete chain
|
||||
@@ -366,7 +396,11 @@ def main():
|
||||
completed.append(intermediate)
|
||||
current = intermediate
|
||||
else:
|
||||
module.fail_json(msg='Cannot complete chain. Stuck at certificate {0}'.format(format_cert(current)))
|
||||
module.fail_json(
|
||||
msg="Cannot complete chain. Stuck at certificate {0}".format(
|
||||
format_cert(current)
|
||||
)
|
||||
)
|
||||
|
||||
# Return results
|
||||
complete_chain = chain + completed
|
||||
|
||||
@@ -198,33 +198,33 @@ else:
|
||||
|
||||
|
||||
CURVES = (
|
||||
('secp224r1', 'SECP224R1'),
|
||||
('secp256k1', 'SECP256K1'),
|
||||
('secp256r1', 'SECP256R1'),
|
||||
('secp384r1', 'SECP384R1'),
|
||||
('secp521r1', 'SECP521R1'),
|
||||
('secp192r1', 'SECP192R1'),
|
||||
('sect163k1', 'SECT163K1'),
|
||||
('sect163r2', 'SECT163R2'),
|
||||
('sect233k1', 'SECT233K1'),
|
||||
('sect233r1', 'SECT233R1'),
|
||||
('sect283k1', 'SECT283K1'),
|
||||
('sect283r1', 'SECT283R1'),
|
||||
('sect409k1', 'SECT409K1'),
|
||||
('sect409r1', 'SECT409R1'),
|
||||
('sect571k1', 'SECT571K1'),
|
||||
('sect571r1', 'SECT571R1'),
|
||||
('brainpoolP256r1', 'BrainpoolP256R1'),
|
||||
('brainpoolP384r1', 'BrainpoolP384R1'),
|
||||
('brainpoolP512r1', 'BrainpoolP512R1'),
|
||||
("secp224r1", "SECP224R1"),
|
||||
("secp256k1", "SECP256K1"),
|
||||
("secp256r1", "SECP256R1"),
|
||||
("secp384r1", "SECP384R1"),
|
||||
("secp521r1", "SECP521R1"),
|
||||
("secp192r1", "SECP192R1"),
|
||||
("sect163k1", "SECT163K1"),
|
||||
("sect163r2", "SECT163R2"),
|
||||
("sect233k1", "SECT233K1"),
|
||||
("sect233r1", "SECT233R1"),
|
||||
("sect283k1", "SECT283K1"),
|
||||
("sect283r1", "SECT283R1"),
|
||||
("sect409k1", "SECT409K1"),
|
||||
("sect409r1", "SECT409R1"),
|
||||
("sect571k1", "SECT571K1"),
|
||||
("sect571r1", "SECT571R1"),
|
||||
("brainpoolP256r1", "BrainpoolP256R1"),
|
||||
("brainpoolP384r1", "BrainpoolP384R1"),
|
||||
("brainpoolP512r1", "BrainpoolP512R1"),
|
||||
)
|
||||
|
||||
|
||||
def add_crypto_information(module):
|
||||
result = {}
|
||||
result['python_cryptography_installed'] = HAS_CRYPTOGRAPHY
|
||||
result["python_cryptography_installed"] = HAS_CRYPTOGRAPHY
|
||||
if not HAS_CRYPTOGRAPHY:
|
||||
result['python_cryptography_import_error'] = CRYPTOGRAPHY_IMP_ERR
|
||||
result["python_cryptography_import_error"] = CRYPTOGRAPHY_IMP_ERR
|
||||
return result
|
||||
|
||||
has_ed25519 = CRYPTOGRAPHY_HAS_ED25519
|
||||
@@ -233,7 +233,8 @@ def add_crypto_information(module):
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
||||
Ed25519PrivateKey,
|
||||
)
|
||||
Ed25519PrivateKey.from_private_bytes(b'')
|
||||
|
||||
Ed25519PrivateKey.from_private_bytes(b"")
|
||||
except ValueError:
|
||||
pass
|
||||
except UnsupportedAlgorithm:
|
||||
@@ -243,7 +244,8 @@ def add_crypto_information(module):
|
||||
if has_ed448:
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
|
||||
Ed448PrivateKey.from_private_bytes(b'')
|
||||
|
||||
Ed448PrivateKey.from_private_bytes(b"")
|
||||
except ValueError:
|
||||
pass
|
||||
except UnsupportedAlgorithm:
|
||||
@@ -255,8 +257,9 @@ def add_crypto_information(module):
|
||||
from cryptography.hazmat.primitives.asymmetric.x25519 import (
|
||||
X25519PrivateKey,
|
||||
)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_X25519_FULL:
|
||||
X25519PrivateKey.from_private_bytes(b'')
|
||||
X25519PrivateKey.from_private_bytes(b"")
|
||||
else:
|
||||
# Some versions do not support serialization and deserialization - use generate() instead
|
||||
X25519PrivateKey.generate()
|
||||
@@ -269,7 +272,8 @@ def add_crypto_information(module):
|
||||
if has_x448:
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey
|
||||
X448PrivateKey.from_private_bytes(b'')
|
||||
|
||||
X448PrivateKey.from_private_bytes(b"")
|
||||
except ValueError:
|
||||
pass
|
||||
except UnsupportedAlgorithm:
|
||||
@@ -282,59 +286,65 @@ def add_crypto_information(module):
|
||||
|
||||
backend = cryptography.hazmat.backends.default_backend()
|
||||
for curve_name, constructor_name in CURVES:
|
||||
ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(constructor_name)
|
||||
ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(
|
||||
constructor_name
|
||||
)
|
||||
if ecclass:
|
||||
try:
|
||||
cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(curve=ecclass(), backend=backend)
|
||||
cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(
|
||||
curve=ecclass(), backend=backend
|
||||
)
|
||||
curves.append(curve_name)
|
||||
except UnsupportedAlgorithm:
|
||||
pass
|
||||
except CryptographyInternalError: # pylint: disable=duplicate-except,bad-except-order
|
||||
except ( # pylint: disable=duplicate-except,bad-except-order
|
||||
CryptographyInternalError
|
||||
):
|
||||
# On Fedora 41, some curves result in InternalError. This is probably because
|
||||
# Fedora's cryptography is linked against the system libssl, which has the
|
||||
# curves removed.
|
||||
pass
|
||||
|
||||
info = {
|
||||
'version': CRYPTOGRAPHY_VERSION,
|
||||
'curves': curves,
|
||||
'has_ec': CRYPTOGRAPHY_HAS_EC,
|
||||
'has_ec_sign': CRYPTOGRAPHY_HAS_EC_SIGN,
|
||||
'has_ed25519': has_ed25519,
|
||||
'has_ed25519_sign': has_ed25519 and CRYPTOGRAPHY_HAS_ED25519_SIGN,
|
||||
'has_ed448': has_ed448,
|
||||
'has_ed448_sign': has_ed448 and CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||
'has_dsa': CRYPTOGRAPHY_HAS_DSA,
|
||||
'has_dsa_sign': CRYPTOGRAPHY_HAS_DSA_SIGN,
|
||||
'has_rsa': CRYPTOGRAPHY_HAS_RSA,
|
||||
'has_rsa_sign': CRYPTOGRAPHY_HAS_RSA_SIGN,
|
||||
'has_x25519': has_x25519,
|
||||
'has_x25519_serialization': has_x25519 and CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
'has_x448': has_x448,
|
||||
"version": CRYPTOGRAPHY_VERSION,
|
||||
"curves": curves,
|
||||
"has_ec": CRYPTOGRAPHY_HAS_EC,
|
||||
"has_ec_sign": CRYPTOGRAPHY_HAS_EC_SIGN,
|
||||
"has_ed25519": has_ed25519,
|
||||
"has_ed25519_sign": has_ed25519 and CRYPTOGRAPHY_HAS_ED25519_SIGN,
|
||||
"has_ed448": has_ed448,
|
||||
"has_ed448_sign": has_ed448 and CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||
"has_dsa": CRYPTOGRAPHY_HAS_DSA,
|
||||
"has_dsa_sign": CRYPTOGRAPHY_HAS_DSA_SIGN,
|
||||
"has_rsa": CRYPTOGRAPHY_HAS_RSA,
|
||||
"has_rsa_sign": CRYPTOGRAPHY_HAS_RSA_SIGN,
|
||||
"has_x25519": has_x25519,
|
||||
"has_x25519_serialization": has_x25519 and CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
"has_x448": has_x448,
|
||||
}
|
||||
result['python_cryptography_capabilities'] = info
|
||||
result["python_cryptography_capabilities"] = info
|
||||
return result
|
||||
|
||||
|
||||
def add_openssl_information(module):
|
||||
openssl_binary = module.get_bin_path('openssl')
|
||||
openssl_binary = module.get_bin_path("openssl")
|
||||
result = {
|
||||
'openssl_present': openssl_binary is not None,
|
||||
"openssl_present": openssl_binary is not None,
|
||||
}
|
||||
if openssl_binary is None:
|
||||
return result
|
||||
|
||||
openssl_result = {
|
||||
'path': openssl_binary,
|
||||
"path": openssl_binary,
|
||||
}
|
||||
result['openssl'] = openssl_result
|
||||
result["openssl"] = openssl_result
|
||||
|
||||
rc, out, err = module.run_command([openssl_binary, 'version'])
|
||||
rc, out, err = module.run_command([openssl_binary, "version"])
|
||||
if rc == 0:
|
||||
openssl_result['version_output'] = out
|
||||
openssl_result["version_output"] = out
|
||||
parts = out.split(None, 2)
|
||||
if len(parts) > 1:
|
||||
openssl_result['version'] = parts[1]
|
||||
openssl_result["version"] = parts[1]
|
||||
|
||||
return result
|
||||
|
||||
@@ -353,5 +363,5 @@ def main():
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -578,6 +578,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -585,14 +586,20 @@ except ImportError:
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
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):
|
||||
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
|
||||
|
||||
@@ -600,7 +607,9 @@ def validate_cert_expiry(cert_expiry):
|
||||
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')
|
||||
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
|
||||
|
||||
@@ -612,24 +621,24 @@ 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'
|
||||
body[dict_param_name] = "true"
|
||||
else:
|
||||
body[dict_param_name] = 'false'
|
||||
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']
|
||||
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
|
||||
@@ -646,82 +655,94 @@ class EcsCertificate(object):
|
||||
self.ecs_client = None
|
||||
if self.path and os.path.exists(self.path):
|
||||
try:
|
||||
self.cert = load_certificate(self.path, backend='cryptography')
|
||||
self.cert = load_certificate(self.path, backend="cryptography")
|
||||
except Exception:
|
||||
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']
|
||||
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)))
|
||||
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)))
|
||||
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']:
|
||||
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():
|
||||
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
|
||||
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']
|
||||
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'))
|
||||
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 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':
|
||||
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")
|
||||
body["certExpiryDate"] = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
|
||||
return body
|
||||
|
||||
def set_tracking_id_by_serial_number(self, module):
|
||||
@@ -729,21 +750,30 @@ class EcsCertificate(object):
|
||||
# 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', {})
|
||||
cert_results = self.ecs_client.GetCertificates(
|
||||
serialNumber=serial_number
|
||||
).get("certificates", {})
|
||||
if len(cert_results) == 1:
|
||||
self.tracking_id = cert_results[0].get('trackingId')
|
||||
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
|
||||
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'))
|
||||
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))
|
||||
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:
|
||||
@@ -751,22 +781,34 @@ class EcsCertificate(object):
|
||||
# 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 (
|
||||
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']
|
||||
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':
|
||||
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']:
|
||||
if self.cert_days < module.params["remaining_days"]:
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -777,20 +819,25 @@ class EcsCertificate(object):
|
||||
|
||||
# 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()
|
||||
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')
|
||||
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.
|
||||
|
||||
@@ -801,135 +848,161 @@ class EcsCertificate(object):
|
||||
|
||||
if not module.check_mode:
|
||||
try:
|
||||
if self.request_type == 'validate_only':
|
||||
body['validateOnly'] = 'true'
|
||||
if self.request_type == "validate_only":
|
||||
body["validateOnly"] = "true"
|
||||
result = self.ecs_client.NewCertRequest(Body=body)
|
||||
if self.request_type == 'new':
|
||||
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')
|
||||
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))
|
||||
module.fail_json(
|
||||
msg="Failed to request new certificate from Entrust (ECS) {0}".format(
|
||||
e.message
|
||||
)
|
||||
)
|
||||
|
||||
if self.request_type != 'validate_only':
|
||||
if self.request_type != "validate_only":
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
|
||||
if self.full_chain_path and self.cert_details.get('chainCerts'):
|
||||
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'
|
||||
write_file(module, to_bytes(chain_string), path=self.full_chain_path)
|
||||
self.backup_full_chain_file = module.backup_local(
|
||||
self.full_chain_path
|
||||
)
|
||||
chain_string = (
|
||||
"\n".join(self.cert_details.get("chainCerts")) + "\n"
|
||||
)
|
||||
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:
|
||||
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'
|
||||
write_file(module, to_bytes(chain_string), path=self.full_chain_path)
|
||||
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"
|
||||
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,
|
||||
"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
|
||||
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'),
|
||||
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']),
|
||||
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"]),
|
||||
)
|
||||
|
||||
|
||||
@@ -939,54 +1012,91 @@ def main():
|
||||
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'],
|
||||
["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 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']))
|
||||
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".')
|
||||
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']
|
||||
elif module.params["request_type"] != "renew":
|
||||
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']))
|
||||
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']))
|
||||
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:
|
||||
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["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["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']))
|
||||
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)
|
||||
@@ -994,5 +1104,5 @@ def main():
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -237,15 +237,15 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
|
||||
def calculate_days_remaining(expiry_date):
|
||||
days_remaining = None
|
||||
if expiry_date:
|
||||
expiry_datetime = datetime.datetime.strptime(expiry_date, '%Y-%m-%dT%H:%M:%SZ')
|
||||
expiry_datetime = datetime.datetime.strptime(expiry_date, "%Y-%m-%dT%H:%M:%SZ")
|
||||
days_remaining = (expiry_datetime - datetime.datetime.now()).days
|
||||
return days_remaining
|
||||
|
||||
|
||||
class EcsDomain(object):
|
||||
'''
|
||||
"""
|
||||
Entrust Certificate Services domain class.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, module):
|
||||
self.changed = False
|
||||
@@ -270,53 +270,76 @@ class EcsDomain(object):
|
||||
# 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']
|
||||
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)))
|
||||
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)))
|
||||
module.fail_json(
|
||||
msg="Please verify credential information. Received exception when testing ECS connection: {0}".format(
|
||||
to_native(e.message)
|
||||
)
|
||||
)
|
||||
|
||||
def set_domain_details(self, domain_details):
|
||||
if domain_details.get('verificationMethod'):
|
||||
self.verification_method = domain_details['verificationMethod'].lower()
|
||||
self.domain_status = domain_details['verificationStatus']
|
||||
self.ov_eligible = domain_details.get('ovEligible')
|
||||
self.ov_days_remaining = calculate_days_remaining(domain_details.get('ovExpiry'))
|
||||
self.ev_eligible = domain_details.get('evEligible')
|
||||
self.ev_days_remaining = calculate_days_remaining(domain_details.get('evExpiry'))
|
||||
self.client_id = domain_details['clientId']
|
||||
if domain_details.get("verificationMethod"):
|
||||
self.verification_method = domain_details["verificationMethod"].lower()
|
||||
self.domain_status = domain_details["verificationStatus"]
|
||||
self.ov_eligible = domain_details.get("ovEligible")
|
||||
self.ov_days_remaining = calculate_days_remaining(
|
||||
domain_details.get("ovExpiry")
|
||||
)
|
||||
self.ev_eligible = domain_details.get("evEligible")
|
||||
self.ev_days_remaining = calculate_days_remaining(
|
||||
domain_details.get("evExpiry")
|
||||
)
|
||||
self.client_id = domain_details["clientId"]
|
||||
|
||||
if self.verification_method == 'dns' and domain_details.get('dnsMethod'):
|
||||
self.dns_location = domain_details['dnsMethod']['recordDomain']
|
||||
self.dns_resource_type = domain_details['dnsMethod']['recordType']
|
||||
self.dns_contents = domain_details['dnsMethod']['recordValue']
|
||||
elif self.verification_method == 'web_server' and domain_details.get('webServerMethod'):
|
||||
self.file_location = domain_details['webServerMethod']['fileLocation']
|
||||
self.file_contents = domain_details['webServerMethod']['fileContents']
|
||||
elif self.verification_method == 'email' and domain_details.get('emailMethod'):
|
||||
self.emails = domain_details['emailMethod']
|
||||
if self.verification_method == "dns" and domain_details.get("dnsMethod"):
|
||||
self.dns_location = domain_details["dnsMethod"]["recordDomain"]
|
||||
self.dns_resource_type = domain_details["dnsMethod"]["recordType"]
|
||||
self.dns_contents = domain_details["dnsMethod"]["recordValue"]
|
||||
elif self.verification_method == "web_server" and domain_details.get(
|
||||
"webServerMethod"
|
||||
):
|
||||
self.file_location = domain_details["webServerMethod"]["fileLocation"]
|
||||
self.file_contents = domain_details["webServerMethod"]["fileContents"]
|
||||
elif self.verification_method == "email" and domain_details.get("emailMethod"):
|
||||
self.emails = domain_details["emailMethod"]
|
||||
|
||||
def check(self, module):
|
||||
try:
|
||||
domain_details = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name'])
|
||||
domain_details = self.ecs_client.GetDomain(
|
||||
clientId=module.params["client_id"], domain=module.params["domain_name"]
|
||||
)
|
||||
self.set_domain_details(domain_details)
|
||||
if self.domain_status != 'APPROVED' and self.domain_status != 'INITIAL_VERIFICATION' and self.domain_status != 'RE_VERIFICATION':
|
||||
if (
|
||||
self.domain_status != "APPROVED"
|
||||
and self.domain_status != "INITIAL_VERIFICATION"
|
||||
and self.domain_status != "RE_VERIFICATION"
|
||||
):
|
||||
return False
|
||||
|
||||
# If domain verification is in process, we want to return the random values and treat it as a valid.
|
||||
if self.domain_status == 'INITIAL_VERIFICATION' or self.domain_status == 'RE_VERIFICATION':
|
||||
if (
|
||||
self.domain_status == "INITIAL_VERIFICATION"
|
||||
or self.domain_status == "RE_VERIFICATION"
|
||||
):
|
||||
# Unless the verification method has changed, in which case we need to do a reverify request.
|
||||
if self.verification_method != module.params['verification_method']:
|
||||
if self.verification_method != module.params["verification_method"]:
|
||||
return False
|
||||
|
||||
if self.domain_status == 'EXPIRING':
|
||||
if self.domain_status == "EXPIRING":
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -327,83 +350,112 @@ class EcsDomain(object):
|
||||
if not self.check(module):
|
||||
body = {}
|
||||
|
||||
body['verificationMethod'] = module.params['verification_method'].upper()
|
||||
if module.params['verification_method'] == 'email':
|
||||
body["verificationMethod"] = module.params["verification_method"].upper()
|
||||
if module.params["verification_method"] == "email":
|
||||
emailMethod = {}
|
||||
if module.params['verification_email']:
|
||||
emailMethod['emailSource'] = 'SPECIFIED'
|
||||
emailMethod['email'] = module.params['verification_email']
|
||||
if module.params["verification_email"]:
|
||||
emailMethod["emailSource"] = "SPECIFIED"
|
||||
emailMethod["email"] = module.params["verification_email"]
|
||||
else:
|
||||
emailMethod['emailSource'] = 'INCLUDE_WHOIS'
|
||||
body['emailMethod'] = emailMethod
|
||||
emailMethod["emailSource"] = "INCLUDE_WHOIS"
|
||||
body["emailMethod"] = emailMethod
|
||||
# Only populate domain name in body if it is not an existing domain
|
||||
if not self.domain_status:
|
||||
body['domainName'] = module.params['domain_name']
|
||||
body["domainName"] = module.params["domain_name"]
|
||||
try:
|
||||
if not self.domain_status:
|
||||
self.ecs_client.AddDomain(clientId=module.params['client_id'], Body=body)
|
||||
self.ecs_client.AddDomain(
|
||||
clientId=module.params["client_id"], Body=body
|
||||
)
|
||||
else:
|
||||
self.ecs_client.ReverifyDomain(clientId=module.params['client_id'], domain=module.params['domain_name'], Body=body)
|
||||
self.ecs_client.ReverifyDomain(
|
||||
clientId=module.params["client_id"],
|
||||
domain=module.params["domain_name"],
|
||||
Body=body,
|
||||
)
|
||||
|
||||
time.sleep(5)
|
||||
result = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name'])
|
||||
result = self.ecs_client.GetDomain(
|
||||
clientId=module.params["client_id"],
|
||||
domain=module.params["domain_name"],
|
||||
)
|
||||
|
||||
# It takes a bit of time before the random values are available
|
||||
if module.params['verification_method'] == 'dns' or module.params['verification_method'] == 'web_server':
|
||||
if (
|
||||
module.params["verification_method"] == "dns"
|
||||
or module.params["verification_method"] == "web_server"
|
||||
):
|
||||
for i in range(4):
|
||||
# Check both that random values are now available, and that they're different than were populated by previous 'check'
|
||||
if module.params['verification_method'] == 'dns':
|
||||
if result.get('dnsMethod') and result['dnsMethod']['recordValue'] != self.dns_contents:
|
||||
if module.params["verification_method"] == "dns":
|
||||
if (
|
||||
result.get("dnsMethod")
|
||||
and result["dnsMethod"]["recordValue"]
|
||||
!= self.dns_contents
|
||||
):
|
||||
break
|
||||
elif module.params['verification_method'] == 'web_server':
|
||||
if result.get('webServerMethod') and result['webServerMethod']['fileContents'] != self.file_contents:
|
||||
elif module.params["verification_method"] == "web_server":
|
||||
if (
|
||||
result.get("webServerMethod")
|
||||
and result["webServerMethod"]["fileContents"]
|
||||
!= self.file_contents
|
||||
):
|
||||
break
|
||||
time.sleep(10)
|
||||
result = self.ecs_client.GetDomain(clientId=module.params['client_id'], domain=module.params['domain_name'])
|
||||
result = self.ecs_client.GetDomain(
|
||||
clientId=module.params["client_id"],
|
||||
domain=module.params["domain_name"],
|
||||
)
|
||||
self.changed = True
|
||||
self.set_domain_details(result)
|
||||
except RestOperationException as e:
|
||||
module.fail_json(msg='Failed to request domain validation from Entrust (ECS) {0}'.format(e.message))
|
||||
module.fail_json(
|
||||
msg="Failed to request domain validation from Entrust (ECS) {0}".format(
|
||||
e.message
|
||||
)
|
||||
)
|
||||
|
||||
def dump(self):
|
||||
result = {
|
||||
'changed': self.changed,
|
||||
'client_id': self.client_id,
|
||||
'domain_status': self.domain_status,
|
||||
"changed": self.changed,
|
||||
"client_id": self.client_id,
|
||||
"domain_status": self.domain_status,
|
||||
}
|
||||
|
||||
if self.verification_method:
|
||||
result['verification_method'] = self.verification_method
|
||||
result["verification_method"] = self.verification_method
|
||||
if self.ov_eligible is not None:
|
||||
result['ov_eligible'] = self.ov_eligible
|
||||
result["ov_eligible"] = self.ov_eligible
|
||||
if self.ov_days_remaining:
|
||||
result['ov_days_remaining'] = self.ov_days_remaining
|
||||
result["ov_days_remaining"] = self.ov_days_remaining
|
||||
if self.ev_eligible is not None:
|
||||
result['ev_eligible'] = self.ev_eligible
|
||||
result["ev_eligible"] = self.ev_eligible
|
||||
if self.ev_days_remaining:
|
||||
result['ev_days_remaining'] = self.ev_days_remaining
|
||||
result["ev_days_remaining"] = self.ev_days_remaining
|
||||
if self.emails:
|
||||
result['emails'] = self.emails
|
||||
result["emails"] = self.emails
|
||||
|
||||
if self.verification_method == 'dns':
|
||||
result['dns_location'] = self.dns_location
|
||||
result['dns_contents'] = self.dns_contents
|
||||
result['dns_resource_type'] = self.dns_resource_type
|
||||
elif self.verification_method == 'web_server':
|
||||
result['file_location'] = self.file_location
|
||||
result['file_contents'] = self.file_contents
|
||||
elif self.verification_method == 'email':
|
||||
result['emails'] = self.emails
|
||||
if self.verification_method == "dns":
|
||||
result["dns_location"] = self.dns_location
|
||||
result["dns_contents"] = self.dns_contents
|
||||
result["dns_resource_type"] = self.dns_resource_type
|
||||
elif self.verification_method == "web_server":
|
||||
result["file_location"] = self.file_location
|
||||
result["file_contents"] = self.file_contents
|
||||
elif self.verification_method == "email":
|
||||
result["emails"] = self.emails
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def ecs_domain_argument_spec():
|
||||
return dict(
|
||||
client_id=dict(type='int', default=1),
|
||||
domain_name=dict(type='str', required=True),
|
||||
verification_method=dict(type='str', required=True, choices=['dns', 'email', 'manual', 'web_server']),
|
||||
verification_email=dict(type='str'),
|
||||
client_id=dict(type="int", default=1),
|
||||
domain_name=dict(type="str", required=True),
|
||||
verification_method=dict(
|
||||
type="str", required=True, choices=["dns", "email", "manual", "web_server"]
|
||||
),
|
||||
verification_email=dict(type="str"),
|
||||
)
|
||||
|
||||
|
||||
@@ -415,8 +467,15 @@ def main():
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
if module.params['verification_email'] and module.params['verification_method'] != 'email':
|
||||
module.fail_json(msg='The verification_email field is invalid when verification_method="{0}".'.format(module.params['verification_method']))
|
||||
if (
|
||||
module.params["verification_email"]
|
||||
and module.params["verification_method"] != "email"
|
||||
):
|
||||
module.fail_json(
|
||||
msg='The verification_email field is invalid when verification_method="{0}".'.format(
|
||||
module.params["verification_method"]
|
||||
)
|
||||
)
|
||||
|
||||
domain = EcsDomain(module)
|
||||
domain.request_domain(module)
|
||||
@@ -424,5 +483,5 @@ def main():
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -294,7 +294,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "1.6"
|
||||
|
||||
CREATE_DEFAULT_CONTEXT_IMP_ERR = None
|
||||
try:
|
||||
@@ -311,6 +311,7 @@ try:
|
||||
import cryptography.exceptions
|
||||
import cryptography.x509
|
||||
from cryptography.hazmat.backends import default_backend as cryptography_backend
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -320,85 +321,100 @@ else:
|
||||
|
||||
|
||||
def send_starttls_packet(sock, server_type):
|
||||
if server_type == 'mysql':
|
||||
if server_type == "mysql":
|
||||
ssl_request_packet = (
|
||||
b'\x20\x00\x00\x01\x85\xae\x7f\x00' +
|
||||
b'\x00\x00\x00\x01\x21\x00\x00\x00' +
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
||||
b'\x00\x00\x00\x00\x00\x00\x00\x00' +
|
||||
b'\x00\x00\x00\x00'
|
||||
b"\x20\x00\x00\x01\x85\xae\x7f\x00"
|
||||
+ b"\x00\x00\x00\x01\x21\x00\x00\x00"
|
||||
+ b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
+ b"\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
+ b"\x00\x00\x00\x00"
|
||||
)
|
||||
|
||||
sock.recv(8192) # discard initial handshake from server for this naive implementation
|
||||
sock.recv(
|
||||
8192
|
||||
) # discard initial handshake from server for this naive implementation
|
||||
sock.send(ssl_request_packet)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
ca_cert=dict(type='path'),
|
||||
host=dict(type='str', required=True),
|
||||
port=dict(type='int', required=True),
|
||||
proxy_host=dict(type='str'),
|
||||
proxy_port=dict(type='int', default=8080),
|
||||
server_name=dict(type='str'),
|
||||
timeout=dict(type='int', default=10),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
starttls=dict(type='str', choices=['mysql']),
|
||||
ciphers=dict(type='list', elements='str'),
|
||||
asn1_base64=dict(type='bool'),
|
||||
tls_ctx_options=dict(type='list', elements='raw'),
|
||||
get_certificate_chain=dict(type='bool', default=False),
|
||||
ca_cert=dict(type="path"),
|
||||
host=dict(type="str", required=True),
|
||||
port=dict(type="int", required=True),
|
||||
proxy_host=dict(type="str"),
|
||||
proxy_port=dict(type="int", default=8080),
|
||||
server_name=dict(type="str"),
|
||||
timeout=dict(type="int", default=10),
|
||||
select_crypto_backend=dict(
|
||||
type="str", choices=["auto", "cryptography"], default="auto"
|
||||
),
|
||||
starttls=dict(type="str", choices=["mysql"]),
|
||||
ciphers=dict(type="list", elements="str"),
|
||||
asn1_base64=dict(type="bool"),
|
||||
tls_ctx_options=dict(type="list", elements="raw"),
|
||||
get_certificate_chain=dict(type="bool", default=False),
|
||||
),
|
||||
)
|
||||
|
||||
ca_cert = module.params.get('ca_cert')
|
||||
host = module.params.get('host')
|
||||
port = module.params.get('port')
|
||||
proxy_host = module.params.get('proxy_host')
|
||||
proxy_port = module.params.get('proxy_port')
|
||||
timeout = module.params.get('timeout')
|
||||
server_name = module.params.get('server_name')
|
||||
start_tls_server_type = module.params.get('starttls')
|
||||
ciphers = module.params.get('ciphers')
|
||||
asn1_base64 = module.params['asn1_base64']
|
||||
tls_ctx_options = module.params['tls_ctx_options']
|
||||
get_certificate_chain = module.params['get_certificate_chain']
|
||||
ca_cert = module.params.get("ca_cert")
|
||||
host = module.params.get("host")
|
||||
port = module.params.get("port")
|
||||
proxy_host = module.params.get("proxy_host")
|
||||
proxy_port = module.params.get("proxy_port")
|
||||
timeout = module.params.get("timeout")
|
||||
server_name = module.params.get("server_name")
|
||||
start_tls_server_type = module.params.get("starttls")
|
||||
ciphers = module.params.get("ciphers")
|
||||
asn1_base64 = module.params["asn1_base64"]
|
||||
tls_ctx_options = module.params["tls_ctx_options"]
|
||||
get_certificate_chain = module.params["get_certificate_chain"]
|
||||
|
||||
if asn1_base64 is None:
|
||||
module.deprecate(
|
||||
'The default value `false` for asn1_base64 is deprecated and will change to `true` in '
|
||||
'community.crypto 3.0.0. If you need this value, it is best to set the value explicitly '
|
||||
'and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible',
|
||||
version='3.0.0',
|
||||
collection_name='community.crypto',
|
||||
"The default value `false` for asn1_base64 is deprecated and will change to `true` in "
|
||||
"community.crypto 3.0.0. If you need this value, it is best to set the value explicitly "
|
||||
"and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
asn1_base64 = False
|
||||
|
||||
if get_certificate_chain and sys.version_info < (3, 10):
|
||||
module.fail_json(
|
||||
msg='get_certificate_chain=true can only be used with Python 3.10 (Python 3.13+ officially supports this). '
|
||||
'The Python version used to run the get_certificate module is %s' % sys.version
|
||||
msg="get_certificate_chain=true can only be used with Python 3.10 (Python 3.13+ officially supports this). "
|
||||
"The Python version used to run the get_certificate module is %s"
|
||||
% sys.version
|
||||
)
|
||||
|
||||
backend = module.params.get('select_crypto_backend')
|
||||
if backend == 'auto':
|
||||
backend = module.params.get("select_crypto_backend")
|
||||
if backend == "auto":
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_cryptography = (
|
||||
CRYPTOGRAPHY_FOUND
|
||||
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
backend = "cryptography"
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
if backend == "auto":
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot detect the required Python library " "cryptography (>= {0})"
|
||||
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
|
||||
if backend == 'cryptography':
|
||||
if backend == "cryptography":
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
@@ -417,19 +433,27 @@ def main():
|
||||
if not HAS_CREATE_DEFAULT_CONTEXT:
|
||||
# Python < 2.7.9
|
||||
if proxy_host:
|
||||
module.fail_json(msg='To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.',
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg="To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.",
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR,
|
||||
)
|
||||
if ciphers is not None:
|
||||
module.fail_json(msg='To use ciphers, you must run the get_certificate module with Python 2.7 or newer.',
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg="To use ciphers, you must run the get_certificate module with Python 2.7 or newer.",
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR,
|
||||
)
|
||||
if tls_ctx_options is not None:
|
||||
module.fail_json(msg='To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.',
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg="To use tls_ctx_options, you must run the get_certificate module with Python 2.7 or newer.",
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR,
|
||||
)
|
||||
try:
|
||||
# Note: get_server_certificate does not support SNI!
|
||||
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
|
||||
module.fail_json(
|
||||
msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e)
|
||||
)
|
||||
else:
|
||||
# Python >= 2.7.9
|
||||
try:
|
||||
@@ -478,21 +502,35 @@ def main():
|
||||
tls_ctx_option_int = tls_ctx_option_attr
|
||||
# If tls_ctx_option_attr is not an integer
|
||||
else:
|
||||
module.fail_json(msg="Failed to determine the numeric value for {0}".format(tls_ctx_option_str))
|
||||
module.fail_json(
|
||||
msg="Failed to determine the numeric value for {0}".format(
|
||||
tls_ctx_option_str
|
||||
)
|
||||
)
|
||||
# If the item is an integer
|
||||
elif isinstance(tls_ctx_option, int):
|
||||
# Set tls_ctx_option_int to the item value
|
||||
tls_ctx_option_int = tls_ctx_option
|
||||
# If the item is not a string nor integer
|
||||
else:
|
||||
module.fail_json(msg="tls_ctx_options must be a string or integer, got {0!r}".format(tls_ctx_option))
|
||||
tls_ctx_option_int = 0 # make pylint happy; this code is actually unreachable
|
||||
module.fail_json(
|
||||
msg="tls_ctx_options must be a string or integer, got {0!r}".format(
|
||||
tls_ctx_option
|
||||
)
|
||||
)
|
||||
tls_ctx_option_int = (
|
||||
0 # make pylint happy; this code is actually unreachable
|
||||
)
|
||||
|
||||
try:
|
||||
# Add the int value of the item to ctx options
|
||||
ctx.options |= tls_ctx_option_int
|
||||
except Exception:
|
||||
module.fail_json(msg="Failed to add {0} to CTX options".format(tls_ctx_option_str or tls_ctx_option_int))
|
||||
module.fail_json(
|
||||
msg="Failed to add {0} to CTX options".format(
|
||||
tls_ctx_option_str or tls_ctx_option_int
|
||||
)
|
||||
)
|
||||
|
||||
tls_sock = ctx.wrap_socket(sock, server_hostname=server_name or host)
|
||||
cert = tls_sock.getpeercert(True)
|
||||
@@ -511,7 +549,9 @@ def main():
|
||||
|
||||
ssl_obj = tls_sock._sslobj # This is of type ssl._ssl._SSLSocket
|
||||
verified_der_chain = _convert_chain(ssl_obj.get_verified_chain())
|
||||
unverified_der_chain = _convert_chain(ssl_obj.get_unverified_chain())
|
||||
unverified_der_chain = _convert_chain(
|
||||
ssl_obj.get_unverified_chain()
|
||||
)
|
||||
else:
|
||||
# This works with Python 3.13+
|
||||
|
||||
@@ -521,70 +561,95 @@ def main():
|
||||
# if they are not byte strings to work around this.
|
||||
def _convert_chain(chain):
|
||||
return [
|
||||
c if isinstance(c, bytes) else c.public_bytes(ssl._ssl.ENCODING_DER)
|
||||
(
|
||||
c
|
||||
if isinstance(c, bytes)
|
||||
else c.public_bytes(ssl._ssl.ENCODING_DER)
|
||||
)
|
||||
for c in chain
|
||||
]
|
||||
|
||||
verified_der_chain = _convert_chain(tls_sock.get_verified_chain())
|
||||
unverified_der_chain = _convert_chain(tls_sock.get_unverified_chain())
|
||||
unverified_der_chain = _convert_chain(
|
||||
tls_sock.get_unverified_chain()
|
||||
)
|
||||
|
||||
verified_chain = [DER_cert_to_PEM_cert(c) for c in verified_der_chain]
|
||||
unverified_chain = [DER_cert_to_PEM_cert(c) for c in unverified_der_chain]
|
||||
unverified_chain = [
|
||||
DER_cert_to_PEM_cert(c) for c in unverified_der_chain
|
||||
]
|
||||
|
||||
except Exception as e:
|
||||
if proxy_host:
|
||||
module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
|
||||
proxy_host, proxy_port, host, port, e))
|
||||
module.fail_json(
|
||||
msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
|
||||
proxy_host, proxy_port, host, port, e
|
||||
)
|
||||
)
|
||||
else:
|
||||
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
|
||||
module.fail_json(
|
||||
msg="Failed to get cert from {0}:{1}, error: {2}".format(
|
||||
host, port, e
|
||||
)
|
||||
)
|
||||
|
||||
result['cert'] = cert
|
||||
result["cert"] = cert
|
||||
|
||||
if backend == 'cryptography':
|
||||
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend())
|
||||
result['subject'] = {}
|
||||
if backend == "cryptography":
|
||||
x509 = cryptography.x509.load_pem_x509_certificate(
|
||||
to_bytes(cert), cryptography_backend()
|
||||
)
|
||||
result["subject"] = {}
|
||||
for attribute in x509.subject:
|
||||
result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||
result["subject"][cryptography_oid_to_name(attribute.oid, short=True)] = (
|
||||
attribute.value
|
||||
)
|
||||
|
||||
result['expired'] = get_not_valid_after(x509) < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||
result["expired"] = get_not_valid_after(x509) < get_now_datetime(
|
||||
with_timezone=CRYPTOGRAPHY_TIMEZONE
|
||||
)
|
||||
|
||||
result['extensions'] = []
|
||||
result["extensions"] = []
|
||||
for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items():
|
||||
oid = cryptography.x509.oid.ObjectIdentifier(dotted_number)
|
||||
ext = {
|
||||
'critical': entry['critical'],
|
||||
'asn1_data': entry['value'],
|
||||
'name': cryptography_oid_to_name(oid, short=True),
|
||||
"critical": entry["critical"],
|
||||
"asn1_data": entry["value"],
|
||||
"name": cryptography_oid_to_name(oid, short=True),
|
||||
}
|
||||
if not asn1_base64:
|
||||
ext['asn1_data'] = base64.b64decode(ext['asn1_data'])
|
||||
result['extensions'].append(ext)
|
||||
ext["asn1_data"] = base64.b64decode(ext["asn1_data"])
|
||||
result["extensions"].append(ext)
|
||||
|
||||
result['issuer'] = {}
|
||||
result["issuer"] = {}
|
||||
for attribute in x509.issuer:
|
||||
result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||
result["issuer"][cryptography_oid_to_name(attribute.oid, short=True)] = (
|
||||
attribute.value
|
||||
)
|
||||
|
||||
result['not_after'] = get_not_valid_after(x509).strftime('%Y%m%d%H%M%SZ')
|
||||
result['not_before'] = get_not_valid_before(x509).strftime('%Y%m%d%H%M%SZ')
|
||||
result["not_after"] = get_not_valid_after(x509).strftime("%Y%m%d%H%M%SZ")
|
||||
result["not_before"] = get_not_valid_before(x509).strftime("%Y%m%d%H%M%SZ")
|
||||
|
||||
result['serial_number'] = x509.serial_number
|
||||
result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid)
|
||||
result["serial_number"] = x509.serial_number
|
||||
result["signature_algorithm"] = cryptography_oid_to_name(
|
||||
x509.signature_algorithm_oid
|
||||
)
|
||||
|
||||
# We need the -1 offset to get the same values as pyOpenSSL
|
||||
if x509.version == cryptography.x509.Version.v1:
|
||||
result['version'] = 1 - 1
|
||||
result["version"] = 1 - 1
|
||||
elif x509.version == cryptography.x509.Version.v3:
|
||||
result['version'] = 3 - 1
|
||||
result["version"] = 3 - 1
|
||||
else:
|
||||
result['version'] = "unknown"
|
||||
result["version"] = "unknown"
|
||||
|
||||
if verified_chain is not None:
|
||||
result['verified_chain'] = verified_chain
|
||||
result["verified_chain"] = verified_chain
|
||||
if unverified_chain is not None:
|
||||
result['unverified_chain'] = unverified_chain
|
||||
result["unverified_chain"] = unverified_chain
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -312,25 +312,29 @@ class Certificate(OpensshModule):
|
||||
super(Certificate, self).__init__(module)
|
||||
self.ssh_keygen = KeygenCommand(self.module)
|
||||
|
||||
self.identifier = self.module.params['identifier'] or ""
|
||||
self.options = self.module.params['options'] or []
|
||||
self.path = self.module.params['path']
|
||||
self.pkcs11_provider = self.module.params['pkcs11_provider']
|
||||
self.principals = self.module.params['principals'] or []
|
||||
self.public_key = self.module.params['public_key']
|
||||
self.regenerate = self.module.params['regenerate'] if not self.module.params['force'] else 'always'
|
||||
self.serial_number = self.module.params['serial_number']
|
||||
self.signature_algorithm = self.module.params['signature_algorithm']
|
||||
self.signing_key = self.module.params['signing_key']
|
||||
self.state = self.module.params['state']
|
||||
self.type = self.module.params['type']
|
||||
self.use_agent = self.module.params['use_agent']
|
||||
self.valid_at = self.module.params['valid_at']
|
||||
self.ignore_timestamps = self.module.params['ignore_timestamps']
|
||||
self.identifier = self.module.params["identifier"] or ""
|
||||
self.options = self.module.params["options"] or []
|
||||
self.path = self.module.params["path"]
|
||||
self.pkcs11_provider = self.module.params["pkcs11_provider"]
|
||||
self.principals = self.module.params["principals"] or []
|
||||
self.public_key = self.module.params["public_key"]
|
||||
self.regenerate = (
|
||||
self.module.params["regenerate"]
|
||||
if not self.module.params["force"]
|
||||
else "always"
|
||||
)
|
||||
self.serial_number = self.module.params["serial_number"]
|
||||
self.signature_algorithm = self.module.params["signature_algorithm"]
|
||||
self.signing_key = self.module.params["signing_key"]
|
||||
self.state = self.module.params["state"]
|
||||
self.type = self.module.params["type"]
|
||||
self.use_agent = self.module.params["use_agent"]
|
||||
self.valid_at = self.module.params["valid_at"]
|
||||
self.ignore_timestamps = self.module.params["ignore_timestamps"]
|
||||
|
||||
self._check_if_base_dir(self.path)
|
||||
|
||||
if self.state == 'present':
|
||||
if self.state == "present":
|
||||
self._validate_parameters()
|
||||
|
||||
self.data = None
|
||||
@@ -339,7 +343,7 @@ class Certificate(OpensshModule):
|
||||
self._load_certificate()
|
||||
|
||||
self.time_parameters = None
|
||||
if self.state == 'present':
|
||||
if self.state == "present":
|
||||
self._set_time_parameters()
|
||||
|
||||
def _validate_parameters(self):
|
||||
@@ -347,7 +351,9 @@ class Certificate(OpensshModule):
|
||||
self._check_if_base_dir(path)
|
||||
|
||||
if self.options and self.type == "host":
|
||||
self.module.fail_json(msg="Options can only be used with user certificates.")
|
||||
self.module.fail_json(
|
||||
msg="Options can only be used with user certificates."
|
||||
)
|
||||
|
||||
if self.use_agent:
|
||||
self._use_agent_available()
|
||||
@@ -358,8 +364,8 @@ class Certificate(OpensshModule):
|
||||
self.module.fail_json(msg="Failed to determine ssh version")
|
||||
elif LooseVersion(ssh_version) < LooseVersion("7.6"):
|
||||
self.module.fail_json(
|
||||
msg="Signing with CA key in ssh agent requires ssh 7.6 or newer." +
|
||||
" Your version is: %s" % ssh_version
|
||||
msg="Signing with CA key in ssh agent requires ssh 7.6 or newer."
|
||||
+ " Your version is: %s" % ssh_version
|
||||
)
|
||||
|
||||
def _exists(self):
|
||||
@@ -369,21 +375,23 @@ class Certificate(OpensshModule):
|
||||
try:
|
||||
self.original_data = OpensshCertificate.load(self.path)
|
||||
except (TypeError, ValueError) as e:
|
||||
if self.regenerate in ('never', 'fail'):
|
||||
self.module.fail_json(msg="Unable to read existing certificate: %s" % to_native(e))
|
||||
if self.regenerate in ("never", "fail"):
|
||||
self.module.fail_json(
|
||||
msg="Unable to read existing certificate: %s" % to_native(e)
|
||||
)
|
||||
self.module.warn("Unable to read existing certificate: %s" % to_native(e))
|
||||
|
||||
def _set_time_parameters(self):
|
||||
try:
|
||||
self.time_parameters = OpensshCertificateTimeParameters(
|
||||
valid_from=self.module.params['valid_from'],
|
||||
valid_to=self.module.params['valid_to'],
|
||||
valid_from=self.module.params["valid_from"],
|
||||
valid_to=self.module.params["valid_to"],
|
||||
)
|
||||
except ValueError as e:
|
||||
self.module.fail_json(msg=to_native(e))
|
||||
|
||||
def _execute(self):
|
||||
if self.state == 'present':
|
||||
if self.state == "present":
|
||||
if self._should_generate():
|
||||
self._generate()
|
||||
self._update_permissions(self.path)
|
||||
@@ -392,44 +400,58 @@ class Certificate(OpensshModule):
|
||||
self._remove()
|
||||
|
||||
def _should_generate(self):
|
||||
if self.regenerate == 'never':
|
||||
if self.regenerate == "never":
|
||||
return self.original_data is None
|
||||
elif self.regenerate == 'fail':
|
||||
elif self.regenerate == "fail":
|
||||
if self.original_data and not self._is_fully_valid():
|
||||
self.module.fail_json(
|
||||
msg="Certificate does not match the provided options.",
|
||||
cert=get_cert_dict(self.original_data)
|
||||
cert=get_cert_dict(self.original_data),
|
||||
)
|
||||
return self.original_data is None
|
||||
elif self.regenerate == 'partial_idempotence':
|
||||
elif self.regenerate == "partial_idempotence":
|
||||
return self.original_data is None or not self._is_partially_valid()
|
||||
elif self.regenerate == 'full_idempotence':
|
||||
elif self.regenerate == "full_idempotence":
|
||||
return self.original_data is None or not self._is_fully_valid()
|
||||
else:
|
||||
return True
|
||||
|
||||
def _is_fully_valid(self):
|
||||
return self._is_partially_valid() and all([
|
||||
self._compare_options() if self.original_data.type == 'user' else True,
|
||||
self.original_data.key_id == self.identifier,
|
||||
self.original_data.public_key == self._get_key_fingerprint(self.public_key),
|
||||
self.original_data.signing_key == self._get_key_fingerprint(self.signing_key),
|
||||
])
|
||||
return self._is_partially_valid() and all(
|
||||
[
|
||||
self._compare_options() if self.original_data.type == "user" else True,
|
||||
self.original_data.key_id == self.identifier,
|
||||
self.original_data.public_key
|
||||
== self._get_key_fingerprint(self.public_key),
|
||||
self.original_data.signing_key
|
||||
== self._get_key_fingerprint(self.signing_key),
|
||||
]
|
||||
)
|
||||
|
||||
def _is_partially_valid(self):
|
||||
return all([
|
||||
set(self.original_data.principals) == set(self.principals),
|
||||
self.original_data.signature_type == self.signature_algorithm if self.signature_algorithm else True,
|
||||
self.original_data.serial == self.serial_number if self.serial_number is not None else True,
|
||||
self.original_data.type == self.type,
|
||||
self._compare_time_parameters(),
|
||||
])
|
||||
return all(
|
||||
[
|
||||
set(self.original_data.principals) == set(self.principals),
|
||||
(
|
||||
self.original_data.signature_type == self.signature_algorithm
|
||||
if self.signature_algorithm
|
||||
else True
|
||||
),
|
||||
(
|
||||
self.original_data.serial == self.serial_number
|
||||
if self.serial_number is not None
|
||||
else True
|
||||
),
|
||||
self.original_data.type == self.type,
|
||||
self._compare_time_parameters(),
|
||||
]
|
||||
)
|
||||
|
||||
def _compare_time_parameters(self):
|
||||
try:
|
||||
original_time_parameters = OpensshCertificateTimeParameters(
|
||||
valid_from=self.original_data.valid_after,
|
||||
valid_to=self.original_data.valid_before
|
||||
valid_to=self.original_data.valid_before,
|
||||
)
|
||||
except ValueError as e:
|
||||
return self.module.fail_json(msg=to_native(e))
|
||||
@@ -437,10 +459,12 @@ class Certificate(OpensshModule):
|
||||
if self.ignore_timestamps:
|
||||
return original_time_parameters.within_range(self.valid_at)
|
||||
|
||||
return all([
|
||||
original_time_parameters == self.time_parameters,
|
||||
original_time_parameters.within_range(self.valid_at)
|
||||
])
|
||||
return all(
|
||||
[
|
||||
original_time_parameters == self.time_parameters,
|
||||
original_time_parameters.within_range(self.valid_at),
|
||||
]
|
||||
)
|
||||
|
||||
def _compare_options(self):
|
||||
try:
|
||||
@@ -448,10 +472,12 @@ class Certificate(OpensshModule):
|
||||
except ValueError as e:
|
||||
return self.module.fail_json(msg=to_native(e))
|
||||
|
||||
return all([
|
||||
set(self.original_data.critical_options) == set(critical_options),
|
||||
set(self.original_data.extensions) == set(extensions)
|
||||
])
|
||||
return all(
|
||||
[
|
||||
set(self.original_data.critical_options) == set(critical_options),
|
||||
set(self.original_data.extensions) == set(extensions),
|
||||
]
|
||||
)
|
||||
|
||||
def _get_key_fingerprint(self, path):
|
||||
private_key_content = self.ssh_keygen.get_private_key(path, check_rc=True)[1]
|
||||
@@ -464,12 +490,16 @@ class Certificate(OpensshModule):
|
||||
temp_certificate = self._generate_temp_certificate()
|
||||
self._safe_secure_move([(temp_certificate, self.path)])
|
||||
except OSError as e:
|
||||
self.module.fail_json(msg="Unable to write certificate to %s: %s" % (self.path, to_native(e)))
|
||||
self.module.fail_json(
|
||||
msg="Unable to write certificate to %s: %s" % (self.path, to_native(e))
|
||||
)
|
||||
|
||||
try:
|
||||
self.data = OpensshCertificate.load(self.path)
|
||||
except (TypeError, ValueError) as e:
|
||||
self.module.fail_json(msg="Unable to read new certificate: %s" % to_native(e))
|
||||
self.module.fail_json(
|
||||
msg="Unable to read new certificate: %s" % to_native(e)
|
||||
)
|
||||
|
||||
def _generate_temp_certificate(self):
|
||||
key_copy = os.path.join(self.module.tmpdir, os.path.basename(self.public_key))
|
||||
@@ -477,16 +507,28 @@ class Certificate(OpensshModule):
|
||||
try:
|
||||
self.module.preserved_copy(self.public_key, key_copy)
|
||||
except OSError as e:
|
||||
self.module.fail_json(msg="Unable to stage temporary key: %s" % to_native(e))
|
||||
self.module.fail_json(
|
||||
msg="Unable to stage temporary key: %s" % to_native(e)
|
||||
)
|
||||
self.module.add_cleanup_file(key_copy)
|
||||
|
||||
self.ssh_keygen.generate_certificate(
|
||||
key_copy, self.identifier, self.options, self.pkcs11_provider, self.principals, self.serial_number,
|
||||
self.signature_algorithm, self.signing_key, self.type, self.time_parameters, self.use_agent,
|
||||
environ_update=dict(TZ="UTC"), check_rc=True
|
||||
key_copy,
|
||||
self.identifier,
|
||||
self.options,
|
||||
self.pkcs11_provider,
|
||||
self.principals,
|
||||
self.serial_number,
|
||||
self.signature_algorithm,
|
||||
self.signing_key,
|
||||
self.type,
|
||||
self.time_parameters,
|
||||
self.use_agent,
|
||||
environ_update=dict(TZ="UTC"),
|
||||
check_rc=True,
|
||||
)
|
||||
|
||||
temp_cert = os.path.splitext(key_copy)[0] + '-cert.pub'
|
||||
temp_cert = os.path.splitext(key_copy)[0] + "-cert.pub"
|
||||
self.module.add_cleanup_file(temp_cert)
|
||||
|
||||
return temp_cert
|
||||
@@ -497,29 +539,31 @@ class Certificate(OpensshModule):
|
||||
try:
|
||||
os.remove(self.path)
|
||||
except OSError as e:
|
||||
self.module.fail_json(msg="Unable to remove existing certificate: %s" % to_native(e))
|
||||
self.module.fail_json(
|
||||
msg="Unable to remove existing certificate: %s" % to_native(e)
|
||||
)
|
||||
|
||||
@property
|
||||
def _result(self):
|
||||
if self.state != 'present':
|
||||
if self.state != "present":
|
||||
return {}
|
||||
|
||||
certificate_info = self.ssh_keygen.get_certificate_info(
|
||||
self.path,
|
||||
check_rc=self.state == 'present' and not self.module.check_mode,
|
||||
check_rc=self.state == "present" and not self.module.check_mode,
|
||||
)[1]
|
||||
|
||||
return {
|
||||
'type': self.type,
|
||||
'filename': self.path,
|
||||
'info': format_cert_info(certificate_info),
|
||||
"type": self.type,
|
||||
"filename": self.path,
|
||||
"info": format_cert_info(certificate_info),
|
||||
}
|
||||
|
||||
@property
|
||||
def diff(self):
|
||||
return {
|
||||
'before': get_cert_dict(self.original_data),
|
||||
'after': get_cert_dict(self.data)
|
||||
"before": get_cert_dict(self.original_data),
|
||||
"after": get_cert_dict(self.data),
|
||||
}
|
||||
|
||||
|
||||
@@ -528,7 +572,17 @@ def format_cert_info(cert_info):
|
||||
string = ""
|
||||
|
||||
for word in cert_info.split():
|
||||
if word in ("Type:", "Public", "Signing", "Key", "Serial:", "Valid:", "Principals:", "Critical", "Extensions:"):
|
||||
if word in (
|
||||
"Type:",
|
||||
"Public",
|
||||
"Signing",
|
||||
"Key",
|
||||
"Serial:",
|
||||
"Valid:",
|
||||
"Principals:",
|
||||
"Critical",
|
||||
"Extensions:",
|
||||
):
|
||||
result.append(string)
|
||||
string = word
|
||||
else:
|
||||
@@ -544,8 +598,8 @@ def get_cert_dict(data):
|
||||
return {}
|
||||
|
||||
result = data.to_dict()
|
||||
result.pop('nonce')
|
||||
result['signature_algorithm'] = data.signature_type
|
||||
result.pop("nonce")
|
||||
result["signature_algorithm"] = data.signature_type
|
||||
|
||||
return result
|
||||
|
||||
@@ -553,36 +607,50 @@ def get_cert_dict(data):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
force=dict(type='bool', default=False),
|
||||
identifier=dict(type='str'),
|
||||
options=dict(type='list', elements='str'),
|
||||
path=dict(type='path', required=True),
|
||||
pkcs11_provider=dict(type='str'),
|
||||
principals=dict(type='list', elements='str'),
|
||||
public_key=dict(type='path'),
|
||||
force=dict(type="bool", default=False),
|
||||
identifier=dict(type="str"),
|
||||
options=dict(type="list", elements="str"),
|
||||
path=dict(type="path", required=True),
|
||||
pkcs11_provider=dict(type="str"),
|
||||
principals=dict(type="list", elements="str"),
|
||||
public_key=dict(type="path"),
|
||||
regenerate=dict(
|
||||
type='str',
|
||||
default='partial_idempotence',
|
||||
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
|
||||
type="str",
|
||||
default="partial_idempotence",
|
||||
choices=[
|
||||
"never",
|
||||
"fail",
|
||||
"partial_idempotence",
|
||||
"full_idempotence",
|
||||
"always",
|
||||
],
|
||||
),
|
||||
signature_algorithm=dict(type='str', choices=['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512']),
|
||||
signing_key=dict(type='path'),
|
||||
serial_number=dict(type='int'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
type=dict(type='str', choices=['host', 'user']),
|
||||
use_agent=dict(type='bool', default=False),
|
||||
valid_at=dict(type='str'),
|
||||
valid_from=dict(type='str'),
|
||||
valid_to=dict(type='str'),
|
||||
ignore_timestamps=dict(type='bool', default=False),
|
||||
signature_algorithm=dict(
|
||||
type="str", choices=["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"]
|
||||
),
|
||||
signing_key=dict(type="path"),
|
||||
serial_number=dict(type="int"),
|
||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
||||
type=dict(type="str", choices=["host", "user"]),
|
||||
use_agent=dict(type="bool", default=False),
|
||||
valid_at=dict(type="str"),
|
||||
valid_from=dict(type="str"),
|
||||
valid_to=dict(type="str"),
|
||||
ignore_timestamps=dict(type="bool", default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
required_if=[('state', 'present', ['type', 'signing_key', 'public_key', 'valid_from', 'valid_to'])],
|
||||
required_if=[
|
||||
(
|
||||
"state",
|
||||
"present",
|
||||
["type", "signing_key", "public_key", "valid_from", "valid_to"],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
Certificate(module).execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -213,33 +213,48 @@ def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
size=dict(type='int'),
|
||||
type=dict(type='str', default='rsa', choices=['rsa', 'dsa', 'rsa1', 'ecdsa', 'ed25519']),
|
||||
force=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
comment=dict(type='str'),
|
||||
regenerate=dict(
|
||||
type='str',
|
||||
default='partial_idempotence',
|
||||
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
size=dict(type="int"),
|
||||
type=dict(
|
||||
type="str",
|
||||
default="rsa",
|
||||
choices=["rsa", "dsa", "rsa1", "ecdsa", "ed25519"],
|
||||
),
|
||||
passphrase=dict(type='str', no_log=True),
|
||||
force=dict(type="bool", default=False),
|
||||
path=dict(type="path", required=True),
|
||||
comment=dict(type="str"),
|
||||
regenerate=dict(
|
||||
type="str",
|
||||
default="partial_idempotence",
|
||||
choices=[
|
||||
"never",
|
||||
"fail",
|
||||
"partial_idempotence",
|
||||
"full_idempotence",
|
||||
"always",
|
||||
],
|
||||
),
|
||||
passphrase=dict(type="str", no_log=True),
|
||||
private_key_format=dict(
|
||||
type='str',
|
||||
default='auto',
|
||||
type="str",
|
||||
default="auto",
|
||||
no_log=False,
|
||||
choices=['auto', 'pkcs1', 'pkcs8', 'ssh']),
|
||||
backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'opensshbin'])
|
||||
choices=["auto", "pkcs1", "pkcs8", "ssh"],
|
||||
),
|
||||
backend=dict(
|
||||
type="str",
|
||||
default="auto",
|
||||
choices=["auto", "cryptography", "opensshbin"],
|
||||
),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
)
|
||||
|
||||
keypair = select_backend(module, module.params['backend'])[1]
|
||||
keypair = select_backend(module, module.params["backend"])[1]
|
||||
|
||||
keypair.execute()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -266,21 +266,21 @@ class CertificateSigningRequestModule(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, module_backend):
|
||||
super(CertificateSigningRequestModule, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.check_mode
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
self.module_backend = module_backend
|
||||
self.return_content = module.params['return_content']
|
||||
self.return_content = module.params["return_content"]
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
self.module_backend.set_existing(load_file_if_exists(self.path, module))
|
||||
|
||||
def generate(self, module):
|
||||
'''Generate the certificate signing request.'''
|
||||
"""Generate the certificate signing request."""
|
||||
if self.force or self.module_backend.needs_regeneration():
|
||||
if not self.check_mode:
|
||||
self.module_backend.generate_csr()
|
||||
@@ -291,10 +291,12 @@ class CertificateSigningRequestModule(OpenSSLObject):
|
||||
self.changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
else:
|
||||
self.changed = module.set_fs_attributes_if_different(file_args, self.changed)
|
||||
self.changed = module.set_fs_attributes_if_different(
|
||||
file_args, self.changed
|
||||
)
|
||||
|
||||
def remove(self, module):
|
||||
self.module_backend.set_existing(None)
|
||||
@@ -303,43 +305,53 @@ class CertificateSigningRequestModule(OpenSSLObject):
|
||||
super(CertificateSigningRequestModule, self).remove(module)
|
||||
|
||||
def dump(self):
|
||||
'''Serialize the object into a dictionary.'''
|
||||
"""Serialize the object into a dictionary."""
|
||||
result = self.module_backend.dump(include_csr=self.return_content)
|
||||
result.update({
|
||||
'filename': self.path,
|
||||
'changed': self.changed,
|
||||
})
|
||||
result.update(
|
||||
{
|
||||
"filename": self.path,
|
||||
"changed": self.changed,
|
||||
}
|
||||
)
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = get_csr_argument_spec()
|
||||
argument_spec.argument_spec.update(dict(
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
force=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
return_content=dict(type='bool', default=False),
|
||||
))
|
||||
argument_spec.required_if.extend([('state', 'present', rof, True) for rof in argument_spec.required_one_of])
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
||||
force=dict(type="bool", default=False),
|
||||
path=dict(type="path", required=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
return_content=dict(type="bool", default=False),
|
||||
)
|
||||
)
|
||||
argument_spec.required_if.extend(
|
||||
[("state", "present", rof, True) for rof in argument_spec.required_one_of]
|
||||
)
|
||||
argument_spec.required_one_of = []
|
||||
module = argument_spec.create_ansible_module(
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(name=base_dir, msg='The directory %s does not exist or the file is not a directory' % base_dir)
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg="The directory %s does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
try:
|
||||
backend = module.params['select_crypto_backend']
|
||||
backend = module.params["select_crypto_backend"]
|
||||
backend, module_backend = select_backend(module, backend)
|
||||
|
||||
csr = CertificateSigningRequestModule(module, module_backend)
|
||||
if module.params['state'] == 'present':
|
||||
if module.params["state"] == "present":
|
||||
csr.generate(module)
|
||||
else:
|
||||
csr.remove(module)
|
||||
|
||||
@@ -325,30 +325,34 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
path=dict(type="path"),
|
||||
content=dict(type="str"),
|
||||
name_encoding=dict(
|
||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
||||
),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography"]
|
||||
),
|
||||
),
|
||||
required_one_of=(["path", "content"],),
|
||||
mutually_exclusive=(["path", "content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if module.params['content'] is not None:
|
||||
data = module.params['content'].encode('utf-8')
|
||||
if module.params["content"] is not None:
|
||||
data = module.params["content"].encode("utf-8")
|
||||
else:
|
||||
try:
|
||||
with open(module.params['path'], 'rb') as f:
|
||||
with open(module.params["path"], "rb") as f:
|
||||
data = f.read()
|
||||
except (IOError, OSError) as e:
|
||||
module.fail_json(msg='Error while reading CSR file from disk: {0}'.format(e))
|
||||
module.fail_json(
|
||||
msg="Error while reading CSR file from disk: {0}".format(e)
|
||||
)
|
||||
|
||||
backend, module_backend = select_backend(module, module.params['select_crypto_backend'], data, validate_signature=True)
|
||||
backend, module_backend = select_backend(
|
||||
module, module.params["select_crypto_backend"], data, validate_signature=True
|
||||
)
|
||||
|
||||
try:
|
||||
result = module_backend.get_info()
|
||||
|
||||
@@ -151,46 +151,50 @@ class CertificateSigningRequestModule(object):
|
||||
self.module = module
|
||||
self.module_backend = module_backend
|
||||
self.changed = False
|
||||
if module.params['content'] is not None:
|
||||
self.module_backend.set_existing(module.params['content'].encode('utf-8'))
|
||||
if module.params["content"] is not None:
|
||||
self.module_backend.set_existing(module.params["content"].encode("utf-8"))
|
||||
|
||||
def generate(self, module):
|
||||
'''Generate the certificate signing request.'''
|
||||
"""Generate the certificate signing request."""
|
||||
if self.module_backend.needs_regeneration():
|
||||
if not self.check_mode:
|
||||
self.module_backend.generate_csr()
|
||||
else:
|
||||
self.module.deprecate(
|
||||
'Check mode support for openssl_csr_pipe will change in community.crypto 3.0.0'
|
||||
' to behave the same as without check mode. You can get that behavior right now'
|
||||
' by adding `check_mode: false` to the openssl_csr_pipe task. If you think this'
|
||||
' breaks your use-case of this module, please create an issue in the'
|
||||
' community.crypto repository',
|
||||
version='3.0.0',
|
||||
collection_name='community.crypto',
|
||||
"Check mode support for openssl_csr_pipe will change in community.crypto 3.0.0"
|
||||
" to behave the same as without check mode. You can get that behavior right now"
|
||||
" by adding `check_mode: false` to the openssl_csr_pipe task. If you think this"
|
||||
" breaks your use-case of this module, please create an issue in the"
|
||||
" community.crypto repository",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
self.changed = True
|
||||
|
||||
def dump(self):
|
||||
'''Serialize the object into a dictionary.'''
|
||||
"""Serialize the object into a dictionary."""
|
||||
result = self.module_backend.dump(include_csr=True)
|
||||
result.update({
|
||||
'changed': self.changed,
|
||||
})
|
||||
result.update(
|
||||
{
|
||||
"changed": self.changed,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = get_csr_argument_spec()
|
||||
argument_spec.argument_spec.update(dict(
|
||||
content=dict(type='str'),
|
||||
))
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
content=dict(type="str"),
|
||||
)
|
||||
)
|
||||
module = argument_spec.create_ansible_module(
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
try:
|
||||
backend = module.params['select_crypto_backend']
|
||||
backend = module.params["select_crypto_backend"]
|
||||
backend, module_backend = select_backend(module, backend)
|
||||
|
||||
csr = CertificateSigningRequestModule(module, module_backend)
|
||||
|
||||
@@ -153,7 +153,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '2.0'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "2.0"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -162,6 +162,7 @@ try:
|
||||
import cryptography.hazmat.backends
|
||||
import cryptography.hazmat.primitives.asymmetric.dh
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -177,14 +178,14 @@ class DHParameterError(Exception):
|
||||
class DHParameterBase(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.state = module.params['state']
|
||||
self.path = module.params['path']
|
||||
self.size = module.params['size']
|
||||
self.force = module.params['force']
|
||||
self.state = module.params["state"]
|
||||
self.path = module.params["path"]
|
||||
self.size = module.params["size"]
|
||||
self.force = module.params["force"]
|
||||
self.changed = False
|
||||
self.return_content = module.params['return_content']
|
||||
self.return_content = module.params["return_content"]
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -232,7 +233,7 @@ class DHParameterBase(object):
|
||||
def _check_fs_attributes(self, module):
|
||||
"""Checks (and changes if not in check mode!) fs attributes"""
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
return False
|
||||
return not module.set_fs_attributes_if_different(file_args, False)
|
||||
|
||||
@@ -240,15 +241,15 @@ class DHParameterBase(object):
|
||||
"""Serialize the object into a dictionary."""
|
||||
|
||||
result = {
|
||||
'size': self.size,
|
||||
'filename': self.path,
|
||||
'changed': self.changed,
|
||||
"size": self.size,
|
||||
"filename": self.path,
|
||||
"changed": self.changed,
|
||||
}
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
if self.return_content:
|
||||
content = load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['dhparams'] = content.decode('utf-8') if content else None
|
||||
result["dhparams"] = content.decode("utf-8") if content else None
|
||||
|
||||
return result
|
||||
|
||||
@@ -271,7 +272,7 @@ class DHParameterOpenSSL(DHParameterBase):
|
||||
|
||||
def __init__(self, module):
|
||||
super(DHParameterOpenSSL, self).__init__(module)
|
||||
self.openssl_bin = module.get_bin_path('openssl', True)
|
||||
self.openssl_bin = module.get_bin_path("openssl", True)
|
||||
|
||||
def _do_generate(self, module):
|
||||
"""Actually generate the DH params."""
|
||||
@@ -280,7 +281,7 @@ class DHParameterOpenSSL(DHParameterBase):
|
||||
os.close(fd)
|
||||
module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit
|
||||
# openssl dhparam -out <path> <bits>
|
||||
command = [self.openssl_bin, 'dhparam', '-out', tmpsrc, str(self.size)]
|
||||
command = [self.openssl_bin, "dhparam", "-out", tmpsrc, str(self.size)]
|
||||
rc, dummy, err = module.run_command(command, check_rc=False)
|
||||
if rc != 0:
|
||||
raise DHParameterError(to_native(err))
|
||||
@@ -293,7 +294,15 @@ class DHParameterOpenSSL(DHParameterBase):
|
||||
|
||||
def _check_params_valid(self, module):
|
||||
"""Check if the params are in the correct state"""
|
||||
command = [self.openssl_bin, 'dhparam', '-check', '-text', '-noout', '-in', self.path]
|
||||
command = [
|
||||
self.openssl_bin,
|
||||
"dhparam",
|
||||
"-check",
|
||||
"-text",
|
||||
"-noout",
|
||||
"-in",
|
||||
self.path,
|
||||
]
|
||||
rc, out, err = module.run_command(command, check_rc=False)
|
||||
result = to_native(out)
|
||||
if rc != 0:
|
||||
@@ -342,9 +351,11 @@ class DHParameterCryptography(DHParameterBase):
|
||||
"""Check if the params are in the correct state"""
|
||||
# Load parameters
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
with open(self.path, "rb") as f:
|
||||
data = f.read()
|
||||
params = cryptography.hazmat.primitives.serialization.load_pem_parameters(data, backend=self.crypto_backend)
|
||||
params = cryptography.hazmat.primitives.serialization.load_pem_parameters(
|
||||
data, backend=self.crypto_backend
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
# Check parameters
|
||||
@@ -357,56 +368,70 @@ def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
size=dict(type='int', default=4096),
|
||||
force=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'openssl']),
|
||||
return_content=dict(type='bool', default=False),
|
||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
||||
size=dict(type="int", default=4096),
|
||||
force=dict(type="bool", default=False),
|
||||
path=dict(type="path", required=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography", "openssl"]
|
||||
),
|
||||
return_content=dict(type="bool", default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
)
|
||||
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg="The directory '%s' does not exist or the file is not a directory" % base_dir
|
||||
msg="The directory '%s' does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
if module.params['state'] == 'present':
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
if module.params["state"] == "present":
|
||||
backend = module.params["select_crypto_backend"]
|
||||
if backend == "auto":
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_openssl = module.get_bin_path('openssl', False) is not None
|
||||
can_use_cryptography = (
|
||||
CRYPTOGRAPHY_FOUND
|
||||
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
can_use_openssl = module.get_bin_path("openssl", False) is not None
|
||||
|
||||
# First try cryptography, then OpenSSL
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
backend = "cryptography"
|
||||
elif can_use_openssl:
|
||||
backend = 'openssl'
|
||||
backend = "openssl"
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Cannot detect either the required Python library cryptography (>= {0}) "
|
||||
"or the OpenSSL binary openssl").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
if backend == "auto":
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot detect either the required Python library cryptography (>= {0}) "
|
||||
"or the OpenSSL binary openssl"
|
||||
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
|
||||
if backend == 'openssl':
|
||||
if backend == "openssl":
|
||||
dhparam = DHParameterOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
elif backend == "cryptography":
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
dhparam = DHParameterCryptography(module)
|
||||
else:
|
||||
raise AssertionError('Internal error: unknown backend')
|
||||
raise AssertionError("Internal error: unknown backend")
|
||||
|
||||
if module.check_mode:
|
||||
result = dhparam.dump()
|
||||
result['changed'] = module.params['force'] or not dhparam.check(module)
|
||||
result["changed"] = module.params["force"] or not dhparam.check(module)
|
||||
module.exit_json(**result)
|
||||
|
||||
try:
|
||||
@@ -418,10 +443,10 @@ def main():
|
||||
|
||||
if module.check_mode:
|
||||
result = dhparam.dump()
|
||||
result['changed'] = os.path.exists(module.params['path'])
|
||||
result["changed"] = os.path.exists(module.params["path"])
|
||||
module.exit_json(**result)
|
||||
|
||||
if os.path.exists(module.params['path']):
|
||||
if os.path.exists(module.params["path"]):
|
||||
try:
|
||||
dhparam.remove(module)
|
||||
except Exception as exc:
|
||||
@@ -432,5 +457,5 @@ def main():
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -314,9 +314,9 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '3.0'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
MAXIMAL_PYOPENSSL_VERSION = '23.3.0'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "3.0"
|
||||
MINIMAL_PYOPENSSL_VERSION = "0.15"
|
||||
MAXIMAL_PYOPENSSL_VERSION = "23.3.0"
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
@@ -325,6 +325,7 @@ try:
|
||||
from OpenSSL.crypto import (
|
||||
load_pkcs12 as _load_pkcs12, # this got removed in pyOpenSSL 23.3.0
|
||||
)
|
||||
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except (ImportError, AttributeError):
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
@@ -339,6 +340,7 @@ try:
|
||||
from cryptography.hazmat.primitives.serialization.pkcs12 import (
|
||||
serialize_key_and_certificates,
|
||||
)
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -352,7 +354,9 @@ try:
|
||||
from cryptography.hazmat.primitives.serialization.pkcs12 import PBES
|
||||
|
||||
# Try to build encryption builder for compatibility2022
|
||||
serialization.PrivateFormat.PKCS12.encryption_builder().key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC).hmac_hash(hashes.SHA1())
|
||||
serialization.PrivateFormat.PKCS12.encryption_builder().key_cert_algorithm(
|
||||
PBES.PBESv1SHA1And3KeyTripleDESCBC
|
||||
).hmac_hash(hashes.SHA1())
|
||||
except Exception:
|
||||
CRYPTOGRAPHY_COMPATIBILITY2022_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_HAS_COMPATIBILITY2022 = False
|
||||
@@ -361,12 +365,15 @@ else:
|
||||
|
||||
|
||||
def load_certificate_set(filename, backend):
|
||||
'''
|
||||
"""
|
||||
Load list of concatenated PEM files, and return a list of parsed certificates.
|
||||
'''
|
||||
with open(filename, 'rb') as f:
|
||||
data = f.read().decode('utf-8')
|
||||
return [load_certificate(None, content=cert.encode('utf-8'), backend=backend) for cert in split_pem_list(data)]
|
||||
"""
|
||||
with open(filename, "rb") as f:
|
||||
data = f.read().decode("utf-8")
|
||||
return [
|
||||
load_certificate(None, content=cert.encode("utf-8"), backend=backend)
|
||||
for cert in split_pem_list(data)
|
||||
]
|
||||
|
||||
|
||||
class PkcsError(OpenSSLObjectError):
|
||||
@@ -376,40 +383,42 @@ class PkcsError(OpenSSLObjectError):
|
||||
class Pkcs(OpenSSLObject):
|
||||
def __init__(self, module, backend, iter_size_default=2048):
|
||||
super(Pkcs, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.check_mode
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
self.backend = backend
|
||||
self.action = module.params['action']
|
||||
self.other_certificates = module.params['other_certificates']
|
||||
self.other_certificates_parse_all = module.params['other_certificates_parse_all']
|
||||
self.other_certificates_content = module.params['other_certificates_content']
|
||||
self.certificate_path = module.params['certificate_path']
|
||||
self.certificate_content = module.params['certificate_content']
|
||||
self.friendly_name = module.params['friendly_name']
|
||||
self.iter_size = module.params['iter_size'] or iter_size_default
|
||||
self.maciter_size = module.params['maciter_size'] or 1
|
||||
self.encryption_level = module.params['encryption_level']
|
||||
self.passphrase = module.params['passphrase']
|
||||
self.action = module.params["action"]
|
||||
self.other_certificates = module.params["other_certificates"]
|
||||
self.other_certificates_parse_all = module.params[
|
||||
"other_certificates_parse_all"
|
||||
]
|
||||
self.other_certificates_content = module.params["other_certificates_content"]
|
||||
self.certificate_path = module.params["certificate_path"]
|
||||
self.certificate_content = module.params["certificate_content"]
|
||||
self.friendly_name = module.params["friendly_name"]
|
||||
self.iter_size = module.params["iter_size"] or iter_size_default
|
||||
self.maciter_size = module.params["maciter_size"] or 1
|
||||
self.encryption_level = module.params["encryption_level"]
|
||||
self.passphrase = module.params["passphrase"]
|
||||
self.pkcs12 = None
|
||||
self.privatekey_passphrase = module.params['privatekey_passphrase']
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
self.privatekey_passphrase = module.params["privatekey_passphrase"]
|
||||
self.privatekey_path = module.params["privatekey_path"]
|
||||
self.privatekey_content = module.params["privatekey_content"]
|
||||
self.pkcs12_bytes = None
|
||||
self.return_content = module.params['return_content']
|
||||
self.src = module.params['src']
|
||||
self.return_content = module.params["return_content"]
|
||||
self.src = module.params["src"]
|
||||
|
||||
if module.params['mode'] is None:
|
||||
module.params['mode'] = '0400'
|
||||
if module.params["mode"] is None:
|
||||
module.params["mode"] = "0400"
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
if self.certificate_path is not None:
|
||||
try:
|
||||
with open(self.certificate_path, 'rb') as fh:
|
||||
with open(self.certificate_path, "rb") as fh:
|
||||
self.certificate_content = fh.read()
|
||||
except (IOError, OSError) as exc:
|
||||
raise PkcsError(exc)
|
||||
@@ -418,7 +427,7 @@ class Pkcs(OpenSSLObject):
|
||||
|
||||
if self.privatekey_path is not None:
|
||||
try:
|
||||
with open(self.privatekey_path, 'rb') as fh:
|
||||
with open(self.privatekey_path, "rb") as fh:
|
||||
self.privatekey_content = fh.read()
|
||||
except (IOError, OSError) as exc:
|
||||
raise PkcsError(exc)
|
||||
@@ -430,17 +439,27 @@ class Pkcs(OpenSSLObject):
|
||||
filenames = list(self.other_certificates)
|
||||
self.other_certificates = []
|
||||
for other_cert_bundle in filenames:
|
||||
self.other_certificates.extend(load_certificate_set(other_cert_bundle, self.backend))
|
||||
self.other_certificates.extend(
|
||||
load_certificate_set(other_cert_bundle, self.backend)
|
||||
)
|
||||
else:
|
||||
self.other_certificates = [
|
||||
load_certificate(other_cert, backend=self.backend) for other_cert in self.other_certificates
|
||||
load_certificate(other_cert, backend=self.backend)
|
||||
for other_cert in self.other_certificates
|
||||
]
|
||||
elif self.other_certificates_content:
|
||||
certs = self.other_certificates_content
|
||||
if self.other_certificates_parse_all:
|
||||
certs = list(itertools.chain.from_iterable(split_pem_list(content) for content in certs))
|
||||
certs = list(
|
||||
itertools.chain.from_iterable(
|
||||
split_pem_list(content) for content in certs
|
||||
)
|
||||
)
|
||||
self.other_certificates = [
|
||||
load_certificate(None, content=to_bytes(other_cert), backend=self.backend) for other_cert in certs
|
||||
load_certificate(
|
||||
None, content=to_bytes(other_cert), backend=self.backend
|
||||
)
|
||||
for other_cert in certs
|
||||
]
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -476,7 +495,12 @@ class Pkcs(OpenSSLObject):
|
||||
def _check_pkey_passphrase():
|
||||
if self.privatekey_passphrase:
|
||||
try:
|
||||
load_privatekey(None, content=self.privatekey_content, passphrase=self.privatekey_passphrase, backend=self.backend)
|
||||
load_privatekey(
|
||||
None,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend=self.backend,
|
||||
)
|
||||
except OpenSSLObjectError:
|
||||
return False
|
||||
return True
|
||||
@@ -484,28 +508,39 @@ class Pkcs(OpenSSLObject):
|
||||
if not state_and_perms:
|
||||
return state_and_perms
|
||||
|
||||
if os.path.exists(self.path) and module.params['action'] == 'export':
|
||||
if os.path.exists(self.path) and module.params["action"] == "export":
|
||||
self.generate_bytes(module) # ignore result
|
||||
self.src = self.path
|
||||
try:
|
||||
pkcs12_privatekey, pkcs12_certificate, pkcs12_other_certificates, pkcs12_friendly_name = self.parse()
|
||||
(
|
||||
pkcs12_privatekey,
|
||||
pkcs12_certificate,
|
||||
pkcs12_other_certificates,
|
||||
pkcs12_friendly_name,
|
||||
) = self.parse()
|
||||
except OpenSSLObjectError:
|
||||
return False
|
||||
if (pkcs12_privatekey is not None) and (self.privatekey_content is not None):
|
||||
if (pkcs12_privatekey is not None) and (
|
||||
self.privatekey_content is not None
|
||||
):
|
||||
expected_pkey = self._dump_privatekey(self.pkcs12)
|
||||
if pkcs12_privatekey != expected_pkey:
|
||||
return False
|
||||
elif bool(pkcs12_privatekey) != bool(self.privatekey_content):
|
||||
return False
|
||||
|
||||
if (pkcs12_certificate is not None) and (self.certificate_content is not None):
|
||||
if (pkcs12_certificate is not None) and (
|
||||
self.certificate_content is not None
|
||||
):
|
||||
expected_cert = self._dump_certificate(self.pkcs12)
|
||||
if pkcs12_certificate != expected_cert:
|
||||
return False
|
||||
elif bool(pkcs12_certificate) != bool(self.certificate_content):
|
||||
return False
|
||||
|
||||
if (pkcs12_other_certificates is not None) and (self.other_certificates is not None):
|
||||
if (pkcs12_other_certificates is not None) and (
|
||||
self.other_certificates is not None
|
||||
):
|
||||
expected_other_certs = self._dump_other_certificates(self.pkcs12)
|
||||
if set(pkcs12_other_certificates) != set(expected_other_certs):
|
||||
return False
|
||||
@@ -516,18 +551,28 @@ class Pkcs(OpenSSLObject):
|
||||
# This check is required because pyOpenSSL will not return a friendly name
|
||||
# if the private key is not set in the file
|
||||
friendly_name = self._get_friendly_name(self.pkcs12)
|
||||
if ((friendly_name is not None) and (pkcs12_friendly_name is not None)):
|
||||
if (friendly_name is not None) and (pkcs12_friendly_name is not None):
|
||||
if friendly_name != pkcs12_friendly_name:
|
||||
return False
|
||||
elif bool(friendly_name) != bool(pkcs12_friendly_name):
|
||||
return False
|
||||
elif module.params['action'] == 'parse' and os.path.exists(self.src) and os.path.exists(self.path):
|
||||
elif (
|
||||
module.params["action"] == "parse"
|
||||
and os.path.exists(self.src)
|
||||
and os.path.exists(self.path)
|
||||
):
|
||||
try:
|
||||
pkey, cert, other_certs, friendly_name = self.parse()
|
||||
except OpenSSLObjectError:
|
||||
return False
|
||||
expected_content = to_bytes(
|
||||
''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None])
|
||||
"".join(
|
||||
[
|
||||
to_native(pem)
|
||||
for pem in [pkey, cert] + other_certs
|
||||
if pem is not None
|
||||
]
|
||||
)
|
||||
)
|
||||
dumped_content = load_file_if_exists(self.path, ignore_errors=True)
|
||||
if expected_content != dumped_content:
|
||||
@@ -541,16 +586,18 @@ class Pkcs(OpenSSLObject):
|
||||
"""Serialize the object into a dictionary."""
|
||||
|
||||
result = {
|
||||
'filename': self.path,
|
||||
"filename": self.path,
|
||||
}
|
||||
if self.privatekey_path:
|
||||
result['privatekey_path'] = self.privatekey_path
|
||||
result["privatekey_path"] = self.privatekey_path
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
if self.return_content:
|
||||
if self.pkcs12_bytes is None:
|
||||
self.pkcs12_bytes = load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['pkcs12'] = base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None
|
||||
result["pkcs12"] = (
|
||||
base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
@@ -563,7 +610,7 @@ class Pkcs(OpenSSLObject):
|
||||
"""Read PKCS#12 file."""
|
||||
|
||||
try:
|
||||
with open(self.src, 'rb') as pkcs12_fh:
|
||||
with open(self.src, "rb") as pkcs12_fh:
|
||||
pkcs12_content = pkcs12_fh.read()
|
||||
return self.parse_bytes(pkcs12_content)
|
||||
except IOError as exc:
|
||||
@@ -583,9 +630,11 @@ class Pkcs(OpenSSLObject):
|
||||
|
||||
class PkcsPyOpenSSL(Pkcs):
|
||||
def __init__(self, module):
|
||||
super(PkcsPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
if self.encryption_level != 'auto':
|
||||
module.fail_json(msg='The PyOpenSSL backend only supports encryption_level = auto')
|
||||
super(PkcsPyOpenSSL, self).__init__(module, "pyopenssl")
|
||||
if self.encryption_level != "auto":
|
||||
module.fail_json(
|
||||
msg="The PyOpenSSL backend only supports encryption_level = auto"
|
||||
)
|
||||
|
||||
def generate_bytes(self, module):
|
||||
"""Generate PKCS#12 file archive."""
|
||||
@@ -595,7 +644,11 @@ class PkcsPyOpenSSL(Pkcs):
|
||||
self.pkcs12.set_ca_certificates(self.other_certificates)
|
||||
|
||||
if self.certificate_content:
|
||||
self.pkcs12.set_certificate(load_certificate(None, content=self.certificate_content, backend=self.backend))
|
||||
self.pkcs12.set_certificate(
|
||||
load_certificate(
|
||||
None, content=self.certificate_content, backend=self.backend
|
||||
)
|
||||
)
|
||||
|
||||
if self.friendly_name:
|
||||
self.pkcs12.set_friendlyname(to_bytes(self.friendly_name))
|
||||
@@ -603,7 +656,13 @@ class PkcsPyOpenSSL(Pkcs):
|
||||
if self.privatekey_content:
|
||||
try:
|
||||
self.pkcs12.set_privatekey(
|
||||
load_privatekey(None, content=self.privatekey_content, passphrase=self.privatekey_passphrase, backend=self.backend))
|
||||
load_privatekey(
|
||||
None,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend=self.backend,
|
||||
)
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise PkcsError(exc)
|
||||
|
||||
@@ -620,8 +679,10 @@ class PkcsPyOpenSSL(Pkcs):
|
||||
crt = crypto.dump_certificate(crypto.FILETYPE_PEM, crt)
|
||||
other_certs = []
|
||||
if p12.get_ca_certificates() is not None:
|
||||
other_certs = [crypto.dump_certificate(crypto.FILETYPE_PEM,
|
||||
other_cert) for other_cert in p12.get_ca_certificates()]
|
||||
other_certs = [
|
||||
crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert)
|
||||
for other_cert in p12.get_ca_certificates()
|
||||
]
|
||||
|
||||
friendly_name = p12.get_friendlyname()
|
||||
|
||||
@@ -651,43 +712,60 @@ class PkcsPyOpenSSL(Pkcs):
|
||||
|
||||
class PkcsCryptography(Pkcs):
|
||||
def __init__(self, module):
|
||||
super(PkcsCryptography, self).__init__(module, 'cryptography', iter_size_default=50000)
|
||||
if self.encryption_level == 'compatibility2022' and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022:
|
||||
super(PkcsCryptography, self).__init__(
|
||||
module, "cryptography", iter_size_default=50000
|
||||
)
|
||||
if (
|
||||
self.encryption_level == "compatibility2022"
|
||||
and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022
|
||||
):
|
||||
module.fail_json(
|
||||
msg='The installed cryptography version does not support encryption_level = compatibility2022.'
|
||||
' You need cryptography >= 38.0.0 and support for SHA1',
|
||||
exception=CRYPTOGRAPHY_COMPATIBILITY2022_ERR)
|
||||
msg="The installed cryptography version does not support encryption_level = compatibility2022."
|
||||
" You need cryptography >= 38.0.0 and support for SHA1",
|
||||
exception=CRYPTOGRAPHY_COMPATIBILITY2022_ERR,
|
||||
)
|
||||
|
||||
def generate_bytes(self, module):
|
||||
"""Generate PKCS#12 file archive."""
|
||||
pkey = None
|
||||
if self.privatekey_content:
|
||||
try:
|
||||
pkey = load_privatekey(None, content=self.privatekey_content, passphrase=self.privatekey_passphrase, backend=self.backend)
|
||||
pkey = load_privatekey(
|
||||
None,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend=self.backend,
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise PkcsError(exc)
|
||||
|
||||
cert = None
|
||||
if self.certificate_content:
|
||||
cert = load_certificate(None, content=self.certificate_content, backend=self.backend)
|
||||
cert = load_certificate(
|
||||
None, content=self.certificate_content, backend=self.backend
|
||||
)
|
||||
|
||||
friendly_name = to_bytes(self.friendly_name) if self.friendly_name is not None else None
|
||||
friendly_name = (
|
||||
to_bytes(self.friendly_name) if self.friendly_name is not None else None
|
||||
)
|
||||
|
||||
# Store fake object which can be used to retrieve the components back
|
||||
self.pkcs12 = (pkey, cert, self.other_certificates, friendly_name)
|
||||
|
||||
if not self.passphrase:
|
||||
encryption = serialization.NoEncryption()
|
||||
elif self.encryption_level == 'compatibility2022':
|
||||
elif self.encryption_level == "compatibility2022":
|
||||
encryption = (
|
||||
serialization.PrivateFormat.PKCS12.encryption_builder().
|
||||
kdf_rounds(self.iter_size).
|
||||
key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC).
|
||||
hmac_hash(hashes.SHA1()).
|
||||
build(to_bytes(self.passphrase))
|
||||
serialization.PrivateFormat.PKCS12.encryption_builder()
|
||||
.kdf_rounds(self.iter_size)
|
||||
.key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC)
|
||||
.hmac_hash(hashes.SHA1())
|
||||
.build(to_bytes(self.passphrase))
|
||||
)
|
||||
else:
|
||||
encryption = serialization.BestAvailableEncryption(to_bytes(self.passphrase))
|
||||
encryption = serialization.BestAvailableEncryption(
|
||||
to_bytes(self.passphrase)
|
||||
)
|
||||
|
||||
return serialize_key_and_certificates(
|
||||
friendly_name,
|
||||
@@ -699,8 +777,9 @@ class PkcsCryptography(Pkcs):
|
||||
|
||||
def parse_bytes(self, pkcs12_content):
|
||||
try:
|
||||
private_key, certificate, additional_certificates, friendly_name = parse_pkcs12(
|
||||
pkcs12_content, self.passphrase)
|
||||
private_key, certificate, additional_certificates, friendly_name = (
|
||||
parse_pkcs12(pkcs12_content, self.passphrase)
|
||||
)
|
||||
|
||||
pkey = None
|
||||
if private_key is not None:
|
||||
@@ -730,103 +809,133 @@ class PkcsCryptography(Pkcs):
|
||||
# self.pkcs12 = (pkey, cert, self.other_certificates, self.friendly_name)
|
||||
|
||||
def _dump_privatekey(self, pkcs12):
|
||||
return pkcs12[0].private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
) if pkcs12[0] else None
|
||||
return (
|
||||
pkcs12[0].private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
)
|
||||
if pkcs12[0]
|
||||
else None
|
||||
)
|
||||
|
||||
def _dump_certificate(self, pkcs12):
|
||||
return pkcs12[1].public_bytes(serialization.Encoding.PEM) if pkcs12[1] else None
|
||||
|
||||
def _dump_other_certificates(self, pkcs12):
|
||||
return [other_cert.public_bytes(serialization.Encoding.PEM) for other_cert in pkcs12[2]]
|
||||
return [
|
||||
other_cert.public_bytes(serialization.Encoding.PEM)
|
||||
for other_cert in pkcs12[2]
|
||||
]
|
||||
|
||||
def _get_friendly_name(self, pkcs12):
|
||||
return pkcs12[3]
|
||||
|
||||
|
||||
def select_backend(module, backend):
|
||||
if backend == 'auto':
|
||||
if backend == "auto":
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_cryptography = (
|
||||
CRYPTOGRAPHY_FOUND
|
||||
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
can_use_pyopenssl = (
|
||||
PYOPENSSL_FOUND and
|
||||
PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) and
|
||||
PYOPENSSL_VERSION < LooseVersion(MAXIMAL_PYOPENSSL_VERSION)
|
||||
PYOPENSSL_FOUND
|
||||
and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
and PYOPENSSL_VERSION < LooseVersion(MAXIMAL_PYOPENSSL_VERSION)
|
||||
)
|
||||
|
||||
# If no restrictions are provided, first try cryptography, then pyOpenSSL
|
||||
if (
|
||||
(module.params['iter_size'] is not None and module.params['encryption_level'] != 'compatibility2022')
|
||||
or module.params['maciter_size'] is not None
|
||||
):
|
||||
module.params["iter_size"] is not None
|
||||
and module.params["encryption_level"] != "compatibility2022"
|
||||
) or module.params["maciter_size"] is not None:
|
||||
# If iter_size (for encryption_level != compatibility2022) or maciter_size is specified, use pyOpenSSL backend
|
||||
backend = 'pyopenssl'
|
||||
backend = "pyopenssl"
|
||||
elif can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
backend = "cryptography"
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
backend = "pyopenssl"
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1}, < {2})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION,
|
||||
MAXIMAL_PYOPENSSL_VERSION))
|
||||
if backend == "auto":
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1}, < {2})"
|
||||
).format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION,
|
||||
MAXIMAL_PYOPENSSL_VERSION,
|
||||
)
|
||||
)
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if backend == "pyopenssl":
|
||||
if not PYOPENSSL_FOUND:
|
||||
msg = missing_required_lib(
|
||||
'pyOpenSSL >= {0}, < {1}'.format(MINIMAL_PYOPENSSL_VERSION, MAXIMAL_PYOPENSSL_VERSION)
|
||||
"pyOpenSSL >= {0}, < {1}".format(
|
||||
MINIMAL_PYOPENSSL_VERSION, MAXIMAL_PYOPENSSL_VERSION
|
||||
)
|
||||
)
|
||||
module.fail_json(msg=msg, exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='3.0.0', collection_name='community.crypto')
|
||||
module.deprecate(
|
||||
"The module is using the PyOpenSSL backend. This backend has been deprecated",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
return backend, PkcsPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
elif backend == "cryptography":
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
return backend, PkcsCryptography(module)
|
||||
else:
|
||||
raise ValueError('Unsupported value for backend: {0}'.format(backend))
|
||||
raise ValueError("Unsupported value for backend: {0}".format(backend))
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
action=dict(type='str', default='export', choices=['export', 'parse']),
|
||||
other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']),
|
||||
other_certificates_parse_all=dict(type='bool', default=False),
|
||||
other_certificates_content=dict(type='list', elements='str'),
|
||||
certificate_path=dict(type='path'),
|
||||
certificate_content=dict(type='str'),
|
||||
force=dict(type='bool', default=False),
|
||||
friendly_name=dict(type='str', aliases=['name']),
|
||||
encryption_level=dict(type='str', choices=['auto', 'compatibility2022'], default='auto'),
|
||||
iter_size=dict(type='int'),
|
||||
maciter_size=dict(type='int'),
|
||||
passphrase=dict(type='str', no_log=True),
|
||||
path=dict(type='path', required=True),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
src=dict(type='path'),
|
||||
backup=dict(type='bool', default=False),
|
||||
return_content=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
action=dict(type="str", default="export", choices=["export", "parse"]),
|
||||
other_certificates=dict(
|
||||
type="list", elements="path", aliases=["ca_certificates"]
|
||||
),
|
||||
other_certificates_parse_all=dict(type="bool", default=False),
|
||||
other_certificates_content=dict(type="list", elements="str"),
|
||||
certificate_path=dict(type="path"),
|
||||
certificate_content=dict(type="str"),
|
||||
force=dict(type="bool", default=False),
|
||||
friendly_name=dict(type="str", aliases=["name"]),
|
||||
encryption_level=dict(
|
||||
type="str", choices=["auto", "compatibility2022"], default="auto"
|
||||
),
|
||||
iter_size=dict(type="int"),
|
||||
maciter_size=dict(type="int"),
|
||||
passphrase=dict(type="str", no_log=True),
|
||||
path=dict(type="path", required=True),
|
||||
privatekey_passphrase=dict(type="str", no_log=True),
|
||||
privatekey_path=dict(type="path"),
|
||||
privatekey_content=dict(type="str", no_log=True),
|
||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
||||
src=dict(type="path"),
|
||||
backup=dict(type="bool", default=False),
|
||||
return_content=dict(type="bool", default=False),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography", "pyopenssl"]
|
||||
),
|
||||
)
|
||||
|
||||
required_if = [
|
||||
['action', 'parse', ['src']],
|
||||
["action", "parse", ["src"]],
|
||||
]
|
||||
|
||||
mutually_exclusive = [
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
['certificate_path', 'certificate_content'],
|
||||
['other_certificates', 'other_certificates_content'],
|
||||
["privatekey_path", "privatekey_content"],
|
||||
["certificate_path", "certificate_content"],
|
||||
["other_certificates", "other_certificates_content"],
|
||||
]
|
||||
|
||||
module = AnsibleModule(
|
||||
@@ -837,62 +946,69 @@ def main():
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
backend, pkcs12 = select_backend(module, module.params['select_crypto_backend'])
|
||||
backend, pkcs12 = select_backend(module, module.params["select_crypto_backend"])
|
||||
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg="The directory '%s' does not exist or the path is not a directory" % base_dir
|
||||
msg="The directory '%s' does not exist or the path is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
try:
|
||||
changed = False
|
||||
|
||||
if module.params['state'] == 'present':
|
||||
if module.params["state"] == "present":
|
||||
if module.check_mode:
|
||||
result = pkcs12.dump()
|
||||
result['changed'] = module.params['force'] or not pkcs12.check(module)
|
||||
result["changed"] = module.params["force"] or not pkcs12.check(module)
|
||||
module.exit_json(**result)
|
||||
|
||||
if not pkcs12.check(module, perms_required=False) or module.params['force']:
|
||||
if module.params['action'] == 'export':
|
||||
if not module.params['friendly_name']:
|
||||
module.fail_json(msg='Friendly_name is required')
|
||||
if not pkcs12.check(module, perms_required=False) or module.params["force"]:
|
||||
if module.params["action"] == "export":
|
||||
if not module.params["friendly_name"]:
|
||||
module.fail_json(msg="Friendly_name is required")
|
||||
pkcs12_content = pkcs12.generate_bytes(module)
|
||||
pkcs12.write(module, pkcs12_content, 0o600)
|
||||
changed = True
|
||||
else:
|
||||
pkey, cert, other_certs, friendly_name = pkcs12.parse()
|
||||
dump_content = ''.join([to_native(pem) for pem in [pkey, cert] + other_certs if pem is not None])
|
||||
dump_content = "".join(
|
||||
[
|
||||
to_native(pem)
|
||||
for pem in [pkey, cert] + other_certs
|
||||
if pem is not None
|
||||
]
|
||||
)
|
||||
pkcs12.write(module, to_bytes(dump_content))
|
||||
changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
changed = True
|
||||
elif module.set_fs_attributes_if_different(file_args, changed):
|
||||
changed = True
|
||||
else:
|
||||
if module.check_mode:
|
||||
result = pkcs12.dump()
|
||||
result['changed'] = os.path.exists(module.params['path'])
|
||||
result["changed"] = os.path.exists(module.params["path"])
|
||||
module.exit_json(**result)
|
||||
|
||||
if os.path.exists(module.params['path']):
|
||||
if os.path.exists(module.params["path"]):
|
||||
pkcs12.remove(module)
|
||||
changed = True
|
||||
|
||||
result = pkcs12.dump()
|
||||
result['changed'] = changed
|
||||
if os.path.exists(module.params['path']):
|
||||
file_mode = "%04o" % stat.S_IMODE(os.stat(module.params['path']).st_mode)
|
||||
result['mode'] = file_mode
|
||||
result["changed"] = changed
|
||||
if os.path.exists(module.params["path"]):
|
||||
file_mode = "%04o" % stat.S_IMODE(os.stat(module.params["path"]).st_mode)
|
||||
result["mode"] = file_mode
|
||||
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -182,21 +182,21 @@ class PrivateKeyModule(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, module_backend):
|
||||
super(PrivateKeyModule, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
self.module_backend = module_backend
|
||||
self.return_content = module.params['return_content']
|
||||
self.return_content = module.params["return_content"]
|
||||
if self.force:
|
||||
module_backend.regenerate = 'always'
|
||||
module_backend.regenerate = "always"
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
if module.params['mode'] is None:
|
||||
module.params['mode'] = '0600'
|
||||
if module.params["mode"] is None:
|
||||
module.params["mode"] = "0600"
|
||||
|
||||
module_backend.set_existing(load_file_if_exists(self.path, module))
|
||||
|
||||
@@ -227,10 +227,12 @@ class PrivateKeyModule(OpenSSLObject):
|
||||
self.changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
else:
|
||||
self.changed = module.set_fs_attributes_if_different(file_args, self.changed)
|
||||
self.changed = module.set_fs_attributes_if_different(
|
||||
file_args, self.changed
|
||||
)
|
||||
|
||||
def remove(self, module):
|
||||
self.module_backend.set_existing(None)
|
||||
@@ -242,10 +244,10 @@ class PrivateKeyModule(OpenSSLObject):
|
||||
"""Serialize the object into a dictionary."""
|
||||
|
||||
result = self.module_backend.dump(include_key=self.return_content)
|
||||
result['filename'] = self.path
|
||||
result['changed'] = self.changed
|
||||
result["filename"] = self.path
|
||||
result["changed"] = self.changed
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
|
||||
return result
|
||||
|
||||
@@ -253,34 +255,37 @@ class PrivateKeyModule(OpenSSLObject):
|
||||
def main():
|
||||
|
||||
argument_spec = get_privatekey_argument_spec()
|
||||
argument_spec.argument_spec.update(dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
force=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
return_content=dict(type='bool', default=False),
|
||||
))
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
force=dict(type="bool", default=False),
|
||||
path=dict(type="path", required=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
return_content=dict(type="bool", default=False),
|
||||
)
|
||||
)
|
||||
module = argument_spec.create_ansible_module(
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
)
|
||||
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||
msg="The directory %s does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
backend, module_backend = select_backend(
|
||||
module=module,
|
||||
backend=module.params['select_crypto_backend'],
|
||||
backend=module.params["select_crypto_backend"],
|
||||
)
|
||||
|
||||
try:
|
||||
private_key = PrivateKeyModule(module, module_backend)
|
||||
|
||||
if private_key.state == 'present':
|
||||
if private_key.state == "present":
|
||||
private_key.generate(module)
|
||||
else:
|
||||
private_key.remove(module)
|
||||
@@ -291,5 +296,5 @@ def main():
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -86,19 +86,19 @@ from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
class PrivateKeyConvertModule(OpenSSLObject):
|
||||
def __init__(self, module, module_backend):
|
||||
super(PrivateKeyConvertModule, self).__init__(
|
||||
module.params['dest_path'],
|
||||
'present',
|
||||
module.params["dest_path"],
|
||||
"present",
|
||||
False,
|
||||
module.check_mode,
|
||||
)
|
||||
self.module_backend = module_backend
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
module.params['path'] = module.params['dest_path']
|
||||
if module.params['mode'] is None:
|
||||
module.params['mode'] = '0600'
|
||||
module.params["path"] = module.params["dest_path"]
|
||||
if module.params["mode"] is None:
|
||||
module.params["mode"] = "0600"
|
||||
|
||||
module_backend.set_existing_destination(load_file_if_exists(self.path, module))
|
||||
|
||||
@@ -115,18 +115,20 @@ class PrivateKeyConvertModule(OpenSSLObject):
|
||||
self.changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
else:
|
||||
self.changed = module.set_fs_attributes_if_different(file_args, self.changed)
|
||||
self.changed = module.set_fs_attributes_if_different(
|
||||
file_args, self.changed
|
||||
)
|
||||
|
||||
def dump(self):
|
||||
"""Serialize the object into a dictionary."""
|
||||
|
||||
result = self.module_backend.dump()
|
||||
result['changed'] = self.changed
|
||||
result["changed"] = self.changed
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
|
||||
return result
|
||||
|
||||
@@ -134,20 +136,23 @@ class PrivateKeyConvertModule(OpenSSLObject):
|
||||
def main():
|
||||
|
||||
argument_spec = get_privatekey_argument_spec()
|
||||
argument_spec.argument_spec.update(dict(
|
||||
dest_path=dict(type='path', required=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
))
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
dest_path=dict(type="path", required=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
)
|
||||
)
|
||||
module = argument_spec.create_ansible_module(
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
)
|
||||
|
||||
base_dir = os.path.dirname(module.params['dest_path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["dest_path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||
msg="The directory %s does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
module_backend = select_backend(module=module)
|
||||
@@ -163,5 +168,5 @@ def main():
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -219,19 +219,17 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str', no_log=True),
|
||||
passphrase=dict(type='str', no_log=True),
|
||||
return_private_key_data=dict(type='bool', default=False),
|
||||
check_consistency=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
path=dict(type="path"),
|
||||
content=dict(type="str", no_log=True),
|
||||
passphrase=dict(type="str", no_log=True),
|
||||
return_private_key_data=dict(type="bool", default=False),
|
||||
check_consistency=dict(type="bool", default=False),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography"]
|
||||
),
|
||||
),
|
||||
required_one_of=(["path", "content"],),
|
||||
mutually_exclusive=(["path", "content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -241,24 +239,28 @@ def main():
|
||||
key_is_consistent=None,
|
||||
)
|
||||
|
||||
if module.params['content'] is not None:
|
||||
data = module.params['content'].encode('utf-8')
|
||||
if module.params["content"] is not None:
|
||||
data = module.params["content"].encode("utf-8")
|
||||
else:
|
||||
try:
|
||||
with open(module.params['path'], 'rb') as f:
|
||||
with open(module.params["path"], "rb") as f:
|
||||
data = f.read()
|
||||
except (IOError, OSError) as e:
|
||||
module.fail_json(msg='Error while reading private key file from disk: {0}'.format(e), **result)
|
||||
module.fail_json(
|
||||
msg="Error while reading private key file from disk: {0}".format(e),
|
||||
**result
|
||||
)
|
||||
|
||||
result['can_load_key'] = True
|
||||
result["can_load_key"] = True
|
||||
|
||||
backend, module_backend = select_backend(
|
||||
module,
|
||||
module.params['select_crypto_backend'],
|
||||
module.params["select_crypto_backend"],
|
||||
data,
|
||||
passphrase=module.params['passphrase'],
|
||||
return_private_key_data=module.params['return_private_key_data'],
|
||||
check_consistency=module.params['check_consistency'])
|
||||
passphrase=module.params["passphrase"],
|
||||
return_private_key_data=module.params["return_private_key_data"],
|
||||
check_consistency=module.params["check_consistency"],
|
||||
)
|
||||
|
||||
try:
|
||||
result.update(module_backend.get_info())
|
||||
|
||||
@@ -216,14 +216,15 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = '1.4'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2.3"
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = "1.4"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization as crypto_serialization
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -240,25 +241,25 @@ class PublicKey(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(PublicKey, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.check_mode
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
self.module = module
|
||||
self.format = module.params['format']
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
self.format = module.params["format"]
|
||||
self.privatekey_path = module.params["privatekey_path"]
|
||||
self.privatekey_content = module.params["privatekey_content"]
|
||||
if self.privatekey_content is not None:
|
||||
self.privatekey_content = self.privatekey_content.encode('utf-8')
|
||||
self.privatekey_passphrase = module.params['privatekey_passphrase']
|
||||
self.privatekey_content = self.privatekey_content.encode("utf-8")
|
||||
self.privatekey_passphrase = module.params["privatekey_passphrase"]
|
||||
self.privatekey = None
|
||||
self.publickey_bytes = None
|
||||
self.return_content = module.params['return_content']
|
||||
self.return_content = module.params["return_content"]
|
||||
self.fingerprint = {}
|
||||
self.backend = backend
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
self.diff_before = self._get_info(None)
|
||||
@@ -269,9 +270,12 @@ class PublicKey(OpenSSLObject):
|
||||
return dict()
|
||||
result = dict(can_parse_key=False)
|
||||
try:
|
||||
result.update(get_publickey_info(
|
||||
self.module, self.backend, content=data, prefer_one_fingerprint=True))
|
||||
result['can_parse_key'] = True
|
||||
result.update(
|
||||
get_publickey_info(
|
||||
self.module, self.backend, content=data, prefer_one_fingerprint=True
|
||||
)
|
||||
)
|
||||
result["can_parse_key"] = True
|
||||
except PublicKeyParseError as exc:
|
||||
result.update(exc.result)
|
||||
except Exception:
|
||||
@@ -283,18 +287,18 @@ class PublicKey(OpenSSLObject):
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend=self.backend
|
||||
backend=self.backend,
|
||||
)
|
||||
if self.backend == 'cryptography':
|
||||
if self.format == 'OpenSSH':
|
||||
if self.backend == "cryptography":
|
||||
if self.format == "OpenSSH":
|
||||
return self.privatekey.public_key().public_bytes(
|
||||
crypto_serialization.Encoding.OpenSSH,
|
||||
crypto_serialization.PublicFormat.OpenSSH
|
||||
crypto_serialization.PublicFormat.OpenSSH,
|
||||
)
|
||||
else:
|
||||
return self.privatekey.public_key().public_bytes(
|
||||
crypto_serialization.Encoding.PEM,
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
|
||||
def generate(self, module):
|
||||
@@ -302,7 +306,7 @@ class PublicKey(OpenSSLObject):
|
||||
|
||||
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
|
||||
raise PublicKeyError(
|
||||
'The private key %s does not exist' % self.privatekey_path
|
||||
"The private key %s does not exist" % self.privatekey_path
|
||||
)
|
||||
|
||||
if not self.check(module, perms_required=False) or self.force:
|
||||
@@ -329,7 +333,7 @@ class PublicKey(OpenSSLObject):
|
||||
backend=self.backend,
|
||||
)
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
elif module.set_fs_attributes_if_different(file_args, False):
|
||||
self.changed = True
|
||||
@@ -340,28 +344,34 @@ class PublicKey(OpenSSLObject):
|
||||
state_and_perms = super(PublicKey, self).check(module, perms_required)
|
||||
|
||||
def _check_privatekey():
|
||||
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
|
||||
if self.privatekey_content is None and not os.path.exists(
|
||||
self.privatekey_path
|
||||
):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(self.path, 'rb') as public_key_fh:
|
||||
with open(self.path, "rb") as public_key_fh:
|
||||
publickey_content = public_key_fh.read()
|
||||
self.diff_before = self.diff_after = self._get_info(publickey_content)
|
||||
if self.return_content:
|
||||
self.publickey_bytes = publickey_content
|
||||
if self.backend == 'cryptography':
|
||||
if self.format == 'OpenSSH':
|
||||
if self.backend == "cryptography":
|
||||
if self.format == "OpenSSH":
|
||||
# Read and dump public key. Makes sure that the comment is stripped off.
|
||||
current_publickey = crypto_serialization.load_ssh_public_key(publickey_content, backend=default_backend())
|
||||
current_publickey = crypto_serialization.load_ssh_public_key(
|
||||
publickey_content, backend=default_backend()
|
||||
)
|
||||
publickey_content = current_publickey.public_bytes(
|
||||
crypto_serialization.Encoding.OpenSSH,
|
||||
crypto_serialization.PublicFormat.OpenSSH
|
||||
crypto_serialization.PublicFormat.OpenSSH,
|
||||
)
|
||||
else:
|
||||
current_publickey = crypto_serialization.load_pem_public_key(publickey_content, backend=default_backend())
|
||||
current_publickey = crypto_serialization.load_pem_public_key(
|
||||
publickey_content, backend=default_backend()
|
||||
)
|
||||
publickey_content = current_publickey.public_bytes(
|
||||
crypto_serialization.Encoding.PEM,
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
@@ -387,20 +397,24 @@ class PublicKey(OpenSSLObject):
|
||||
"""Serialize the object into a dictionary."""
|
||||
|
||||
result = {
|
||||
'privatekey': self.privatekey_path,
|
||||
'filename': self.path,
|
||||
'format': self.format,
|
||||
'changed': self.changed,
|
||||
'fingerprint': self.fingerprint,
|
||||
"privatekey": self.privatekey_path,
|
||||
"filename": self.path,
|
||||
"format": self.format,
|
||||
"changed": self.changed,
|
||||
"fingerprint": self.fingerprint,
|
||||
}
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
if self.return_content:
|
||||
if self.publickey_bytes is None:
|
||||
self.publickey_bytes = load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['publickey'] = self.publickey_bytes.decode('utf-8') if self.publickey_bytes else None
|
||||
self.publickey_bytes = load_file_if_exists(
|
||||
self.path, ignore_errors=True
|
||||
)
|
||||
result["publickey"] = (
|
||||
self.publickey_bytes.decode("utf-8") if self.publickey_bytes else None
|
||||
)
|
||||
|
||||
result['diff'] = dict(
|
||||
result["diff"] = dict(
|
||||
before=self.diff_before,
|
||||
after=self.diff_after,
|
||||
)
|
||||
@@ -412,72 +426,87 @@ def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
force=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
format=dict(type='str', default='PEM', choices=['OpenSSH', 'PEM']),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
return_content=dict(type='bool', default=False),
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
force=dict(type="bool", default=False),
|
||||
path=dict(type="path", required=True),
|
||||
privatekey_path=dict(type="path"),
|
||||
privatekey_content=dict(type="str", no_log=True),
|
||||
format=dict(type="str", default="PEM", choices=["OpenSSH", "PEM"]),
|
||||
privatekey_passphrase=dict(type="str", no_log=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
select_crypto_backend=dict(
|
||||
type="str", choices=["auto", "cryptography"], default="auto"
|
||||
),
|
||||
return_content=dict(type="bool", default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
required_if=[('state', 'present', ['privatekey_path', 'privatekey_content'], True)],
|
||||
mutually_exclusive=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
),
|
||||
required_if=[
|
||||
("state", "present", ["privatekey_path", "privatekey_content"], True)
|
||||
],
|
||||
mutually_exclusive=(["privatekey_path", "privatekey_content"],),
|
||||
)
|
||||
|
||||
minimal_cryptography_version = MINIMAL_CRYPTOGRAPHY_VERSION
|
||||
if module.params['format'] == 'OpenSSH':
|
||||
if module.params["format"] == "OpenSSH":
|
||||
minimal_cryptography_version = MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
backend = module.params["select_crypto_backend"]
|
||||
if backend == "auto":
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(minimal_cryptography_version)
|
||||
can_use_cryptography = (
|
||||
CRYPTOGRAPHY_FOUND
|
||||
and CRYPTOGRAPHY_VERSION >= LooseVersion(minimal_cryptography_version)
|
||||
)
|
||||
|
||||
# Decision
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
backend = "cryptography"
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(minimal_cryptography_version))
|
||||
if backend == "auto":
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot detect the required Python library " "cryptography (>= {0})"
|
||||
).format(minimal_cryptography_version)
|
||||
)
|
||||
|
||||
if module.params['format'] == 'OpenSSH' and backend != 'cryptography':
|
||||
if module.params["format"] == "OpenSSH" and backend != "cryptography":
|
||||
module.fail_json(msg="Format OpenSSH requires the cryptography backend.")
|
||||
|
||||
if backend == 'cryptography':
|
||||
if backend == "cryptography":
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(minimal_cryptography_version)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(minimal_cryptography_version)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg="The directory '%s' does not exist or the file is not a directory" % base_dir
|
||||
msg="The directory '%s' does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
try:
|
||||
public_key = PublicKey(module, backend)
|
||||
|
||||
if public_key.state == 'present':
|
||||
if public_key.state == "present":
|
||||
if module.check_mode:
|
||||
result = public_key.dump()
|
||||
result['changed'] = module.params['force'] or not public_key.check(module)
|
||||
result["changed"] = module.params["force"] or not public_key.check(
|
||||
module
|
||||
)
|
||||
module.exit_json(**result)
|
||||
|
||||
public_key.generate(module)
|
||||
else:
|
||||
if module.check_mode:
|
||||
result = public_key.dump()
|
||||
result['changed'] = os.path.exists(module.params['path'])
|
||||
result["changed"] = os.path.exists(module.params["path"])
|
||||
module.exit_json(**result)
|
||||
|
||||
public_key.remove(module)
|
||||
@@ -488,5 +517,5 @@ def main():
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -170,16 +170,14 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str', no_log=True),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
path=dict(type="path"),
|
||||
content=dict(type="str", no_log=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography"]
|
||||
),
|
||||
),
|
||||
required_one_of=(["path", "content"],),
|
||||
mutually_exclusive=(["path", "content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
@@ -189,19 +187,21 @@ def main():
|
||||
key_is_consistent=None,
|
||||
)
|
||||
|
||||
if module.params['content'] is not None:
|
||||
data = module.params['content'].encode('utf-8')
|
||||
if module.params["content"] is not None:
|
||||
data = module.params["content"].encode("utf-8")
|
||||
else:
|
||||
try:
|
||||
with open(module.params['path'], 'rb') as f:
|
||||
with open(module.params["path"], "rb") as f:
|
||||
data = f.read()
|
||||
except (IOError, OSError) as e:
|
||||
module.fail_json(msg='Error while reading public key file from disk: {0}'.format(e), **result)
|
||||
module.fail_json(
|
||||
msg="Error while reading public key file from disk: {0}".format(e),
|
||||
**result
|
||||
)
|
||||
|
||||
backend, module_backend = select_backend(
|
||||
module,
|
||||
module.params['select_crypto_backend'],
|
||||
data)
|
||||
module, module.params["select_crypto_backend"], data
|
||||
)
|
||||
|
||||
try:
|
||||
result.update(module_backend.get_info())
|
||||
|
||||
@@ -113,13 +113,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "1.4"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.hazmat.primitives.asymmetric.padding
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -147,19 +148,19 @@ class SignatureBase(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureBase, self).__init__(
|
||||
path=module.params['path'],
|
||||
state='present',
|
||||
path=module.params["path"],
|
||||
state="present",
|
||||
force=False,
|
||||
check_mode=module.check_mode
|
||||
check_mode=module.check_mode,
|
||||
)
|
||||
|
||||
self.backend = backend
|
||||
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
self.privatekey_path = module.params["privatekey_path"]
|
||||
self.privatekey_content = module.params["privatekey_content"]
|
||||
if self.privatekey_content is not None:
|
||||
self.privatekey_content = self.privatekey_content.encode('utf-8')
|
||||
self.privatekey_passphrase = module.params['privatekey_passphrase']
|
||||
self.privatekey_content = self.privatekey_content.encode("utf-8")
|
||||
self.privatekey_passphrase = module.params["privatekey_passphrase"]
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
@@ -196,31 +197,50 @@ class SignatureCryptography(SignatureBase):
|
||||
signature = None
|
||||
|
||||
if CRYPTOGRAPHY_HAS_DSA_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
if isinstance(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey,
|
||||
):
|
||||
signature = private_key.sign(_in, _hash)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_EC_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||
signature = private_key.sign(_in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash))
|
||||
if isinstance(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey,
|
||||
):
|
||||
signature = private_key.sign(
|
||||
_in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash)
|
||||
)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED25519_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
|
||||
if isinstance(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey,
|
||||
):
|
||||
signature = private_key.sign(_in)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED448_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
|
||||
if isinstance(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey,
|
||||
):
|
||||
signature = private_key.sign(_in)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_RSA_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
if isinstance(
|
||||
private_key,
|
||||
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey,
|
||||
):
|
||||
signature = private_key.sign(_in, _padding, _hash)
|
||||
|
||||
if signature is None:
|
||||
self.module.fail_json(
|
||||
msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION)
|
||||
msg="Unsupported key type. Your cryptography version is {0}".format(
|
||||
CRYPTOGRAPHY_VERSION
|
||||
)
|
||||
)
|
||||
|
||||
result['signature'] = base64.b64encode(signature)
|
||||
result["signature"] = base64.b64encode(signature)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
@@ -230,45 +250,53 @@ class SignatureCryptography(SignatureBase):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
path=dict(type='path', required=True),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
),
|
||||
required_one_of=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
privatekey_path=dict(type="path"),
|
||||
privatekey_content=dict(type="str", no_log=True),
|
||||
privatekey_passphrase=dict(type="str", no_log=True),
|
||||
path=dict(type="path", required=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", choices=["auto", "cryptography"], default="auto"
|
||||
),
|
||||
),
|
||||
mutually_exclusive=(["privatekey_path", "privatekey_content"],),
|
||||
required_one_of=(["privatekey_path", "privatekey_content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not os.path.isfile(module.params['path']):
|
||||
if not os.path.isfile(module.params["path"]):
|
||||
module.fail_json(
|
||||
name=module.params['path'],
|
||||
msg='The file {0} does not exist'.format(module.params['path'])
|
||||
name=module.params["path"],
|
||||
msg="The file {0} does not exist".format(module.params["path"]),
|
||||
)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
backend = module.params["select_crypto_backend"]
|
||||
if backend == "auto":
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_cryptography = (
|
||||
CRYPTOGRAPHY_FOUND
|
||||
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
|
||||
# Decision
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
backend = "cryptography"
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Cannot detect the required Python library "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
if backend == "auto":
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot detect the required Python library " "cryptography (>= {0})"
|
||||
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
try:
|
||||
if backend == 'cryptography':
|
||||
if backend == "cryptography":
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
_sign = SignatureCryptography(module, backend)
|
||||
|
||||
result = _sign.run()
|
||||
@@ -278,5 +306,5 @@ def main():
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -102,13 +102,14 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "1.4"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.hazmat.primitives.asymmetric.padding
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -136,19 +137,19 @@ class SignatureInfoBase(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureInfoBase, self).__init__(
|
||||
path=module.params['path'],
|
||||
state='present',
|
||||
path=module.params["path"],
|
||||
state="present",
|
||||
force=False,
|
||||
check_mode=module.check_mode
|
||||
check_mode=module.check_mode,
|
||||
)
|
||||
|
||||
self.backend = backend
|
||||
|
||||
self.signature = module.params['signature']
|
||||
self.certificate_path = module.params['certificate_path']
|
||||
self.certificate_content = module.params['certificate_content']
|
||||
self.signature = module.params["signature"]
|
||||
self.certificate_path = module.params["certificate_path"]
|
||||
self.certificate_content = module.params["certificate_content"]
|
||||
if self.certificate_content is not None:
|
||||
self.certificate_content = self.certificate_content.encode('utf-8')
|
||||
self.certificate_content = self.certificate_content.encode("utf-8")
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
@@ -187,7 +188,10 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
if CRYPTOGRAPHY_HAS_DSA_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey):
|
||||
if isinstance(
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey,
|
||||
):
|
||||
public_key.verify(_signature, _in, _hash)
|
||||
verified = True
|
||||
valid = True
|
||||
@@ -197,8 +201,15 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
if CRYPTOGRAPHY_HAS_EC_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
|
||||
public_key.verify(_signature, _in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash))
|
||||
if isinstance(
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey,
|
||||
):
|
||||
public_key.verify(
|
||||
_signature,
|
||||
_in,
|
||||
cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash),
|
||||
)
|
||||
verified = True
|
||||
valid = True
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
@@ -207,7 +218,10 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED25519_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
|
||||
if isinstance(
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey,
|
||||
):
|
||||
public_key.verify(_signature, _in)
|
||||
verified = True
|
||||
valid = True
|
||||
@@ -217,7 +231,10 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED448_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
|
||||
if isinstance(
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey,
|
||||
):
|
||||
public_key.verify(_signature, _in)
|
||||
verified = True
|
||||
valid = True
|
||||
@@ -227,7 +244,10 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
if CRYPTOGRAPHY_HAS_RSA_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
|
||||
if isinstance(
|
||||
public_key,
|
||||
cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey,
|
||||
):
|
||||
public_key.verify(_signature, _in, _padding, _hash)
|
||||
verified = True
|
||||
valid = True
|
||||
@@ -237,9 +257,11 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
if not verified:
|
||||
self.module.fail_json(
|
||||
msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION)
|
||||
msg="Unsupported key type. Your cryptography version is {0}".format(
|
||||
CRYPTOGRAPHY_VERSION
|
||||
)
|
||||
)
|
||||
result['valid'] = valid
|
||||
result["valid"] = valid
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
@@ -249,45 +271,54 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
certificate_path=dict(type='path'),
|
||||
certificate_content=dict(type='str'),
|
||||
path=dict(type='path', required=True),
|
||||
signature=dict(type='str', required=True),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
),
|
||||
required_one_of=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
certificate_path=dict(type="path"),
|
||||
certificate_content=dict(type="str"),
|
||||
path=dict(type="path", required=True),
|
||||
signature=dict(type="str", required=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", choices=["auto", "cryptography"], default="auto"
|
||||
),
|
||||
),
|
||||
mutually_exclusive=(["certificate_path", "certificate_content"],),
|
||||
required_one_of=(["certificate_path", "certificate_content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not os.path.isfile(module.params['path']):
|
||||
if not os.path.isfile(module.params["path"]):
|
||||
module.fail_json(
|
||||
name=module.params['path'],
|
||||
msg='The file {0} does not exist'.format(module.params['path'])
|
||||
name=module.params["path"],
|
||||
msg="The file {0} does not exist".format(module.params["path"]),
|
||||
)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
backend = module.params["select_crypto_backend"]
|
||||
if backend == "auto":
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_cryptography = (
|
||||
CRYPTOGRAPHY_FOUND
|
||||
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
|
||||
# Decision
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
backend = "cryptography"
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
if backend == "auto":
|
||||
module.fail_json(
|
||||
msg=(
|
||||
"Cannot detect any of the required Python libraries "
|
||||
"cryptography (>= {0})"
|
||||
).format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
try:
|
||||
if backend == 'cryptography':
|
||||
if backend == "cryptography":
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
_sign = SignatureInfoCryptography(module, backend)
|
||||
|
||||
result = _sign.run()
|
||||
@@ -297,5 +328,5 @@ def main():
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -266,14 +266,14 @@ from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
class CertificateAbsent(OpenSSLObject):
|
||||
def __init__(self, module):
|
||||
super(CertificateAbsent, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.check_mode
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
self.module = module
|
||||
self.return_content = module.params['return_content']
|
||||
self.backup = module.params['backup']
|
||||
self.return_content = module.params["return_content"]
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
def generate(self, module):
|
||||
@@ -286,31 +286,32 @@ class CertificateAbsent(OpenSSLObject):
|
||||
|
||||
def dump(self, check_mode=False):
|
||||
result = {
|
||||
'changed': self.changed,
|
||||
'filename': self.path,
|
||||
'privatekey': self.module.params['privatekey_path'],
|
||||
'csr': self.module.params['csr_path']
|
||||
"changed": self.changed,
|
||||
"filename": self.path,
|
||||
"privatekey": self.module.params["privatekey_path"],
|
||||
"csr": self.module.params["csr_path"],
|
||||
}
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
if self.return_content:
|
||||
result['certificate'] = None
|
||||
result["certificate"] = None
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class GenericCertificate(OpenSSLObject):
|
||||
"""Retrieve a certificate using the given module backend."""
|
||||
|
||||
def __init__(self, module, module_backend):
|
||||
super(GenericCertificate, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.check_mode
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
self.module = module
|
||||
self.return_content = module.params['return_content']
|
||||
self.backup = module.params['backup']
|
||||
self.return_content = module.params["return_content"]
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
self.module_backend = module_backend
|
||||
@@ -327,23 +328,30 @@ class GenericCertificate(OpenSSLObject):
|
||||
self.changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
else:
|
||||
self.changed = module.set_fs_attributes_if_different(file_args, self.changed)
|
||||
self.changed = module.set_fs_attributes_if_different(
|
||||
file_args, self.changed
|
||||
)
|
||||
|
||||
def check(self, module, perms_required=True):
|
||||
"""Ensure the resource is in its desired state."""
|
||||
return super(GenericCertificate, self).check(module, perms_required) and not self.module_backend.needs_regeneration()
|
||||
return (
|
||||
super(GenericCertificate, self).check(module, perms_required)
|
||||
and not self.module_backend.needs_regeneration()
|
||||
)
|
||||
|
||||
def dump(self, check_mode=False):
|
||||
result = self.module_backend.dump(include_certificate=self.return_content)
|
||||
result.update({
|
||||
'changed': self.changed,
|
||||
'filename': self.path,
|
||||
})
|
||||
result.update(
|
||||
{
|
||||
"changed": self.changed,
|
||||
"filename": self.path,
|
||||
}
|
||||
)
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
return result
|
||||
|
||||
|
||||
@@ -353,46 +361,49 @@ def main():
|
||||
add_entrust_provider_to_argument_spec(argument_spec)
|
||||
add_ownca_provider_to_argument_spec(argument_spec)
|
||||
add_selfsigned_provider_to_argument_spec(argument_spec)
|
||||
argument_spec.argument_spec.update(dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
path=dict(type='path', required=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
return_content=dict(type='bool', default=False),
|
||||
))
|
||||
argument_spec.required_if.append(['state', 'present', ['provider']])
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
path=dict(type="path", required=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
return_content=dict(type="bool", default=False),
|
||||
)
|
||||
)
|
||||
argument_spec.required_if.append(["state", "present", ["provider"]])
|
||||
module = argument_spec.create_ansible_module(
|
||||
add_file_common_args=True,
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
try:
|
||||
if module.params['state'] == 'absent':
|
||||
if module.params["state"] == "absent":
|
||||
certificate = CertificateAbsent(module)
|
||||
|
||||
if module.check_mode:
|
||||
result = certificate.dump(check_mode=True)
|
||||
result['changed'] = os.path.exists(module.params['path'])
|
||||
result["changed"] = os.path.exists(module.params["path"])
|
||||
module.exit_json(**result)
|
||||
|
||||
certificate.remove(module)
|
||||
|
||||
else:
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||
msg="The directory %s does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
provider = module.params['provider']
|
||||
provider = module.params["provider"]
|
||||
provider_map = {
|
||||
'acme': AcmeCertificateProvider,
|
||||
'entrust': EntrustCertificateProvider,
|
||||
'ownca': OwnCACertificateProvider,
|
||||
'selfsigned': SelfSignedCertificateProvider,
|
||||
"acme": AcmeCertificateProvider,
|
||||
"entrust": EntrustCertificateProvider,
|
||||
"ownca": OwnCACertificateProvider,
|
||||
"selfsigned": SelfSignedCertificateProvider,
|
||||
}
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
backend = module.params["select_crypto_backend"]
|
||||
module_backend = select_backend(module, backend, provider_map[provider]())
|
||||
certificate = GenericCertificate(module, module_backend)
|
||||
certificate.generate(module)
|
||||
|
||||
@@ -137,7 +137,7 @@ from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "1.6"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -152,14 +152,22 @@ else:
|
||||
|
||||
|
||||
def parse_certificate(input, strict=False):
|
||||
input_format = 'pem' if identify_pem_format(input) else 'der'
|
||||
if input_format == 'pem':
|
||||
input_format = "pem" if identify_pem_format(input) else "der"
|
||||
if input_format == "pem":
|
||||
pems = split_pem_list(to_text(input))
|
||||
if len(pems) > 1 and strict:
|
||||
raise ValueError('The input contains {count} PEM objects, expecting only one since strict=true'.format(count=len(pems)))
|
||||
raise ValueError(
|
||||
"The input contains {count} PEM objects, expecting only one since strict=true".format(
|
||||
count=len(pems)
|
||||
)
|
||||
)
|
||||
pem_header_type, content = extract_pem(pems[0], strict=strict)
|
||||
if strict and pem_header_type not in ('CERTIFICATE', 'X509 CERTIFICATE'):
|
||||
raise ValueError('type is {type!r}, expecting CERTIFICATE or X509 CERTIFICATE'.format(type=pem_header_type))
|
||||
if strict and pem_header_type not in ("CERTIFICATE", "X509 CERTIFICATE"):
|
||||
raise ValueError(
|
||||
"type is {type!r}, expecting CERTIFICATE or X509 CERTIFICATE".format(
|
||||
type=pem_header_type
|
||||
)
|
||||
)
|
||||
input = base64.b64decode(content)
|
||||
else:
|
||||
pem_header_type = None
|
||||
@@ -169,66 +177,81 @@ def parse_certificate(input, strict=False):
|
||||
class X509CertificateConvertModule(OpenSSLObject):
|
||||
def __init__(self, module):
|
||||
super(X509CertificateConvertModule, self).__init__(
|
||||
module.params['dest_path'],
|
||||
'present',
|
||||
module.params["dest_path"],
|
||||
"present",
|
||||
False,
|
||||
module.check_mode,
|
||||
)
|
||||
|
||||
self.src_path = module.params['src_path']
|
||||
self.src_content = module.params['src_content']
|
||||
self.src_content_base64 = module.params['src_content_base64']
|
||||
self.src_path = module.params["src_path"]
|
||||
self.src_content = module.params["src_content"]
|
||||
self.src_content_base64 = module.params["src_content_base64"]
|
||||
if self.src_content is not None:
|
||||
self.input = to_bytes(self.src_content)
|
||||
if self.src_content_base64:
|
||||
try:
|
||||
self.input = base64.b64decode(self.input)
|
||||
except Exception as exc:
|
||||
module.fail_json(msg='Cannot Base64 decode src_content: {exc}'.format(exc=exc))
|
||||
module.fail_json(
|
||||
msg="Cannot Base64 decode src_content: {exc}".format(exc=exc)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
with open(self.src_path, 'rb') as f:
|
||||
with open(self.src_path, "rb") as f:
|
||||
self.input = f.read()
|
||||
except Exception as exc:
|
||||
module.fail_json(msg='Failure while reading file {fn}: {exc}'.format(fn=self.src_path, exc=exc))
|
||||
module.fail_json(
|
||||
msg="Failure while reading file {fn}: {exc}".format(
|
||||
fn=self.src_path, exc=exc
|
||||
)
|
||||
)
|
||||
|
||||
self.format = module.params['format']
|
||||
self.strict = module.params['strict']
|
||||
self.wanted_pem_type = 'CERTIFICATE'
|
||||
self.format = module.params["format"]
|
||||
self.strict = module.params["strict"]
|
||||
self.wanted_pem_type = "CERTIFICATE"
|
||||
|
||||
try:
|
||||
self.input, self.input_format, dummy = parse_certificate(self.input, strict=self.strict)
|
||||
self.input, self.input_format, dummy = parse_certificate(
|
||||
self.input, strict=self.strict
|
||||
)
|
||||
except Exception as exc:
|
||||
module.fail_json(msg='Error while parsing PEM: {exc}'.format(exc=exc))
|
||||
module.fail_json(msg="Error while parsing PEM: {exc}".format(exc=exc))
|
||||
|
||||
if module.params['verify_cert_parsable']:
|
||||
if module.params["verify_cert_parsable"]:
|
||||
self.verify_cert_parsable(module)
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
module.params['path'] = self.path
|
||||
module.params["path"] = self.path
|
||||
|
||||
self.dest_content = load_file_if_exists(self.path, module)
|
||||
self.dest_content_format = None
|
||||
self.dest_content_pem_type = None
|
||||
if self.dest_content is not None:
|
||||
try:
|
||||
self.dest_content, self.dest_content_format, self.dest_content_pem_type = parse_certificate(
|
||||
self.dest_content, strict=True)
|
||||
(
|
||||
self.dest_content,
|
||||
self.dest_content_format,
|
||||
self.dest_content_pem_type,
|
||||
) = parse_certificate(self.dest_content, strict=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def verify_cert_parsable(self, module):
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
try:
|
||||
load_der_x509_certificate(self.input, default_backend())
|
||||
except Exception as exc:
|
||||
module.fail_json(msg='Error while parsing certificate: {exc}'.format(exc=exc))
|
||||
module.fail_json(
|
||||
msg="Error while parsing certificate: {exc}".format(exc=exc)
|
||||
)
|
||||
|
||||
def needs_conversion(self):
|
||||
if self.dest_content is None or self.dest_content_format is None:
|
||||
@@ -237,18 +260,20 @@ class X509CertificateConvertModule(OpenSSLObject):
|
||||
return True
|
||||
if self.input != self.dest_content:
|
||||
return True
|
||||
if self.format == 'pem' and self.dest_content_pem_type != self.wanted_pem_type:
|
||||
if self.format == "pem" and self.dest_content_pem_type != self.wanted_pem_type:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_dest_certificate(self):
|
||||
if self.format == 'der':
|
||||
if self.format == "der":
|
||||
return self.input
|
||||
data = to_bytes(base64.b64encode(self.input))
|
||||
lines = [to_bytes('{0}{1}{2}'.format(PEM_START, self.wanted_pem_type, PEM_END))]
|
||||
lines += [data[i:i + 64] for i in range(0, len(data), 64)]
|
||||
lines.append(to_bytes('{0}{1}{2}\n'.format(PEM_END_START, self.wanted_pem_type, PEM_END)))
|
||||
return b'\n'.join(lines)
|
||||
lines = [to_bytes("{0}{1}{2}".format(PEM_START, self.wanted_pem_type, PEM_END))]
|
||||
lines += [data[i : i + 64] for i in range(0, len(data), 64)]
|
||||
lines.append(
|
||||
to_bytes("{0}{1}{2}\n".format(PEM_END_START, self.wanted_pem_type, PEM_END))
|
||||
)
|
||||
return b"\n".join(lines)
|
||||
|
||||
def generate(self, module):
|
||||
"""Do conversion."""
|
||||
@@ -262,10 +287,12 @@ class X509CertificateConvertModule(OpenSSLObject):
|
||||
self.changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
else:
|
||||
self.changed = module.set_fs_attributes_if_different(file_args, self.changed)
|
||||
self.changed = module.set_fs_attributes_if_different(
|
||||
file_args, self.changed
|
||||
)
|
||||
|
||||
def dump(self):
|
||||
"""Serialize the object into a dictionary."""
|
||||
@@ -273,35 +300,36 @@ class X509CertificateConvertModule(OpenSSLObject):
|
||||
changed=self.changed,
|
||||
)
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = dict(
|
||||
src_path=dict(type='path'),
|
||||
src_content=dict(type='str'),
|
||||
src_content_base64=dict(type='bool', default=False),
|
||||
format=dict(type='str', required=True, choices=['pem', 'der']),
|
||||
strict=dict(type='bool', default=False),
|
||||
dest_path=dict(type='path', required=True),
|
||||
backup=dict(type='bool', default=False),
|
||||
verify_cert_parsable=dict(type='bool', default=False),
|
||||
src_path=dict(type="path"),
|
||||
src_content=dict(type="str"),
|
||||
src_content_base64=dict(type="bool", default=False),
|
||||
format=dict(type="str", required=True, choices=["pem", "der"]),
|
||||
strict=dict(type="bool", default=False),
|
||||
dest_path=dict(type="path", required=True),
|
||||
backup=dict(type="bool", default=False),
|
||||
verify_cert_parsable=dict(type="bool", default=False),
|
||||
)
|
||||
module = AnsibleModule(
|
||||
argument_spec,
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
required_one_of=[('src_path', 'src_content')],
|
||||
mutually_exclusive=[('src_path', 'src_content')],
|
||||
required_one_of=[("src_path", "src_content")],
|
||||
mutually_exclusive=[("src_path", "src_content")],
|
||||
)
|
||||
|
||||
base_dir = os.path.dirname(module.params['dest_path']) or '.'
|
||||
base_dir = os.path.dirname(module.params["dest_path"]) or "."
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||
msg="The directory %s does not exist or the file is not a directory"
|
||||
% base_dir,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -313,5 +341,5 @@ def main():
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -414,51 +414,61 @@ from ansible_collections.community.crypto.plugins.module_utils.time import (
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
valid_at=dict(type='dict'),
|
||||
name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
path=dict(type="path"),
|
||||
content=dict(type="str"),
|
||||
valid_at=dict(type="dict"),
|
||||
name_encoding=dict(
|
||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
||||
),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "cryptography"]
|
||||
),
|
||||
),
|
||||
required_one_of=(["path", "content"],),
|
||||
mutually_exclusive=(["path", "content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if module.params['content'] is not None:
|
||||
data = module.params['content'].encode('utf-8')
|
||||
if module.params["content"] is not None:
|
||||
data = module.params["content"].encode("utf-8")
|
||||
else:
|
||||
try:
|
||||
with open(module.params['path'], 'rb') as f:
|
||||
with open(module.params["path"], "rb") as f:
|
||||
data = f.read()
|
||||
except (IOError, OSError) as e:
|
||||
module.fail_json(msg='Error while reading certificate file from disk: {0}'.format(e))
|
||||
module.fail_json(
|
||||
msg="Error while reading certificate file from disk: {0}".format(e)
|
||||
)
|
||||
|
||||
backend, module_backend = select_backend(module, module.params['select_crypto_backend'], data)
|
||||
backend, module_backend = select_backend(
|
||||
module, module.params["select_crypto_backend"], data
|
||||
)
|
||||
|
||||
valid_at = module.params['valid_at']
|
||||
valid_at = module.params["valid_at"]
|
||||
if valid_at:
|
||||
for k, v in valid_at.items():
|
||||
if not isinstance(v, string_types):
|
||||
module.fail_json(
|
||||
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
|
||||
msg="The value for valid_at.{0} must be of type string (got {1})".format(
|
||||
k, type(v)
|
||||
)
|
||||
)
|
||||
valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k), with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||
valid_at[k] = get_relative_time_option(
|
||||
v, "valid_at.{0}".format(k), with_timezone=CRYPTOGRAPHY_TIMEZONE
|
||||
)
|
||||
|
||||
try:
|
||||
result = module_backend.get_info(der_support_enabled=module.params['content'] is None)
|
||||
result = module_backend.get_info(
|
||||
der_support_enabled=module.params["content"] is None
|
||||
)
|
||||
|
||||
not_before = module_backend.get_not_before()
|
||||
not_after = module_backend.get_not_after()
|
||||
|
||||
result['valid_at'] = dict()
|
||||
result["valid_at"] = dict()
|
||||
if valid_at:
|
||||
for k, v in valid_at.items():
|
||||
result['valid_at'][k] = not_before <= v <= not_after
|
||||
result["valid_at"][k] = not_before <= v <= not_after
|
||||
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
|
||||
@@ -151,13 +151,14 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
|
||||
|
||||
class GenericCertificate(object):
|
||||
"""Retrieve a certificate using the given module backend."""
|
||||
|
||||
def __init__(self, module, module_backend):
|
||||
self.check_mode = module.check_mode
|
||||
self.module = module
|
||||
self.module_backend = module_backend
|
||||
self.changed = False
|
||||
if module.params['content'] is not None:
|
||||
self.module_backend.set_existing(module.params['content'].encode('utf-8'))
|
||||
if module.params["content"] is not None:
|
||||
self.module_backend.set_existing(module.params["content"].encode("utf-8"))
|
||||
|
||||
def generate(self, module):
|
||||
if self.module_backend.needs_regeneration():
|
||||
@@ -165,46 +166,50 @@ class GenericCertificate(object):
|
||||
self.module_backend.generate_certificate()
|
||||
else:
|
||||
self.module.deprecate(
|
||||
'Check mode support for x509_certificate_pipe will change in community.crypto 3.0.0'
|
||||
' to behave the same as without check mode. You can get that behavior right now'
|
||||
' by adding `check_mode: false` to the x509_certificate_pipe task. If you think this'
|
||||
' breaks your use-case of this module, please create an issue in the'
|
||||
' community.crypto repository',
|
||||
version='3.0.0',
|
||||
collection_name='community.crypto',
|
||||
"Check mode support for x509_certificate_pipe will change in community.crypto 3.0.0"
|
||||
" to behave the same as without check mode. You can get that behavior right now"
|
||||
" by adding `check_mode: false` to the x509_certificate_pipe task. If you think this"
|
||||
" breaks your use-case of this module, please create an issue in the"
|
||||
" community.crypto repository",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
self.changed = True
|
||||
|
||||
def dump(self, check_mode=False):
|
||||
result = self.module_backend.dump(include_certificate=True)
|
||||
result.update({
|
||||
'changed': self.changed,
|
||||
})
|
||||
result.update(
|
||||
{
|
||||
"changed": self.changed,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = get_certificate_argument_spec()
|
||||
argument_spec.argument_spec['provider']['required'] = True
|
||||
argument_spec.argument_spec["provider"]["required"] = True
|
||||
add_entrust_provider_to_argument_spec(argument_spec)
|
||||
add_ownca_provider_to_argument_spec(argument_spec)
|
||||
add_selfsigned_provider_to_argument_spec(argument_spec)
|
||||
argument_spec.argument_spec.update(dict(
|
||||
content=dict(type='str'),
|
||||
))
|
||||
argument_spec.argument_spec.update(
|
||||
dict(
|
||||
content=dict(type="str"),
|
||||
)
|
||||
)
|
||||
module = argument_spec.create_ansible_module(
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
try:
|
||||
provider = module.params['provider']
|
||||
provider = module.params["provider"]
|
||||
provider_map = {
|
||||
'entrust': EntrustCertificateProvider,
|
||||
'ownca': OwnCACertificateProvider,
|
||||
'selfsigned': SelfSignedCertificateProvider,
|
||||
"entrust": EntrustCertificateProvider,
|
||||
"ownca": OwnCACertificateProvider,
|
||||
"selfsigned": SelfSignedCertificateProvider,
|
||||
}
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
backend = module.params["select_crypto_backend"]
|
||||
module_backend = select_backend(module, backend, provider_map[provider]())
|
||||
certificate = GenericCertificate(module, module_backend)
|
||||
certificate.generate(module)
|
||||
|
||||
@@ -496,7 +496,7 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = "1.2"
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -510,6 +510,7 @@ try:
|
||||
NameAttribute,
|
||||
RevokedCertificateBuilder,
|
||||
)
|
||||
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
@@ -526,100 +527,122 @@ class CRL(OpenSSLObject):
|
||||
|
||||
def __init__(self, module):
|
||||
super(CRL, self).__init__(
|
||||
module.params['path'],
|
||||
module.params['state'],
|
||||
module.params['force'],
|
||||
module.check_mode
|
||||
module.params["path"],
|
||||
module.params["state"],
|
||||
module.params["force"],
|
||||
module.check_mode,
|
||||
)
|
||||
|
||||
self.format = module.params['format']
|
||||
self.format = module.params["format"]
|
||||
|
||||
self.update = module.params['crl_mode'] == 'update'
|
||||
self.ignore_timestamps = module.params['ignore_timestamps']
|
||||
self.return_content = module.params['return_content']
|
||||
self.name_encoding = module.params['name_encoding']
|
||||
self.serial_numbers_format = module.params['serial_numbers']
|
||||
self.update = module.params["crl_mode"] == "update"
|
||||
self.ignore_timestamps = module.params["ignore_timestamps"]
|
||||
self.return_content = module.params["return_content"]
|
||||
self.name_encoding = module.params["name_encoding"]
|
||||
self.serial_numbers_format = module.params["serial_numbers"]
|
||||
self.crl_content = None
|
||||
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
self.privatekey_path = module.params["privatekey_path"]
|
||||
self.privatekey_content = module.params["privatekey_content"]
|
||||
if self.privatekey_content is not None:
|
||||
self.privatekey_content = self.privatekey_content.encode('utf-8')
|
||||
self.privatekey_passphrase = module.params['privatekey_passphrase']
|
||||
self.privatekey_content = self.privatekey_content.encode("utf-8")
|
||||
self.privatekey_passphrase = module.params["privatekey_passphrase"]
|
||||
|
||||
try:
|
||||
if module.params['issuer_ordered']:
|
||||
if module.params["issuer_ordered"]:
|
||||
self.issuer_ordered = True
|
||||
self.issuer = parse_ordered_name_field(module.params['issuer_ordered'], 'issuer_ordered')
|
||||
self.issuer = parse_ordered_name_field(
|
||||
module.params["issuer_ordered"], "issuer_ordered"
|
||||
)
|
||||
else:
|
||||
self.issuer_ordered = False
|
||||
self.issuer = parse_name_field(module.params['issuer'], 'issuer')
|
||||
self.issuer = parse_name_field(module.params["issuer"], "issuer")
|
||||
except (TypeError, ValueError) as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
self.last_update = get_relative_time_option(module.params['last_update'], 'last_update', with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||
self.next_update = get_relative_time_option(module.params['next_update'], 'next_update', with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||
self.last_update = get_relative_time_option(
|
||||
module.params["last_update"],
|
||||
"last_update",
|
||||
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||
)
|
||||
self.next_update = get_relative_time_option(
|
||||
module.params["next_update"],
|
||||
"next_update",
|
||||
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||
)
|
||||
|
||||
self.digest = select_message_digest(module.params['digest'])
|
||||
self.digest = select_message_digest(module.params["digest"])
|
||||
if self.digest is None:
|
||||
raise CRLError('The digest "{0}" is not supported'.format(module.params['digest']))
|
||||
raise CRLError(
|
||||
'The digest "{0}" is not supported'.format(module.params["digest"])
|
||||
)
|
||||
|
||||
self.module = module
|
||||
|
||||
self.revoked_certificates = []
|
||||
for i, rc in enumerate(module.params['revoked_certificates']):
|
||||
for i, rc in enumerate(module.params["revoked_certificates"]):
|
||||
result = {
|
||||
'serial_number': None,
|
||||
'revocation_date': None,
|
||||
'issuer': None,
|
||||
'issuer_critical': False,
|
||||
'reason': None,
|
||||
'reason_critical': False,
|
||||
'invalidity_date': None,
|
||||
'invalidity_date_critical': False,
|
||||
"serial_number": None,
|
||||
"revocation_date": None,
|
||||
"issuer": None,
|
||||
"issuer_critical": False,
|
||||
"reason": None,
|
||||
"reason_critical": False,
|
||||
"invalidity_date": None,
|
||||
"invalidity_date_critical": False,
|
||||
}
|
||||
path_prefix = 'revoked_certificates[{0}].'.format(i)
|
||||
if rc['path'] is not None or rc['content'] is not None:
|
||||
path_prefix = "revoked_certificates[{0}].".format(i)
|
||||
if rc["path"] is not None or rc["content"] is not None:
|
||||
# Load certificate from file or content
|
||||
try:
|
||||
if rc['content'] is not None:
|
||||
rc['content'] = rc['content'].encode('utf-8')
|
||||
cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography')
|
||||
result['serial_number'] = cryptography_serial_number_of_cert(cert)
|
||||
if rc["content"] is not None:
|
||||
rc["content"] = rc["content"].encode("utf-8")
|
||||
cert = load_certificate(
|
||||
rc["path"], content=rc["content"], backend="cryptography"
|
||||
)
|
||||
result["serial_number"] = cryptography_serial_number_of_cert(cert)
|
||||
except OpenSSLObjectError as e:
|
||||
if rc['content'] is not None:
|
||||
if rc["content"] is not None:
|
||||
module.fail_json(
|
||||
msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e))
|
||||
msg="Cannot parse certificate from {0}content: {1}".format(
|
||||
path_prefix, to_native(e)
|
||||
)
|
||||
)
|
||||
else:
|
||||
module.fail_json(
|
||||
msg='Cannot read certificate "{1}" from {0}path: {2}'.format(path_prefix, rc['path'], to_native(e))
|
||||
msg='Cannot read certificate "{1}" from {0}path: {2}'.format(
|
||||
path_prefix, rc["path"], to_native(e)
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Specify serial_number (and potentially issuer) directly
|
||||
result['serial_number'] = self._parse_serial_number(rc['serial_number'], i)
|
||||
result["serial_number"] = self._parse_serial_number(
|
||||
rc["serial_number"], i
|
||||
)
|
||||
# All other options
|
||||
if rc['issuer']:
|
||||
result['issuer'] = [cryptography_get_name(issuer, 'issuer') for issuer in rc['issuer']]
|
||||
result['issuer_critical'] = rc['issuer_critical']
|
||||
result['revocation_date'] = get_relative_time_option(
|
||||
rc['revocation_date'],
|
||||
path_prefix + 'revocation_date',
|
||||
if rc["issuer"]:
|
||||
result["issuer"] = [
|
||||
cryptography_get_name(issuer, "issuer") for issuer in rc["issuer"]
|
||||
]
|
||||
result["issuer_critical"] = rc["issuer_critical"]
|
||||
result["revocation_date"] = get_relative_time_option(
|
||||
rc["revocation_date"],
|
||||
path_prefix + "revocation_date",
|
||||
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||
)
|
||||
if rc['reason']:
|
||||
result['reason'] = REVOCATION_REASON_MAP[rc['reason']]
|
||||
result['reason_critical'] = rc['reason_critical']
|
||||
if rc['invalidity_date']:
|
||||
result['invalidity_date'] = get_relative_time_option(
|
||||
rc['invalidity_date'],
|
||||
path_prefix + 'invalidity_date',
|
||||
if rc["reason"]:
|
||||
result["reason"] = REVOCATION_REASON_MAP[rc["reason"]]
|
||||
result["reason_critical"] = rc["reason_critical"]
|
||||
if rc["invalidity_date"]:
|
||||
result["invalidity_date"] = get_relative_time_option(
|
||||
rc["invalidity_date"],
|
||||
path_prefix + "invalidity_date",
|
||||
with_timezone=CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE,
|
||||
)
|
||||
result['invalidity_date_critical'] = rc['invalidity_date_critical']
|
||||
result["invalidity_date_critical"] = rc["invalidity_date_critical"]
|
||||
self.revoked_certificates.append(result)
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup = module.params["backup"]
|
||||
self.backup_file = None
|
||||
|
||||
try:
|
||||
@@ -627,17 +650,17 @@ class CRL(OpenSSLObject):
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend='cryptography'
|
||||
backend="cryptography",
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
raise CRLError(exc)
|
||||
|
||||
self.crl = None
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
with open(self.path, "rb") as f:
|
||||
data = f.read()
|
||||
self.actual_format = 'pem' if identify_pem_format(data) else 'der'
|
||||
if self.actual_format == 'pem':
|
||||
self.actual_format = "pem" if identify_pem_format(data) else "der"
|
||||
if self.actual_format == "pem":
|
||||
self.crl = x509.load_pem_x509_crl(data, default_backend())
|
||||
if self.return_content:
|
||||
self.crl_content = data
|
||||
@@ -653,30 +676,36 @@ class CRL(OpenSSLObject):
|
||||
self.diff_after = self.diff_before = self._get_info(data)
|
||||
|
||||
def _parse_serial_number(self, value, index):
|
||||
if self.serial_numbers_format == 'integer':
|
||||
if self.serial_numbers_format == "integer":
|
||||
try:
|
||||
return check_type_int(value)
|
||||
except TypeError as exc:
|
||||
self.module.fail_json(msg='Error while parsing revoked_certificates[{idx}].serial_number as an integer: {exc}'.format(
|
||||
idx=index + 1,
|
||||
exc=to_native(exc),
|
||||
))
|
||||
if self.serial_numbers_format == 'hex-octets':
|
||||
self.module.fail_json(
|
||||
msg="Error while parsing revoked_certificates[{idx}].serial_number as an integer: {exc}".format(
|
||||
idx=index + 1,
|
||||
exc=to_native(exc),
|
||||
)
|
||||
)
|
||||
if self.serial_numbers_format == "hex-octets":
|
||||
try:
|
||||
return parse_serial(check_type_str(value))
|
||||
except (TypeError, ValueError) as exc:
|
||||
self.module.fail_json(msg='Error while parsing revoked_certificates[{idx}].serial_number as an colon-separated hex octet string: {exc}'.format(
|
||||
idx=index + 1,
|
||||
exc=to_native(exc),
|
||||
))
|
||||
raise RuntimeError('Unexpected value %s of serial_numbers' % (self.serial_numbers_format, ))
|
||||
self.module.fail_json(
|
||||
msg="Error while parsing revoked_certificates[{idx}].serial_number as an colon-separated hex octet string: {exc}".format(
|
||||
idx=index + 1,
|
||||
exc=to_native(exc),
|
||||
)
|
||||
)
|
||||
raise RuntimeError(
|
||||
"Unexpected value %s of serial_numbers" % (self.serial_numbers_format,)
|
||||
)
|
||||
|
||||
def _get_info(self, data):
|
||||
if data is None:
|
||||
return dict()
|
||||
try:
|
||||
result = get_crl_info(self.module, data)
|
||||
result['can_parse_crl'] = True
|
||||
result["can_parse_crl"] = True
|
||||
return result
|
||||
except Exception:
|
||||
return dict(can_parse_crl=False)
|
||||
@@ -688,35 +717,38 @@ class CRL(OpenSSLObject):
|
||||
|
||||
def _compress_entry(self, entry):
|
||||
issuer = None
|
||||
if entry['issuer'] is not None:
|
||||
if entry["issuer"] is not None:
|
||||
# Normalize to IDNA. If this is used-provided, it was already converted to
|
||||
# IDNA (by cryptography_get_name) and thus the `idna` library is present.
|
||||
# If this is coming from cryptography and is not already in IDNA (i.e. ascii),
|
||||
# cryptography < 2.1 must be in use, which depends on `idna`. So this should
|
||||
# not require `idna` except if it was already used by code earlier during
|
||||
# this invocation.
|
||||
issuer = tuple(cryptography_decode_name(issuer, idn_rewrite='idna') for issuer in entry['issuer'])
|
||||
issuer = tuple(
|
||||
cryptography_decode_name(issuer, idn_rewrite="idna")
|
||||
for issuer in entry["issuer"]
|
||||
)
|
||||
if self.ignore_timestamps:
|
||||
# Throw out revocation_date
|
||||
return (
|
||||
entry['serial_number'],
|
||||
entry["serial_number"],
|
||||
issuer,
|
||||
entry['issuer_critical'],
|
||||
entry['reason'],
|
||||
entry['reason_critical'],
|
||||
entry['invalidity_date'],
|
||||
entry['invalidity_date_critical'],
|
||||
entry["issuer_critical"],
|
||||
entry["reason"],
|
||||
entry["reason_critical"],
|
||||
entry["invalidity_date"],
|
||||
entry["invalidity_date_critical"],
|
||||
)
|
||||
else:
|
||||
return (
|
||||
entry['serial_number'],
|
||||
entry['revocation_date'],
|
||||
entry["serial_number"],
|
||||
entry["revocation_date"],
|
||||
issuer,
|
||||
entry['issuer_critical'],
|
||||
entry['reason'],
|
||||
entry['reason_critical'],
|
||||
entry['invalidity_date'],
|
||||
entry['invalidity_date_critical'],
|
||||
entry["issuer_critical"],
|
||||
entry["reason"],
|
||||
entry["reason_critical"],
|
||||
entry["invalidity_date"],
|
||||
entry["invalidity_date_critical"],
|
||||
)
|
||||
|
||||
def check(self, module, perms_required=True, ignore_conversion=True):
|
||||
@@ -735,13 +767,18 @@ class CRL(OpenSSLObject):
|
||||
if self.next_update != get_next_update(self.crl) and not self.ignore_timestamps:
|
||||
return False
|
||||
if cryptography_key_needs_digest_for_signing(self.privatekey):
|
||||
if self.crl.signature_hash_algorithm is None or self.digest.name != self.crl.signature_hash_algorithm.name:
|
||||
if (
|
||||
self.crl.signature_hash_algorithm is None
|
||||
or self.digest.name != self.crl.signature_hash_algorithm.name
|
||||
):
|
||||
return False
|
||||
else:
|
||||
if self.crl.signature_hash_algorithm is not None:
|
||||
return False
|
||||
|
||||
want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer]
|
||||
want_issuer = [
|
||||
(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer
|
||||
]
|
||||
is_issuer = [(sub.oid, sub.value) for sub in self.crl.issuer]
|
||||
if not self.issuer_ordered:
|
||||
want_issuer = set(want_issuer)
|
||||
@@ -749,7 +786,10 @@ class CRL(OpenSSLObject):
|
||||
if want_issuer != is_issuer:
|
||||
return False
|
||||
|
||||
old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl]
|
||||
old_entries = [
|
||||
self._compress_entry(cryptography_decode_revoked_certificate(cert))
|
||||
for cert in self.crl
|
||||
]
|
||||
new_entries = [self._compress_entry(cert) for cert in self.revoked_certificates]
|
||||
if self.update:
|
||||
# We do not simply use a set so that duplicate entries are treated correctly
|
||||
@@ -772,10 +812,16 @@ class CRL(OpenSSLObject):
|
||||
crl = CertificateRevocationListBuilder()
|
||||
|
||||
try:
|
||||
crl = crl.issuer_name(Name([
|
||||
NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1]))
|
||||
for entry in self.issuer
|
||||
]))
|
||||
crl = crl.issuer_name(
|
||||
Name(
|
||||
[
|
||||
NameAttribute(
|
||||
cryptography_name_to_oid(entry[0]), to_text(entry[1])
|
||||
)
|
||||
for entry in self.issuer
|
||||
]
|
||||
)
|
||||
)
|
||||
except ValueError as e:
|
||||
raise CRLError(e)
|
||||
|
||||
@@ -783,29 +829,31 @@ class CRL(OpenSSLObject):
|
||||
crl = set_next_update(crl, self.next_update)
|
||||
|
||||
if self.update and self.crl:
|
||||
new_entries = set([self._compress_entry(entry) for entry in self.revoked_certificates])
|
||||
new_entries = set(
|
||||
[self._compress_entry(entry) for entry in self.revoked_certificates]
|
||||
)
|
||||
for entry in self.crl:
|
||||
decoded_entry = self._compress_entry(cryptography_decode_revoked_certificate(entry))
|
||||
decoded_entry = self._compress_entry(
|
||||
cryptography_decode_revoked_certificate(entry)
|
||||
)
|
||||
if decoded_entry not in new_entries:
|
||||
crl = crl.add_revoked_certificate(entry)
|
||||
for entry in self.revoked_certificates:
|
||||
revoked_cert = RevokedCertificateBuilder()
|
||||
revoked_cert = revoked_cert.serial_number(entry['serial_number'])
|
||||
revoked_cert = set_revocation_date(revoked_cert, entry['revocation_date'])
|
||||
if entry['issuer'] is not None:
|
||||
revoked_cert = revoked_cert.serial_number(entry["serial_number"])
|
||||
revoked_cert = set_revocation_date(revoked_cert, entry["revocation_date"])
|
||||
if entry["issuer"] is not None:
|
||||
revoked_cert = revoked_cert.add_extension(
|
||||
x509.CertificateIssuer(entry['issuer']),
|
||||
entry['issuer_critical']
|
||||
x509.CertificateIssuer(entry["issuer"]), entry["issuer_critical"]
|
||||
)
|
||||
if entry['reason'] is not None:
|
||||
if entry["reason"] is not None:
|
||||
revoked_cert = revoked_cert.add_extension(
|
||||
x509.CRLReason(entry['reason']),
|
||||
entry['reason_critical']
|
||||
x509.CRLReason(entry["reason"]), entry["reason_critical"]
|
||||
)
|
||||
if entry['invalidity_date'] is not None:
|
||||
if entry["invalidity_date"] is not None:
|
||||
revoked_cert = revoked_cert.add_extension(
|
||||
x509.InvalidityDate(entry['invalidity_date']),
|
||||
entry['invalidity_date_critical']
|
||||
x509.InvalidityDate(entry["invalidity_date"]),
|
||||
entry["invalidity_date_critical"],
|
||||
)
|
||||
crl = crl.add_revoked_certificate(revoked_cert.build(backend))
|
||||
|
||||
@@ -813,17 +861,23 @@ class CRL(OpenSSLObject):
|
||||
if cryptography_key_needs_digest_for_signing(self.privatekey):
|
||||
digest = self.digest
|
||||
self.crl = crl.sign(self.privatekey, digest, backend=backend)
|
||||
if self.format == 'pem':
|
||||
if self.format == "pem":
|
||||
return self.crl.public_bytes(Encoding.PEM)
|
||||
else:
|
||||
return self.crl.public_bytes(Encoding.DER)
|
||||
|
||||
def generate(self):
|
||||
result = None
|
||||
if not self.check(self.module, perms_required=False, ignore_conversion=True) or self.force:
|
||||
if (
|
||||
not self.check(self.module, perms_required=False, ignore_conversion=True)
|
||||
or self.force
|
||||
):
|
||||
result = self._generate_crl()
|
||||
elif not self.check(self.module, perms_required=False, ignore_conversion=False) and self.crl:
|
||||
if self.format == 'pem':
|
||||
elif (
|
||||
not self.check(self.module, perms_required=False, ignore_conversion=False)
|
||||
and self.crl
|
||||
):
|
||||
if self.format == "pem":
|
||||
result = self.crl.public_bytes(Encoding.PEM)
|
||||
else:
|
||||
result = self.crl.public_bytes(Encoding.DER)
|
||||
@@ -831,7 +885,7 @@ class CRL(OpenSSLObject):
|
||||
if result is not None:
|
||||
self.diff_after = self._get_info(result)
|
||||
if self.return_content:
|
||||
if self.format == 'pem':
|
||||
if self.format == "pem":
|
||||
self.crl_content = result
|
||||
else:
|
||||
self.crl_content = base64.b64encode(result)
|
||||
@@ -841,59 +895,67 @@ class CRL(OpenSSLObject):
|
||||
self.changed = True
|
||||
|
||||
file_args = self.module.load_file_common_arguments(self.module.params)
|
||||
if self.module.check_file_absent_if_check_mode(file_args['path']):
|
||||
if self.module.check_file_absent_if_check_mode(file_args["path"]):
|
||||
self.changed = True
|
||||
elif self.module.set_fs_attributes_if_different(file_args, False):
|
||||
self.changed = True
|
||||
|
||||
def dump(self, check_mode=False):
|
||||
result = {
|
||||
'changed': self.changed,
|
||||
'filename': self.path,
|
||||
'privatekey': self.privatekey_path,
|
||||
'format': self.format,
|
||||
'last_update': None,
|
||||
'next_update': None,
|
||||
'digest': None,
|
||||
'issuer_ordered': None,
|
||||
'issuer': None,
|
||||
'revoked_certificates': [],
|
||||
"changed": self.changed,
|
||||
"filename": self.path,
|
||||
"privatekey": self.privatekey_path,
|
||||
"format": self.format,
|
||||
"last_update": None,
|
||||
"next_update": None,
|
||||
"digest": None,
|
||||
"issuer_ordered": None,
|
||||
"issuer": None,
|
||||
"revoked_certificates": [],
|
||||
}
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
result["backup_file"] = self.backup_file
|
||||
|
||||
if check_mode:
|
||||
result['last_update'] = self.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result['next_update'] = self.next_update.strftime(TIMESTAMP_FORMAT)
|
||||
result["last_update"] = self.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result["next_update"] = self.next_update.strftime(TIMESTAMP_FORMAT)
|
||||
# result['digest'] = cryptography_oid_to_name(self.crl.signature_algorithm_oid)
|
||||
result['digest'] = self.module.params['digest']
|
||||
result['issuer_ordered'] = self.issuer
|
||||
result['issuer'] = {}
|
||||
result["digest"] = self.module.params["digest"]
|
||||
result["issuer_ordered"] = self.issuer
|
||||
result["issuer"] = {}
|
||||
for k, v in self.issuer:
|
||||
result['issuer'][k] = v
|
||||
result['revoked_certificates'] = []
|
||||
result["issuer"][k] = v
|
||||
result["revoked_certificates"] = []
|
||||
for entry in self.revoked_certificates:
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding))
|
||||
result["revoked_certificates"].append(
|
||||
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)
|
||||
)
|
||||
elif self.crl:
|
||||
result['last_update'] = get_last_update(self.crl).strftime(TIMESTAMP_FORMAT)
|
||||
result['next_update'] = get_next_update(self.crl).strftime(TIMESTAMP_FORMAT)
|
||||
result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl))
|
||||
result["last_update"] = get_last_update(self.crl).strftime(TIMESTAMP_FORMAT)
|
||||
result["next_update"] = get_next_update(self.crl).strftime(TIMESTAMP_FORMAT)
|
||||
result["digest"] = cryptography_oid_to_name(
|
||||
cryptography_get_signature_algorithm_oid_from_crl(self.crl)
|
||||
)
|
||||
issuer = []
|
||||
for attribute in self.crl.issuer:
|
||||
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
result['issuer_ordered'] = issuer
|
||||
result['issuer'] = {}
|
||||
issuer.append(
|
||||
[cryptography_oid_to_name(attribute.oid), attribute.value]
|
||||
)
|
||||
result["issuer_ordered"] = issuer
|
||||
result["issuer"] = {}
|
||||
for k, v in issuer:
|
||||
result['issuer'][k] = v
|
||||
result['revoked_certificates'] = []
|
||||
result["issuer"][k] = v
|
||||
result["revoked_certificates"] = []
|
||||
for cert in self.crl:
|
||||
entry = cryptography_decode_revoked_certificate(cert)
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding))
|
||||
result["revoked_certificates"].append(
|
||||
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)
|
||||
)
|
||||
|
||||
if self.return_content:
|
||||
result['crl'] = self.crl_content
|
||||
result["crl"] = self.crl_content
|
||||
|
||||
result['diff'] = dict(
|
||||
result["diff"] = dict(
|
||||
before=self.diff_before,
|
||||
after=self.diff_after,
|
||||
)
|
||||
@@ -903,100 +965,121 @@ class CRL(OpenSSLObject):
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
||||
crl_mode=dict(
|
||||
type='str',
|
||||
type="str",
|
||||
# default='generate',
|
||||
choices=['generate', 'update'],
|
||||
choices=["generate", "update"],
|
||||
),
|
||||
mode=dict(
|
||||
type='str',
|
||||
type="str",
|
||||
# default='generate',
|
||||
choices=['generate', 'update'],
|
||||
removed_in_version='3.0.0',
|
||||
removed_from_collection='community.crypto',
|
||||
choices=["generate", "update"],
|
||||
removed_in_version="3.0.0",
|
||||
removed_from_collection="community.crypto",
|
||||
),
|
||||
force=dict(type='bool', default=False),
|
||||
backup=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
format=dict(type='str', default='pem', choices=['pem', 'der']),
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str', no_log=True),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
issuer=dict(type='dict'),
|
||||
issuer_ordered=dict(type='list', elements='dict'),
|
||||
last_update=dict(type='str', default='+0s'),
|
||||
next_update=dict(type='str'),
|
||||
digest=dict(type='str', default='sha256'),
|
||||
ignore_timestamps=dict(type='bool', default=False),
|
||||
return_content=dict(type='bool', default=False),
|
||||
force=dict(type="bool", default=False),
|
||||
backup=dict(type="bool", default=False),
|
||||
path=dict(type="path", required=True),
|
||||
format=dict(type="str", default="pem", choices=["pem", "der"]),
|
||||
privatekey_path=dict(type="path"),
|
||||
privatekey_content=dict(type="str", no_log=True),
|
||||
privatekey_passphrase=dict(type="str", no_log=True),
|
||||
issuer=dict(type="dict"),
|
||||
issuer_ordered=dict(type="list", elements="dict"),
|
||||
last_update=dict(type="str", default="+0s"),
|
||||
next_update=dict(type="str"),
|
||||
digest=dict(type="str", default="sha256"),
|
||||
ignore_timestamps=dict(type="bool", default=False),
|
||||
return_content=dict(type="bool", default=False),
|
||||
revoked_certificates=dict(
|
||||
type='list',
|
||||
elements='dict',
|
||||
type="list",
|
||||
elements="dict",
|
||||
options=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
serial_number=dict(type='raw'),
|
||||
revocation_date=dict(type='str', default='+0s'),
|
||||
issuer=dict(type='list', elements='str'),
|
||||
issuer_critical=dict(type='bool', default=False),
|
||||
path=dict(type="path"),
|
||||
content=dict(type="str"),
|
||||
serial_number=dict(type="raw"),
|
||||
revocation_date=dict(type="str", default="+0s"),
|
||||
issuer=dict(type="list", elements="str"),
|
||||
issuer_critical=dict(type="bool", default=False),
|
||||
reason=dict(
|
||||
type='str',
|
||||
type="str",
|
||||
choices=[
|
||||
'unspecified', 'key_compromise', 'ca_compromise', 'affiliation_changed',
|
||||
'superseded', 'cessation_of_operation', 'certificate_hold',
|
||||
'privilege_withdrawn', 'aa_compromise', 'remove_from_crl'
|
||||
]
|
||||
"unspecified",
|
||||
"key_compromise",
|
||||
"ca_compromise",
|
||||
"affiliation_changed",
|
||||
"superseded",
|
||||
"cessation_of_operation",
|
||||
"certificate_hold",
|
||||
"privilege_withdrawn",
|
||||
"aa_compromise",
|
||||
"remove_from_crl",
|
||||
],
|
||||
),
|
||||
reason_critical=dict(type='bool', default=False),
|
||||
invalidity_date=dict(type='str'),
|
||||
invalidity_date_critical=dict(type='bool', default=False),
|
||||
reason_critical=dict(type="bool", default=False),
|
||||
invalidity_date=dict(type="str"),
|
||||
invalidity_date_critical=dict(type="bool", default=False),
|
||||
),
|
||||
required_one_of=[['path', 'content', 'serial_number']],
|
||||
mutually_exclusive=[['path', 'content', 'serial_number']],
|
||||
required_one_of=[["path", "content", "serial_number"]],
|
||||
mutually_exclusive=[["path", "content", "serial_number"]],
|
||||
),
|
||||
name_encoding=dict(
|
||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
||||
),
|
||||
serial_numbers=dict(
|
||||
type="str", default="integer", choices=["integer", "hex-octets"]
|
||||
),
|
||||
name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']),
|
||||
serial_numbers=dict(type='str', default='integer', choices=['integer', 'hex-octets']),
|
||||
),
|
||||
required_if=[
|
||||
('state', 'present', ['privatekey_path', 'privatekey_content'], True),
|
||||
('state', 'present', ['issuer', 'issuer_ordered'], True),
|
||||
('state', 'present', ['next_update', 'revoked_certificates'], False),
|
||||
("state", "present", ["privatekey_path", "privatekey_content"], True),
|
||||
("state", "present", ["issuer", "issuer_ordered"], True),
|
||||
("state", "present", ["next_update", "revoked_certificates"], False),
|
||||
],
|
||||
mutually_exclusive=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
['issuer', 'issuer_ordered'],
|
||||
["privatekey_path", "privatekey_content"],
|
||||
["issuer", "issuer_ordered"],
|
||||
),
|
||||
supports_check_mode=True,
|
||||
add_file_common_args=True,
|
||||
)
|
||||
|
||||
if module.params['mode']:
|
||||
if module.params['crl_mode']:
|
||||
module.fail_json('You cannot use both `mode` and `crl_mode`. Use `crl_mode`.')
|
||||
module.params['crl_mode'] = module.params['mode']
|
||||
if module.params["mode"]:
|
||||
if module.params["crl_mode"]:
|
||||
module.fail_json(
|
||||
"You cannot use both `mode` and `crl_mode`. Use `crl_mode`."
|
||||
)
|
||||
module.params["crl_mode"] = module.params["mode"]
|
||||
# TODO: in 3.0.0, once the option `mode` has been removed, remove this:
|
||||
module.params.pop('mode', None)
|
||||
module.params.pop("mode", None)
|
||||
# From then on, `mode` will be the file mode of the CRL file
|
||||
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
module.fail_json(
|
||||
msg=missing_required_lib(
|
||||
"cryptography >= {0}".format(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR,
|
||||
)
|
||||
|
||||
try:
|
||||
crl = CRL(module)
|
||||
|
||||
if module.params['state'] == 'present':
|
||||
if module.params["state"] == "present":
|
||||
if module.check_mode:
|
||||
result = crl.dump(check_mode=True)
|
||||
result['changed'] = module.params['force'] or not crl.check(module) or not crl.check(module, ignore_conversion=False)
|
||||
result["changed"] = (
|
||||
module.params["force"]
|
||||
or not crl.check(module)
|
||||
or not crl.check(module, ignore_conversion=False)
|
||||
)
|
||||
module.exit_json(**result)
|
||||
|
||||
crl.generate()
|
||||
else:
|
||||
if module.check_mode:
|
||||
result = crl.dump(check_mode=True)
|
||||
result['changed'] = os.path.exists(module.params['path'])
|
||||
result["changed"] = os.path.exists(module.params["path"])
|
||||
module.exit_json(**result)
|
||||
|
||||
crl.remove()
|
||||
|
||||
@@ -195,36 +195,42 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
list_revoked_certificates=dict(type='bool', default=True),
|
||||
name_encoding=dict(type='str', default='ignore', choices=['ignore', 'idna', 'unicode']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
path=dict(type="path"),
|
||||
content=dict(type="str"),
|
||||
list_revoked_certificates=dict(type="bool", default=True),
|
||||
name_encoding=dict(
|
||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
||||
),
|
||||
),
|
||||
required_one_of=(["path", "content"],),
|
||||
mutually_exclusive=(["path", "content"],),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if module.params['content'] is None:
|
||||
if module.params["content"] is None:
|
||||
try:
|
||||
with open(module.params['path'], 'rb') as f:
|
||||
with open(module.params["path"], "rb") as f:
|
||||
data = f.read()
|
||||
except (IOError, OSError) as e:
|
||||
module.fail_json(msg='Error while reading CRL file from disk: {0}'.format(e))
|
||||
module.fail_json(
|
||||
msg="Error while reading CRL file from disk: {0}".format(e)
|
||||
)
|
||||
else:
|
||||
data = module.params['content'].encode('utf-8')
|
||||
data = module.params["content"].encode("utf-8")
|
||||
if not identify_pem_format(data):
|
||||
try:
|
||||
data = base64.b64decode(module.params['content'])
|
||||
data = base64.b64decode(module.params["content"])
|
||||
except (binascii.Error, TypeError) as e:
|
||||
module.fail_json(msg='Error while Base64 decoding content: {0}'.format(e))
|
||||
module.fail_json(
|
||||
msg="Error while Base64 decoding content: {0}".format(e)
|
||||
)
|
||||
|
||||
try:
|
||||
result = get_crl_info(module, data, list_revoked_certificates=module.params['list_revoked_certificates'])
|
||||
result = get_crl_info(
|
||||
module,
|
||||
data,
|
||||
list_revoked_certificates=module.params["list_revoked_certificates"],
|
||||
)
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
|
||||
Reference in New Issue
Block a user