1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2019, Felix Fontein <felix@fontein.de> 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10 11DOCUMENTATION = r''' 12--- 13module: x509_crl 14version_added: '1.0.0' 15short_description: Generate Certificate Revocation Lists (CRLs) 16description: 17 - This module allows one to (re)generate or update Certificate Revocation Lists (CRLs). 18 - Certificates on the revocation list can be either specified by serial number and (optionally) their issuer, 19 or as a path to a certificate file in PEM format. 20requirements: 21 - cryptography >= 1.2 22author: 23 - Felix Fontein (@felixfontein) 24options: 25 state: 26 description: 27 - Whether the CRL file should exist or not, taking action if the state is different from what is stated. 28 type: str 29 default: present 30 choices: [ absent, present ] 31 32 mode: 33 description: 34 - Defines how to process entries of existing CRLs. 35 - If set to C(generate), makes sure that the CRL has the exact set of revoked certificates 36 as specified in I(revoked_certificates). 37 - If set to C(update), makes sure that the CRL contains the revoked certificates from 38 I(revoked_certificates), but can also contain other revoked certificates. If the CRL file 39 already exists, all entries from the existing CRL will also be included in the new CRL. 40 When using C(update), you might be interested in setting I(ignore_timestamps) to C(yes). 41 type: str 42 default: generate 43 choices: [ generate, update ] 44 45 force: 46 description: 47 - Should the CRL be forced to be regenerated. 48 type: bool 49 default: no 50 51 backup: 52 description: 53 - Create a backup file including a timestamp so you can get the original 54 CRL back if you overwrote it with a new one by accident. 55 type: bool 56 default: no 57 58 path: 59 description: 60 - Remote absolute path where the generated CRL file should be created or is already located. 61 type: path 62 required: yes 63 64 format: 65 description: 66 - Whether the CRL file should be in PEM or DER format. 67 - If an existing CRL file does match everything but I(format), it will be converted to the correct format 68 instead of regenerated. 69 type: str 70 choices: [pem, der] 71 default: pem 72 73 privatekey_path: 74 description: 75 - Path to the CA's private key to use when signing the CRL. 76 - Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both. 77 type: path 78 79 privatekey_content: 80 description: 81 - The content of the CA's private key to use when signing the CRL. 82 - Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both. 83 type: str 84 85 privatekey_passphrase: 86 description: 87 - The passphrase for the I(privatekey_path). 88 - This is required if the private key is password protected. 89 type: str 90 91 issuer: 92 description: 93 - Key/value pairs that will be present in the issuer name field of the CRL. 94 - If you need to specify more than one value with the same key, use a list as value. 95 - Required if I(state) is C(present). 96 type: dict 97 98 last_update: 99 description: 100 - The point in time from which this CRL can be trusted. 101 - Time can be specified either as relative time or as absolute timestamp. 102 - Time will always be interpreted as UTC. 103 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 104 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 105 - Note that if using relative time this module is NOT idempotent, except when 106 I(ignore_timestamps) is set to C(yes). 107 type: str 108 default: "+0s" 109 110 next_update: 111 description: 112 - "The absolute latest point in time by which this I(issuer) is expected to have issued 113 another CRL. Many clients will treat a CRL as expired once I(next_update) occurs." 114 - Time can be specified either as relative time or as absolute timestamp. 115 - Time will always be interpreted as UTC. 116 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 117 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 118 - Note that if using relative time this module is NOT idempotent, except when 119 I(ignore_timestamps) is set to C(yes). 120 - Required if I(state) is C(present). 121 type: str 122 123 digest: 124 description: 125 - Digest algorithm to be used when signing the CRL. 126 type: str 127 default: sha256 128 129 revoked_certificates: 130 description: 131 - List of certificates to be revoked. 132 - Required if I(state) is C(present). 133 type: list 134 elements: dict 135 suboptions: 136 path: 137 description: 138 - Path to a certificate in PEM format. 139 - The serial number and issuer will be extracted from the certificate. 140 - Mutually exclusive with I(content) and I(serial_number). One of these three options 141 must be specified. 142 type: path 143 content: 144 description: 145 - Content of a certificate in PEM format. 146 - The serial number and issuer will be extracted from the certificate. 147 - Mutually exclusive with I(path) and I(serial_number). One of these three options 148 must be specified. 149 type: str 150 serial_number: 151 description: 152 - Serial number of the certificate. 153 - Mutually exclusive with I(path) and I(content). One of these three options must 154 be specified. 155 type: int 156 revocation_date: 157 description: 158 - The point in time the certificate was revoked. 159 - Time can be specified either as relative time or as absolute timestamp. 160 - Time will always be interpreted as UTC. 161 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 162 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 163 - Note that if using relative time this module is NOT idempotent, except when 164 I(ignore_timestamps) is set to C(yes). 165 type: str 166 default: "+0s" 167 issuer: 168 description: 169 - The certificate's issuer. 170 - "Example: C(DNS:ca.example.org)" 171 type: list 172 elements: str 173 issuer_critical: 174 description: 175 - Whether the certificate issuer extension should be critical. 176 type: bool 177 default: no 178 reason: 179 description: 180 - The value for the revocation reason extension. 181 type: str 182 choices: 183 - unspecified 184 - key_compromise 185 - ca_compromise 186 - affiliation_changed 187 - superseded 188 - cessation_of_operation 189 - certificate_hold 190 - privilege_withdrawn 191 - aa_compromise 192 - remove_from_crl 193 reason_critical: 194 description: 195 - Whether the revocation reason extension should be critical. 196 type: bool 197 default: no 198 invalidity_date: 199 description: 200 - The point in time it was known/suspected that the private key was compromised 201 or that the certificate otherwise became invalid. 202 - Time can be specified either as relative time or as absolute timestamp. 203 - Time will always be interpreted as UTC. 204 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 205 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 206 - Note that if using relative time this module is NOT idempotent. This will NOT 207 change when I(ignore_timestamps) is set to C(yes). 208 type: str 209 invalidity_date_critical: 210 description: 211 - Whether the invalidity date extension should be critical. 212 type: bool 213 default: no 214 215 ignore_timestamps: 216 description: 217 - Whether the timestamps I(last_update), I(next_update) and I(revocation_date) (in 218 I(revoked_certificates)) should be ignored for idempotency checks. The timestamp 219 I(invalidity_date) in I(revoked_certificates) will never be ignored. 220 - Use this in combination with relative timestamps for these values to get idempotency. 221 type: bool 222 default: no 223 224 return_content: 225 description: 226 - If set to C(yes), will return the (current or generated) CRL's content as I(crl). 227 type: bool 228 default: no 229 230extends_documentation_fragment: 231 - files 232 233notes: 234 - All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern. 235 - Date specified should be UTC. Minutes and seconds are mandatory. 236 - Supports C(check_mode). 237''' 238 239EXAMPLES = r''' 240- name: Generate a CRL 241 community.crypto.x509_crl: 242 path: /etc/ssl/my-ca.crl 243 privatekey_path: /etc/ssl/private/my-ca.pem 244 issuer: 245 CN: My CA 246 last_update: "+0s" 247 next_update: "+7d" 248 revoked_certificates: 249 - serial_number: 1234 250 revocation_date: 20190331202428Z 251 issuer: 252 CN: My CA 253 - serial_number: 2345 254 revocation_date: 20191013152910Z 255 reason: affiliation_changed 256 invalidity_date: 20191001000000Z 257 - path: /etc/ssl/crt/revoked-cert.pem 258 revocation_date: 20191010010203Z 259''' 260 261RETURN = r''' 262filename: 263 description: Path to the generated CRL. 264 returned: changed or success 265 type: str 266 sample: /path/to/my-ca.crl 267backup_file: 268 description: Name of backup file created. 269 returned: changed and if I(backup) is C(yes) 270 type: str 271 sample: /path/to/my-ca.crl.2019-03-09@11:22~ 272privatekey: 273 description: Path to the private CA key. 274 returned: changed or success 275 type: str 276 sample: /path/to/my-ca.pem 277format: 278 description: 279 - Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)). 280 returned: success 281 type: str 282 sample: pem 283issuer: 284 description: 285 - The CRL's issuer. 286 - Note that for repeated values, only the last one will be returned. 287 returned: success 288 type: dict 289 sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' 290issuer_ordered: 291 description: The CRL's issuer as an ordered list of tuples. 292 returned: success 293 type: list 294 elements: list 295 sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' 296last_update: 297 description: The point in time from which this CRL can be trusted as ASN.1 TIME. 298 returned: success 299 type: str 300 sample: 20190413202428Z 301next_update: 302 description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME. 303 returned: success 304 type: str 305 sample: 20190413202428Z 306digest: 307 description: The signature algorithm used to sign the CRL. 308 returned: success 309 type: str 310 sample: sha256WithRSAEncryption 311revoked_certificates: 312 description: List of certificates to be revoked. 313 returned: success 314 type: list 315 elements: dict 316 contains: 317 serial_number: 318 description: Serial number of the certificate. 319 type: int 320 sample: 1234 321 revocation_date: 322 description: The point in time the certificate was revoked as ASN.1 TIME. 323 type: str 324 sample: 20190413202428Z 325 issuer: 326 description: The certificate's issuer. 327 type: list 328 elements: str 329 sample: '["DNS:ca.example.org"]' 330 issuer_critical: 331 description: Whether the certificate issuer extension is critical. 332 type: bool 333 sample: no 334 reason: 335 description: 336 - The value for the revocation reason extension. 337 - One of C(unspecified), C(key_compromise), C(ca_compromise), C(affiliation_changed), C(superseded), 338 C(cessation_of_operation), C(certificate_hold), C(privilege_withdrawn), C(aa_compromise), and 339 C(remove_from_crl). 340 type: str 341 sample: key_compromise 342 reason_critical: 343 description: Whether the revocation reason extension is critical. 344 type: bool 345 sample: no 346 invalidity_date: 347 description: | 348 The point in time it was known/suspected that the private key was compromised 349 or that the certificate otherwise became invalid as ASN.1 TIME. 350 type: str 351 sample: 20190413202428Z 352 invalidity_date_critical: 353 description: Whether the invalidity date extension is critical. 354 type: bool 355 sample: no 356crl: 357 description: 358 - The (current or generated) CRL's content. 359 - Will be the CRL itself if I(format) is C(pem), and Base64 of the 360 CRL if I(format) is C(der). 361 returned: if I(state) is C(present) and I(return_content) is C(yes) 362 type: str 363''' 364 365 366import base64 367import os 368import traceback 369 370from distutils.version import LooseVersion 371 372from ansible.module_utils.basic import AnsibleModule, missing_required_lib 373from ansible.module_utils.common.text.converters import to_native, to_text 374 375from ansible_collections.community.crypto.plugins.module_utils.io import ( 376 write_file, 377) 378 379from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( 380 OpenSSLObjectError, 381 OpenSSLBadPassphraseError, 382) 383 384from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( 385 OpenSSLObject, 386 load_privatekey, 387 load_certificate, 388 parse_name_field, 389 get_relative_time_option, 390 select_message_digest, 391) 392 393from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( 394 cryptography_get_name, 395 cryptography_name_to_oid, 396 cryptography_oid_to_name, 397 cryptography_serial_number_of_cert, 398) 399 400from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import ( 401 REVOCATION_REASON_MAP, 402 TIMESTAMP_FORMAT, 403 cryptography_decode_revoked_certificate, 404 cryptography_dump_revoked, 405 cryptography_get_signature_algorithm_oid_from_crl, 406) 407 408from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( 409 identify_pem_format, 410) 411 412from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.crl_info import ( 413 get_crl_info, 414) 415 416MINIMAL_CRYPTOGRAPHY_VERSION = '1.2' 417 418CRYPTOGRAPHY_IMP_ERR = None 419try: 420 import cryptography 421 from cryptography import x509 422 from cryptography.hazmat.backends import default_backend 423 from cryptography.hazmat.primitives.serialization import Encoding 424 from cryptography.x509 import ( 425 CertificateRevocationListBuilder, 426 RevokedCertificateBuilder, 427 NameAttribute, 428 Name, 429 ) 430 CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) 431except ImportError: 432 CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() 433 CRYPTOGRAPHY_FOUND = False 434else: 435 CRYPTOGRAPHY_FOUND = True 436 437 438class CRLError(OpenSSLObjectError): 439 pass 440 441 442class CRL(OpenSSLObject): 443 444 def __init__(self, module): 445 super(CRL, self).__init__( 446 module.params['path'], 447 module.params['state'], 448 module.params['force'], 449 module.check_mode 450 ) 451 452 self.format = module.params['format'] 453 454 self.update = module.params['mode'] == 'update' 455 self.ignore_timestamps = module.params['ignore_timestamps'] 456 self.return_content = module.params['return_content'] 457 self.crl_content = None 458 459 self.privatekey_path = module.params['privatekey_path'] 460 self.privatekey_content = module.params['privatekey_content'] 461 if self.privatekey_content is not None: 462 self.privatekey_content = self.privatekey_content.encode('utf-8') 463 self.privatekey_passphrase = module.params['privatekey_passphrase'] 464 465 self.issuer = parse_name_field(module.params['issuer']) 466 self.issuer = [(entry[0], entry[1]) for entry in self.issuer if entry[1]] 467 468 self.last_update = get_relative_time_option(module.params['last_update'], 'last_update') 469 self.next_update = get_relative_time_option(module.params['next_update'], 'next_update') 470 471 self.digest = select_message_digest(module.params['digest']) 472 if self.digest is None: 473 raise CRLError('The digest "{0}" is not supported'.format(module.params['digest'])) 474 475 self.revoked_certificates = [] 476 for i, rc in enumerate(module.params['revoked_certificates']): 477 result = { 478 'serial_number': None, 479 'revocation_date': None, 480 'issuer': None, 481 'issuer_critical': False, 482 'reason': None, 483 'reason_critical': False, 484 'invalidity_date': None, 485 'invalidity_date_critical': False, 486 } 487 path_prefix = 'revoked_certificates[{0}].'.format(i) 488 if rc['path'] is not None or rc['content'] is not None: 489 # Load certificate from file or content 490 try: 491 if rc['content'] is not None: 492 rc['content'] = rc['content'].encode('utf-8') 493 cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography') 494 result['serial_number'] = cryptography_serial_number_of_cert(cert) 495 except OpenSSLObjectError as e: 496 if rc['content'] is not None: 497 module.fail_json( 498 msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e)) 499 ) 500 else: 501 module.fail_json( 502 msg='Cannot read certificate "{1}" from {0}path: {2}'.format(path_prefix, rc['path'], to_native(e)) 503 ) 504 else: 505 # Specify serial_number (and potentially issuer) directly 506 result['serial_number'] = rc['serial_number'] 507 # All other options 508 if rc['issuer']: 509 result['issuer'] = [cryptography_get_name(issuer, 'issuer') for issuer in rc['issuer']] 510 result['issuer_critical'] = rc['issuer_critical'] 511 result['revocation_date'] = get_relative_time_option( 512 rc['revocation_date'], 513 path_prefix + 'revocation_date' 514 ) 515 if rc['reason']: 516 result['reason'] = REVOCATION_REASON_MAP[rc['reason']] 517 result['reason_critical'] = rc['reason_critical'] 518 if rc['invalidity_date']: 519 result['invalidity_date'] = get_relative_time_option( 520 rc['invalidity_date'], 521 path_prefix + 'invalidity_date' 522 ) 523 result['invalidity_date_critical'] = rc['invalidity_date_critical'] 524 self.revoked_certificates.append(result) 525 526 self.module = module 527 528 self.backup = module.params['backup'] 529 self.backup_file = None 530 531 try: 532 self.privatekey = load_privatekey( 533 path=self.privatekey_path, 534 content=self.privatekey_content, 535 passphrase=self.privatekey_passphrase, 536 backend='cryptography' 537 ) 538 except OpenSSLBadPassphraseError as exc: 539 raise CRLError(exc) 540 541 self.crl = None 542 try: 543 with open(self.path, 'rb') as f: 544 data = f.read() 545 self.actual_format = 'pem' if identify_pem_format(data) else 'der' 546 if self.actual_format == 'pem': 547 self.crl = x509.load_pem_x509_crl(data, default_backend()) 548 if self.return_content: 549 self.crl_content = data 550 else: 551 self.crl = x509.load_der_x509_crl(data, default_backend()) 552 if self.return_content: 553 self.crl_content = base64.b64encode(data) 554 except Exception as dummy: 555 self.crl_content = None 556 self.actual_format = self.format 557 data = None 558 559 self.diff_after = self.diff_before = self._get_info(data) 560 561 def _get_info(self, data): 562 if data is None: 563 return dict() 564 try: 565 result = get_crl_info(self.module, data) 566 result['can_parse_crl'] = True 567 return result 568 except Exception as exc: 569 return dict(can_parse_crl=False) 570 571 def remove(self): 572 if self.backup: 573 self.backup_file = self.module.backup_local(self.path) 574 super(CRL, self).remove(self.module) 575 576 def _compress_entry(self, entry): 577 if self.ignore_timestamps: 578 # Throw out revocation_date 579 return ( 580 entry['serial_number'], 581 tuple(entry['issuer']) if entry['issuer'] is not None else None, 582 entry['issuer_critical'], 583 entry['reason'], 584 entry['reason_critical'], 585 entry['invalidity_date'], 586 entry['invalidity_date_critical'], 587 ) 588 else: 589 return ( 590 entry['serial_number'], 591 entry['revocation_date'], 592 tuple(entry['issuer']) if entry['issuer'] is not None else None, 593 entry['issuer_critical'], 594 entry['reason'], 595 entry['reason_critical'], 596 entry['invalidity_date'], 597 entry['invalidity_date_critical'], 598 ) 599 600 def check(self, module, perms_required=True, ignore_conversion=True): 601 """Ensure the resource is in its desired state.""" 602 603 state_and_perms = super(CRL, self).check(self.module, perms_required) 604 605 if not state_and_perms: 606 return False 607 608 if self.crl is None: 609 return False 610 611 if self.last_update != self.crl.last_update and not self.ignore_timestamps: 612 return False 613 if self.next_update != self.crl.next_update and not self.ignore_timestamps: 614 return False 615 if self.digest.name != self.crl.signature_hash_algorithm.name: 616 return False 617 618 want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer] 619 if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]: 620 return False 621 622 old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl] 623 new_entries = [self._compress_entry(cert) for cert in self.revoked_certificates] 624 if self.update: 625 # We don't simply use a set so that duplicate entries are treated correctly 626 for entry in new_entries: 627 try: 628 old_entries.remove(entry) 629 except ValueError: 630 return False 631 else: 632 if old_entries != new_entries: 633 return False 634 635 if self.format != self.actual_format and not ignore_conversion: 636 return False 637 638 return True 639 640 def _generate_crl(self): 641 backend = default_backend() 642 crl = CertificateRevocationListBuilder() 643 644 try: 645 crl = crl.issuer_name(Name([ 646 NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) 647 for entry in self.issuer 648 ])) 649 except ValueError as e: 650 raise CRLError(e) 651 652 crl = crl.last_update(self.last_update) 653 crl = crl.next_update(self.next_update) 654 655 if self.update and self.crl: 656 new_entries = set([self._compress_entry(entry) for entry in self.revoked_certificates]) 657 for entry in self.crl: 658 decoded_entry = self._compress_entry(cryptography_decode_revoked_certificate(entry)) 659 if decoded_entry not in new_entries: 660 crl = crl.add_revoked_certificate(entry) 661 for entry in self.revoked_certificates: 662 revoked_cert = RevokedCertificateBuilder() 663 revoked_cert = revoked_cert.serial_number(entry['serial_number']) 664 revoked_cert = revoked_cert.revocation_date(entry['revocation_date']) 665 if entry['issuer'] is not None: 666 revoked_cert = revoked_cert.add_extension( 667 x509.CertificateIssuer([ 668 cryptography_get_name(name, 'issuer') for name in entry['issuer'] 669 ]), 670 entry['issuer_critical'] 671 ) 672 if entry['reason'] is not None: 673 revoked_cert = revoked_cert.add_extension( 674 x509.CRLReason(entry['reason']), 675 entry['reason_critical'] 676 ) 677 if entry['invalidity_date'] is not None: 678 revoked_cert = revoked_cert.add_extension( 679 x509.InvalidityDate(entry['invalidity_date']), 680 entry['invalidity_date_critical'] 681 ) 682 crl = crl.add_revoked_certificate(revoked_cert.build(backend)) 683 684 self.crl = crl.sign(self.privatekey, self.digest, backend=backend) 685 if self.format == 'pem': 686 return self.crl.public_bytes(Encoding.PEM) 687 else: 688 return self.crl.public_bytes(Encoding.DER) 689 690 def generate(self): 691 result = None 692 if not self.check(self.module, perms_required=False, ignore_conversion=True) or self.force: 693 result = self._generate_crl() 694 elif not self.check(self.module, perms_required=False, ignore_conversion=False) and self.crl: 695 if self.format == 'pem': 696 result = self.crl.public_bytes(Encoding.PEM) 697 else: 698 result = self.crl.public_bytes(Encoding.DER) 699 700 if result is not None: 701 self.diff_after = self._get_info(result) 702 if self.return_content: 703 if self.format == 'pem': 704 self.crl_content = result 705 else: 706 self.crl_content = base64.b64encode(result) 707 if self.backup: 708 self.backup_file = self.module.backup_local(self.path) 709 write_file(self.module, result) 710 self.changed = True 711 712 file_args = self.module.load_file_common_arguments(self.module.params) 713 if self.module.check_file_absent_if_check_mode(file_args['path']): 714 self.changed = True 715 elif self.module.set_fs_attributes_if_different(file_args, False): 716 self.changed = True 717 718 def dump(self, check_mode=False): 719 result = { 720 'changed': self.changed, 721 'filename': self.path, 722 'privatekey': self.privatekey_path, 723 'format': self.format, 724 'last_update': None, 725 'next_update': None, 726 'digest': None, 727 'issuer_ordered': None, 728 'issuer': None, 729 'revoked_certificates': [], 730 } 731 if self.backup_file: 732 result['backup_file'] = self.backup_file 733 734 if check_mode: 735 result['last_update'] = self.last_update.strftime(TIMESTAMP_FORMAT) 736 result['next_update'] = self.next_update.strftime(TIMESTAMP_FORMAT) 737 # result['digest'] = cryptography_oid_to_name(self.crl.signature_algorithm_oid) 738 result['digest'] = self.module.params['digest'] 739 result['issuer_ordered'] = self.issuer 740 result['issuer'] = {} 741 for k, v in self.issuer: 742 result['issuer'][k] = v 743 result['revoked_certificates'] = [] 744 for entry in self.revoked_certificates: 745 result['revoked_certificates'].append(cryptography_dump_revoked(entry)) 746 elif self.crl: 747 result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT) 748 result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT) 749 result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl)) 750 issuer = [] 751 for attribute in self.crl.issuer: 752 issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value]) 753 result['issuer_ordered'] = issuer 754 result['issuer'] = {} 755 for k, v in issuer: 756 result['issuer'][k] = v 757 result['revoked_certificates'] = [] 758 for cert in self.crl: 759 entry = cryptography_decode_revoked_certificate(cert) 760 result['revoked_certificates'].append(cryptography_dump_revoked(entry)) 761 762 if self.return_content: 763 result['crl'] = self.crl_content 764 765 result['diff'] = dict( 766 before=self.diff_before, 767 after=self.diff_after, 768 ) 769 return result 770 771 772def main(): 773 module = AnsibleModule( 774 argument_spec=dict( 775 state=dict(type='str', default='present', choices=['present', 'absent']), 776 mode=dict(type='str', default='generate', choices=['generate', 'update']), 777 force=dict(type='bool', default=False), 778 backup=dict(type='bool', default=False), 779 path=dict(type='path', required=True), 780 format=dict(type='str', default='pem', choices=['pem', 'der']), 781 privatekey_path=dict(type='path'), 782 privatekey_content=dict(type='str', no_log=True), 783 privatekey_passphrase=dict(type='str', no_log=True), 784 issuer=dict(type='dict'), 785 last_update=dict(type='str', default='+0s'), 786 next_update=dict(type='str'), 787 digest=dict(type='str', default='sha256'), 788 ignore_timestamps=dict(type='bool', default=False), 789 return_content=dict(type='bool', default=False), 790 revoked_certificates=dict( 791 type='list', 792 elements='dict', 793 options=dict( 794 path=dict(type='path'), 795 content=dict(type='str'), 796 serial_number=dict(type='int'), 797 revocation_date=dict(type='str', default='+0s'), 798 issuer=dict(type='list', elements='str'), 799 issuer_critical=dict(type='bool', default=False), 800 reason=dict( 801 type='str', 802 choices=[ 803 'unspecified', 'key_compromise', 'ca_compromise', 'affiliation_changed', 804 'superseded', 'cessation_of_operation', 'certificate_hold', 805 'privilege_withdrawn', 'aa_compromise', 'remove_from_crl' 806 ] 807 ), 808 reason_critical=dict(type='bool', default=False), 809 invalidity_date=dict(type='str'), 810 invalidity_date_critical=dict(type='bool', default=False), 811 ), 812 required_one_of=[['path', 'content', 'serial_number']], 813 mutually_exclusive=[['path', 'content', 'serial_number']], 814 ), 815 ), 816 required_if=[ 817 ('state', 'present', ['privatekey_path', 'privatekey_content'], True), 818 ('state', 'present', ['issuer', 'next_update', 'revoked_certificates'], False), 819 ], 820 mutually_exclusive=( 821 ['privatekey_path', 'privatekey_content'], 822 ), 823 supports_check_mode=True, 824 add_file_common_args=True, 825 ) 826 827 if not CRYPTOGRAPHY_FOUND: 828 module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), 829 exception=CRYPTOGRAPHY_IMP_ERR) 830 831 try: 832 crl = CRL(module) 833 834 if module.params['state'] == 'present': 835 if module.check_mode: 836 result = crl.dump(check_mode=True) 837 result['changed'] = module.params['force'] or not crl.check(module) or not crl.check(module, ignore_conversion=False) 838 module.exit_json(**result) 839 840 crl.generate() 841 else: 842 if module.check_mode: 843 result = crl.dump(check_mode=True) 844 result['changed'] = os.path.exists(module.params['path']) 845 module.exit_json(**result) 846 847 crl.remove() 848 849 result = crl.dump() 850 module.exit_json(**result) 851 except OpenSSLObjectError as exc: 852 module.fail_json(msg=to_native(exc)) 853 854 855if __name__ == "__main__": 856 main() 857