1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org> 5# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at> 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import absolute_import, division, print_function 9__metaclass__ = type 10 11ANSIBLE_METADATA = {'metadata_version': '1.1', 12 'status': ['preview'], 13 'supported_by': 'community'} 14 15DOCUMENTATION = r''' 16--- 17module: openssl_certificate 18version_added: "2.4" 19short_description: Generate and/or check OpenSSL certificates 20description: 21 - This module allows one to (re)generate OpenSSL certificates. 22 - It implements a notion of provider (ie. C(selfsigned), C(ownca), C(acme), C(assertonly), C(entrust)) 23 for your certificate. 24 - The C(assertonly) provider is intended for use cases where one is only interested in 25 checking properties of a supplied certificate. Please note that this provider has been 26 deprecated in Ansible 2.9 and will be removed in Ansible 2.13. See the examples on how 27 to emulate C(assertonly) usage with M(openssl_certificate_info), M(openssl_csr_info), 28 M(openssl_privatekey_info) and M(assert). This also allows more flexible checks than 29 the ones offered by the C(assertonly) provider. 30 - The C(ownca) provider is intended for generating OpenSSL certificate signed with your own 31 CA (Certificate Authority) certificate (self-signed certificate). 32 - Many properties that can be specified in this module are for validation of an 33 existing or newly generated certificate. The proper place to specify them, if you 34 want to receive a certificate with these properties is a CSR (Certificate Signing Request). 35 - "Please note that the module regenerates existing certificate if it doesn't match the module's 36 options, or if it seems to be corrupt. If you are concerned that this could overwrite 37 your existing certificate, consider using the I(backup) option." 38 - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. 39 - If both the cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) 40 cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with C(select_crypto_backend)). 41 Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13. 42requirements: 43 - PyOpenSSL >= 0.15 or cryptography >= 1.6 (if using C(selfsigned) or C(assertonly) provider) 44 - acme-tiny (if using the C(acme) provider) 45author: 46 - Yanis Guenane (@Spredzy) 47 - Markus Teufelberger (@MarkusTeufelberger) 48options: 49 state: 50 description: 51 - Whether the certificate should exist or not, taking action if the state is different from what is stated. 52 type: str 53 default: present 54 choices: [ absent, present ] 55 56 path: 57 description: 58 - Remote absolute path where the generated certificate file should be created or is already located. 59 type: path 60 required: true 61 62 provider: 63 description: 64 - Name of the provider to use to generate/retrieve the OpenSSL certificate. 65 - The C(assertonly) provider will not generate files and fail if the certificate file is missing. 66 - The C(assertonly) provider has been deprecated in Ansible 2.9 and will be removed in Ansible 2.13. 67 Please see the examples on how to emulate it with M(openssl_certificate_info), M(openssl_csr_info), 68 M(openssl_privatekey_info) and M(assert). 69 - "The C(entrust) provider was added for Ansible 2.9 and requires credentials for the 70 L(https://www.entrustdatacard.com/products/categories/ssl-certificates,Entrust Certificate Services) (ECS) API." 71 - Required if I(state) is C(present). 72 type: str 73 choices: [ acme, assertonly, entrust, ownca, selfsigned ] 74 75 force: 76 description: 77 - Generate the certificate, even if it already exists. 78 type: bool 79 default: no 80 81 csr_path: 82 description: 83 - Path to the Certificate Signing Request (CSR) used to generate this certificate. 84 - This is not required in C(assertonly) mode. 85 type: path 86 87 privatekey_path: 88 description: 89 - Path to the private key to use when signing the certificate. 90 type: path 91 92 privatekey_passphrase: 93 description: 94 - The passphrase for the I(privatekey_path). 95 - This is required if the private key is password protected. 96 type: str 97 98 selfsigned_version: 99 description: 100 - Version of the C(selfsigned) certificate. 101 - Nowadays it should almost always be C(3). 102 - This is only used by the C(selfsigned) provider. 103 type: int 104 default: 3 105 version_added: "2.5" 106 107 selfsigned_digest: 108 description: 109 - Digest algorithm to be used when self-signing the certificate. 110 - This is only used by the C(selfsigned) provider. 111 type: str 112 default: sha256 113 114 selfsigned_not_before: 115 description: 116 - The point in time the certificate is valid from. 117 - Time can be specified either as relative time or as absolute timestamp. 118 - Time will always be interpreted as UTC. 119 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 120 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 121 - Note that if using relative time this module is NOT idempotent. 122 - If this value is not specified, the certificate will start being valid from now. 123 - This is only used by the C(selfsigned) provider. 124 type: str 125 default: +0s 126 aliases: [ selfsigned_notBefore ] 127 128 selfsigned_not_after: 129 description: 130 - The point in time at which the certificate stops being valid. 131 - Time can be specified either as relative time or as absolute timestamp. 132 - Time will always be interpreted as UTC. 133 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 134 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 135 - Note that if using relative time this module is NOT idempotent. 136 - If this value is not specified, the certificate will stop being valid 10 years from now. 137 - This is only used by the C(selfsigned) provider. 138 type: str 139 default: +3650d 140 aliases: [ selfsigned_notAfter ] 141 142 selfsigned_create_subject_key_identifier: 143 description: 144 - Whether to create the Subject Key Identifier (SKI) from the public key. 145 - A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not 146 provide one. 147 - A value of C(always_create) always creates a SKI. If the CSR provides one, that one is 148 ignored. 149 - A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used. 150 - This is only used by the C(selfsigned) provider. 151 - Note that this is only supported if the C(cryptography) backend is used! 152 type: str 153 choices: [create_if_not_provided, always_create, never_create] 154 default: create_if_not_provided 155 version_added: "2.9" 156 157 ownca_path: 158 description: 159 - Remote absolute path of the CA (Certificate Authority) certificate. 160 - This is only used by the C(ownca) provider. 161 type: path 162 version_added: "2.7" 163 164 ownca_privatekey_path: 165 description: 166 - Path to the CA (Certificate Authority) private key to use when signing the certificate. 167 - This is only used by the C(ownca) provider. 168 type: path 169 version_added: "2.7" 170 171 ownca_privatekey_passphrase: 172 description: 173 - The passphrase for the I(ownca_privatekey_path). 174 - This is only used by the C(ownca) provider. 175 type: str 176 version_added: "2.7" 177 178 ownca_digest: 179 description: 180 - The digest algorithm to be used for the C(ownca) certificate. 181 - This is only used by the C(ownca) provider. 182 type: str 183 default: sha256 184 version_added: "2.7" 185 186 ownca_version: 187 description: 188 - The version of the C(ownca) certificate. 189 - Nowadays it should almost always be C(3). 190 - This is only used by the C(ownca) provider. 191 type: int 192 default: 3 193 version_added: "2.7" 194 195 ownca_not_before: 196 description: 197 - The point in time the certificate is valid from. 198 - Time can be specified either as relative time or as absolute timestamp. 199 - Time will always be interpreted as UTC. 200 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 201 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 202 - Note that if using relative time this module is NOT idempotent. 203 - If this value is not specified, the certificate will start being valid from now. 204 - This is only used by the C(ownca) provider. 205 type: str 206 default: +0s 207 version_added: "2.7" 208 209 ownca_not_after: 210 description: 211 - The point in time at which the certificate stops being valid. 212 - Time can be specified either as relative time or as absolute timestamp. 213 - Time will always be interpreted as UTC. 214 - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer 215 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 216 - Note that if using relative time this module is NOT idempotent. 217 - If this value is not specified, the certificate will stop being valid 10 years from now. 218 - This is only used by the C(ownca) provider. 219 type: str 220 default: +3650d 221 version_added: "2.7" 222 223 ownca_create_subject_key_identifier: 224 description: 225 - Whether to create the Subject Key Identifier (SKI) from the public key. 226 - A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not 227 provide one. 228 - A value of C(always_create) always creates a SKI. If the CSR provides one, that one is 229 ignored. 230 - A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used. 231 - This is only used by the C(ownca) provider. 232 - Note that this is only supported if the C(cryptography) backend is used! 233 type: str 234 choices: [create_if_not_provided, always_create, never_create] 235 default: create_if_not_provided 236 version_added: "2.9" 237 238 ownca_create_authority_key_identifier: 239 description: 240 - Create a Authority Key Identifier from the CA's certificate. If the CSR provided 241 a authority key identifier, it is ignored. 242 - The Authority Key Identifier is generated from the CA certificate's Subject Key Identifier, 243 if available. If it is not available, the CA certificate's public key will be used. 244 - This is only used by the C(ownca) provider. 245 - Note that this is only supported if the C(cryptography) backend is used! 246 type: bool 247 default: yes 248 version_added: "2.9" 249 250 acme_accountkey_path: 251 description: 252 - The path to the accountkey for the C(acme) provider. 253 - This is only used by the C(acme) provider. 254 type: path 255 256 acme_challenge_path: 257 description: 258 - The path to the ACME challenge directory that is served on U(http://<HOST>:80/.well-known/acme-challenge/) 259 - This is only used by the C(acme) provider. 260 type: path 261 262 acme_chain: 263 description: 264 - Include the intermediate certificate to the generated certificate 265 - This is only used by the C(acme) provider. 266 - Note that this is only available for older versions of C(acme-tiny). 267 New versions include the chain automatically, and setting I(acme_chain) to C(yes) results in an error. 268 type: bool 269 default: no 270 version_added: "2.5" 271 272 signature_algorithms: 273 description: 274 - A list of algorithms that you would accept the certificate to be signed with 275 (e.g. ['sha256WithRSAEncryption', 'sha512WithRSAEncryption']). 276 - This is only used by the C(assertonly) provider. 277 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 278 For alternatives, see the example on replacing C(assertonly). 279 type: list 280 elements: str 281 282 issuer: 283 description: 284 - The key/value pairs that must be present in the issuer name field of the certificate. 285 - If you need to specify more than one value with the same key, use a list as value. 286 - This is only used by the C(assertonly) provider. 287 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 288 For alternatives, see the example on replacing C(assertonly). 289 type: dict 290 291 issuer_strict: 292 description: 293 - If set to C(yes), the I(issuer) field must contain only these values. 294 - This is only used by the C(assertonly) provider. 295 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 296 For alternatives, see the example on replacing C(assertonly). 297 type: bool 298 default: no 299 version_added: "2.5" 300 301 subject: 302 description: 303 - The key/value pairs that must be present in the subject name field of the certificate. 304 - If you need to specify more than one value with the same key, use a list as value. 305 - This is only used by the C(assertonly) provider. 306 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 307 For alternatives, see the example on replacing C(assertonly). 308 type: dict 309 310 subject_strict: 311 description: 312 - If set to C(yes), the I(subject) field must contain only these values. 313 - This is only used by the C(assertonly) provider. 314 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 315 For alternatives, see the example on replacing C(assertonly). 316 type: bool 317 default: no 318 version_added: "2.5" 319 320 has_expired: 321 description: 322 - Checks if the certificate is expired/not expired at the time the module is executed. 323 - This is only used by the C(assertonly) provider. 324 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 325 For alternatives, see the example on replacing C(assertonly). 326 type: bool 327 default: no 328 329 version: 330 description: 331 - The version of the certificate. 332 - Nowadays it should almost always be 3. 333 - This is only used by the C(assertonly) provider. 334 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 335 For alternatives, see the example on replacing C(assertonly). 336 type: int 337 338 valid_at: 339 description: 340 - The certificate must be valid at this point in time. 341 - The timestamp is formatted as an ASN.1 TIME. 342 - This is only used by the C(assertonly) provider. 343 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 344 For alternatives, see the example on replacing C(assertonly). 345 type: str 346 347 invalid_at: 348 description: 349 - The certificate must be invalid at this point in time. 350 - The timestamp is formatted as an ASN.1 TIME. 351 - This is only used by the C(assertonly) provider. 352 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 353 For alternatives, see the example on replacing C(assertonly). 354 type: str 355 356 not_before: 357 description: 358 - The certificate must start to become valid at this point in time. 359 - The timestamp is formatted as an ASN.1 TIME. 360 - This is only used by the C(assertonly) provider. 361 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 362 For alternatives, see the example on replacing C(assertonly). 363 type: str 364 aliases: [ notBefore ] 365 366 not_after: 367 description: 368 - The certificate must expire at this point in time. 369 - The timestamp is formatted as an ASN.1 TIME. 370 - This is only used by the C(assertonly) provider. 371 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 372 For alternatives, see the example on replacing C(assertonly). 373 type: str 374 aliases: [ notAfter ] 375 376 valid_in: 377 description: 378 - The certificate must still be valid at this relative time offset from now. 379 - Valid format is C([+-]timespec | number_of_seconds) where timespec can be an integer 380 + C([w | d | h | m | s]) (e.g. C(+32w1d2h). 381 - Note that if using this parameter, this module is NOT idempotent. 382 - This is only used by the C(assertonly) provider. 383 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 384 For alternatives, see the example on replacing C(assertonly). 385 type: str 386 387 key_usage: 388 description: 389 - The I(key_usage) extension field must contain all these values. 390 - This is only used by the C(assertonly) provider. 391 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 392 For alternatives, see the example on replacing C(assertonly). 393 type: list 394 elements: str 395 aliases: [ keyUsage ] 396 397 key_usage_strict: 398 description: 399 - If set to C(yes), the I(key_usage) extension field must contain only these values. 400 - This is only used by the C(assertonly) provider. 401 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 402 For alternatives, see the example on replacing C(assertonly). 403 type: bool 404 default: no 405 aliases: [ keyUsage_strict ] 406 407 extended_key_usage: 408 description: 409 - The I(extended_key_usage) extension field must contain all these values. 410 - This is only used by the C(assertonly) provider. 411 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 412 For alternatives, see the example on replacing C(assertonly). 413 type: list 414 elements: str 415 aliases: [ extendedKeyUsage ] 416 417 extended_key_usage_strict: 418 description: 419 - If set to C(yes), the I(extended_key_usage) extension field must contain only these values. 420 - This is only used by the C(assertonly) provider. 421 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 422 For alternatives, see the example on replacing C(assertonly). 423 type: bool 424 default: no 425 aliases: [ extendedKeyUsage_strict ] 426 427 subject_alt_name: 428 description: 429 - The I(subject_alt_name) extension field must contain these values. 430 - This is only used by the C(assertonly) provider. 431 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 432 For alternatives, see the example on replacing C(assertonly). 433 type: list 434 elements: str 435 aliases: [ subjectAltName ] 436 437 subject_alt_name_strict: 438 description: 439 - If set to C(yes), the I(subject_alt_name) extension field must contain only these values. 440 - This is only used by the C(assertonly) provider. 441 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 442 For alternatives, see the example on replacing C(assertonly). 443 type: bool 444 default: no 445 aliases: [ subjectAltName_strict ] 446 447 select_crypto_backend: 448 description: 449 - Determines which crypto backend to use. 450 - The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl). 451 - If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library. 452 - If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library. 453 - Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13. 454 From that point on, only the C(cryptography) backend will be available. 455 type: str 456 default: auto 457 choices: [ auto, cryptography, pyopenssl ] 458 version_added: "2.8" 459 460 backup: 461 description: 462 - Create a backup file including a timestamp so you can get the original 463 certificate back if you overwrote it with a new one by accident. 464 - This is not used by the C(assertonly) provider. 465 - This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in Ansible 2.13. 466 For alternatives, see the example on replacing C(assertonly). 467 type: bool 468 default: no 469 version_added: "2.8" 470 471 entrust_cert_type: 472 description: 473 - Specify the type of certificate requested. 474 - This is only used by the C(entrust) provider. 475 type: str 476 default: STANDARD_SSL 477 choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ] 478 version_added: "2.9" 479 480 entrust_requester_email: 481 description: 482 - The email of the requester of the certificate (for tracking purposes). 483 - This is only used by the C(entrust) provider. 484 - This is required if the provider is C(entrust). 485 type: str 486 version_added: "2.9" 487 488 entrust_requester_name: 489 description: 490 - The name of the requester of the certificate (for tracking purposes). 491 - This is only used by the C(entrust) provider. 492 - This is required if the provider is C(entrust). 493 type: str 494 version_added: "2.9" 495 496 entrust_requester_phone: 497 description: 498 - The phone number of the requester of the certificate (for tracking purposes). 499 - This is only used by the C(entrust) provider. 500 - This is required if the provider is C(entrust). 501 type: str 502 version_added: "2.9" 503 504 entrust_api_user: 505 description: 506 - The username for authentication to the Entrust Certificate Services (ECS) API. 507 - This is only used by the C(entrust) provider. 508 - This is required if the provider is C(entrust). 509 type: str 510 version_added: "2.9" 511 512 entrust_api_key: 513 description: 514 - The key (password) for authentication to the Entrust Certificate Services (ECS) API. 515 - This is only used by the C(entrust) provider. 516 - This is required if the provider is C(entrust). 517 type: str 518 version_added: "2.9" 519 520 entrust_api_client_cert_path: 521 description: 522 - The path to the client certificate used to authenticate to the Entrust Certificate Services (ECS) API. 523 - This is only used by the C(entrust) provider. 524 - This is required if the provider is C(entrust). 525 type: path 526 version_added: "2.9" 527 528 entrust_api_client_cert_key_path: 529 description: 530 - The path to the private key of the client certificate used to authenticate to the Entrust Certificate Services (ECS) API. 531 - This is only used by the C(entrust) provider. 532 - This is required if the provider is C(entrust). 533 type: path 534 version_added: "2.9" 535 536 entrust_not_after: 537 description: 538 - The point in time at which the certificate stops being valid. 539 - Time can be specified either as relative time or as an absolute timestamp. 540 - A valid absolute time format is C(ASN.1 TIME) such as C(2019-06-18). 541 - A valid relative time format is C([+-]timespec) where timespec can be an integer + C([w | d | h | m | s]), such as C(+365d) or C(+32w1d2h)). 542 - Time will always be interpreted as UTC. 543 - Note that only the date (day, month, year) is supported for specifying the expiry date of the issued certificate. 544 - The full date-time is adjusted to EST (GMT -5:00) before issuance, which may result in a certificate with an expiration date one day 545 earlier than expected if a relative time is used. 546 - The minimum certificate lifetime is 90 days, and maximum is three years. 547 - If this value is not specified, the certificate will stop being valid 365 days the date of issue. 548 - This is only used by the C(entrust) provider. 549 type: str 550 default: +365d 551 version_added: "2.9" 552 553 entrust_api_specification_path: 554 description: 555 - The path to the specification file defining the Entrust Certificate Services (ECS) API configuration. 556 - You can use this to keep a local copy of the specification to avoid downloading it every time the module is used. 557 - This is only used by the C(entrust) provider. 558 type: path 559 default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml 560 version_added: "2.9" 561 562extends_documentation_fragment: files 563notes: 564 - All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern. 565 - Date specified should be UTC. Minutes and seconds are mandatory. 566 - For security reason, when you use C(ownca) provider, you should NOT run M(openssl_certificate) on 567 a target machine, but on a dedicated CA machine. It is recommended not to store the CA private key 568 on the target machine. Once signed, the certificate can be moved to the target machine. 569seealso: 570- module: openssl_csr 571- module: openssl_dhparam 572- module: openssl_pkcs12 573- module: openssl_privatekey 574- module: openssl_publickey 575''' 576 577EXAMPLES = r''' 578- name: Generate a Self Signed OpenSSL certificate 579 openssl_certificate: 580 path: /etc/ssl/crt/ansible.com.crt 581 privatekey_path: /etc/ssl/private/ansible.com.pem 582 csr_path: /etc/ssl/csr/ansible.com.csr 583 provider: selfsigned 584 585- name: Generate an OpenSSL certificate signed with your own CA certificate 586 openssl_certificate: 587 path: /etc/ssl/crt/ansible.com.crt 588 csr_path: /etc/ssl/csr/ansible.com.csr 589 ownca_path: /etc/ssl/crt/ansible_CA.crt 590 ownca_privatekey_path: /etc/ssl/private/ansible_CA.pem 591 provider: ownca 592 593- name: Generate a Let's Encrypt Certificate 594 openssl_certificate: 595 path: /etc/ssl/crt/ansible.com.crt 596 csr_path: /etc/ssl/csr/ansible.com.csr 597 provider: acme 598 acme_accountkey_path: /etc/ssl/private/ansible.com.pem 599 acme_challenge_path: /etc/ssl/challenges/ansible.com/ 600 601- name: Force (re-)generate a new Let's Encrypt Certificate 602 openssl_certificate: 603 path: /etc/ssl/crt/ansible.com.crt 604 csr_path: /etc/ssl/csr/ansible.com.csr 605 provider: acme 606 acme_accountkey_path: /etc/ssl/private/ansible.com.pem 607 acme_challenge_path: /etc/ssl/challenges/ansible.com/ 608 force: yes 609 610- name: Generate an Entrust certificate via the Entrust Certificate Services (ECS) API 611 openssl_certificate: 612 path: /etc/ssl/crt/ansible.com.crt 613 csr_path: /etc/ssl/csr/ansible.com.csr 614 provider: entrust 615 entrust_requester_name: Jo Doe 616 entrust_requester_email: jdoe@ansible.com 617 entrust_requester_phone: 555-555-5555 618 entrust_cert_type: STANDARD_SSL 619 entrust_api_user: apiusername 620 entrust_api_key: a^lv*32!cd9LnT 621 entrust_api_client_cert_path: /etc/ssl/entrust/ecs-client.crt 622 entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-key.crt 623 entrust_api_specification_path: /etc/ssl/entrust/api-docs/cms-api-2.1.0.yaml 624 625# The following example shows one assertonly usage using all existing options for 626# assertonly, and shows how to emulate the behavior with the openssl_certificate_info, 627# openssl_csr_info, openssl_privatekey_info and assert modules: 628 629- openssl_certificate: 630 provider: assertonly 631 path: /etc/ssl/crt/ansible.com.crt 632 csr_path: /etc/ssl/csr/ansible.com.csr 633 privatekey_path: /etc/ssl/csr/ansible.com.key 634 signature_algorithms: 635 - sha256WithRSAEncryption 636 - sha512WithRSAEncryption 637 subject: 638 commonName: ansible.com 639 subject_strict: yes 640 issuer: 641 commonName: ansible.com 642 issuer_strict: yes 643 has_expired: no 644 version: 3 645 key_usage: 646 - Data Encipherment 647 key_usage_strict: yes 648 extended_key_usage: 649 - DVCS 650 extended_key_usage_strict: yes 651 subject_alt_name: 652 - dns:ansible.com 653 subject_alt_name_strict: yes 654 not_before: 20190331202428Z 655 not_after: 20190413202428Z 656 valid_at: "+1d10h" 657 invalid_at: 20200331202428Z 658 valid_in: 10 # in ten seconds 659 660- openssl_certificate_info: 661 path: /etc/ssl/crt/ansible.com.crt 662 # for valid_at, invalid_at and valid_in 663 valid_at: 664 one_day_ten_hours: "+1d10h" 665 fixed_timestamp: 20200331202428Z 666 ten_seconds: "+10" 667 register: result 668 669- openssl_csr_info: 670 # Verifies that the CSR signature is valid; module will fail if not 671 path: /etc/ssl/csr/ansible.com.csr 672 register: result_csr 673 674- openssl_privatekey_info: 675 path: /etc/ssl/csr/ansible.com.key 676 register: result_privatekey 677 678- assert: 679 that: 680 # When private key is specified for assertonly, this will be checked: 681 - result.public_key == result_privatekey.public_key 682 # When CSR is specified for assertonly, this will be checked: 683 - result.public_key == result_csr.public_key 684 - result.subject_ordered == result_csr.subject_ordered 685 - result.extensions_by_oid == result_csr.extensions_by_oid 686 # signature_algorithms check 687 - "result.signature_algorithm == 'sha256WithRSAEncryption' or result.signature_algorithm == 'sha512WithRSAEncryption'" 688 # subject and subject_strict 689 - "result.subject.commonName == 'ansible.com'" 690 - "result.subject | length == 1" # the number must be the number of entries you check for 691 # issuer and issuer_strict 692 - "result.issuer.commonName == 'ansible.com'" 693 - "result.issuer | length == 1" # the number must be the number of entries you check for 694 # has_expired 695 - not result.expired 696 # version 697 - result.version == 3 698 # key_usage and key_usage_strict 699 - "'Data Encipherment' in result.key_usage" 700 - "result.key_usage | length == 1" # the number must be the number of entries you check for 701 # extended_key_usage and extended_key_usage_strict 702 - "'DVCS' in result.extended_key_usage" 703 - "result.extended_key_usage | length == 1" # the number must be the number of entries you check for 704 # subject_alt_name and subject_alt_name_strict 705 - "'dns:ansible.com' in result.subject_alt_name" 706 - "result.subject_alt_name | length == 1" # the number must be the number of entries you check for 707 # not_before and not_after 708 - "result.not_before == '20190331202428Z'" 709 - "result.not_after == '20190413202428Z'" 710 # valid_at, invalid_at and valid_in 711 - "result.valid_at.one_day_ten_hours" # for valid_at 712 - "not result.valid_at.fixed_timestamp" # for invalid_at 713 - "result.valid_at.ten_seconds" # for valid_in 714 715# Examples for some checks one could use the assertonly provider for: 716# (Please note that assertonly has been deprecated!) 717 718# How to use the assertonly provider to implement and trigger your own custom certificate generation workflow: 719- name: Check if a certificate is currently still valid, ignoring failures 720 openssl_certificate: 721 path: /etc/ssl/crt/example.com.crt 722 provider: assertonly 723 has_expired: no 724 ignore_errors: yes 725 register: validity_check 726 727- name: Run custom task(s) to get a new, valid certificate in case the initial check failed 728 command: superspecialSSL recreate /etc/ssl/crt/example.com.crt 729 when: validity_check.failed 730 731- name: Check the new certificate again for validity with the same parameters, this time failing the play if it is still invalid 732 openssl_certificate: 733 path: /etc/ssl/crt/example.com.crt 734 provider: assertonly 735 has_expired: no 736 when: validity_check.failed 737 738# Some other checks that assertonly could be used for: 739- name: Verify that an existing certificate was issued by the Let's Encrypt CA and is currently still valid 740 openssl_certificate: 741 path: /etc/ssl/crt/example.com.crt 742 provider: assertonly 743 issuer: 744 O: Let's Encrypt 745 has_expired: no 746 747- name: Ensure that a certificate uses a modern signature algorithm (no SHA1, MD5 or DSA) 748 openssl_certificate: 749 path: /etc/ssl/crt/example.com.crt 750 provider: assertonly 751 signature_algorithms: 752 - sha224WithRSAEncryption 753 - sha256WithRSAEncryption 754 - sha384WithRSAEncryption 755 - sha512WithRSAEncryption 756 - sha224WithECDSAEncryption 757 - sha256WithECDSAEncryption 758 - sha384WithECDSAEncryption 759 - sha512WithECDSAEncryption 760 761- name: Ensure that the existing certificate belongs to the specified private key 762 openssl_certificate: 763 path: /etc/ssl/crt/example.com.crt 764 privatekey_path: /etc/ssl/private/example.com.pem 765 provider: assertonly 766 767- name: Ensure that the existing certificate is still valid at the winter solstice 2017 768 openssl_certificate: 769 path: /etc/ssl/crt/example.com.crt 770 provider: assertonly 771 valid_at: 20171221162800Z 772 773- name: Ensure that the existing certificate is still valid 2 weeks (1209600 seconds) from now 774 openssl_certificate: 775 path: /etc/ssl/crt/example.com.crt 776 provider: assertonly 777 valid_in: 1209600 778 779- name: Ensure that the existing certificate is only used for digital signatures and encrypting other keys 780 openssl_certificate: 781 path: /etc/ssl/crt/example.com.crt 782 provider: assertonly 783 key_usage: 784 - digitalSignature 785 - keyEncipherment 786 key_usage_strict: true 787 788- name: Ensure that the existing certificate can be used for client authentication 789 openssl_certificate: 790 path: /etc/ssl/crt/example.com.crt 791 provider: assertonly 792 extended_key_usage: 793 - clientAuth 794 795- name: Ensure that the existing certificate can only be used for client authentication and time stamping 796 openssl_certificate: 797 path: /etc/ssl/crt/example.com.crt 798 provider: assertonly 799 extended_key_usage: 800 - clientAuth 801 - 1.3.6.1.5.5.7.3.8 802 extended_key_usage_strict: true 803 804- name: Ensure that the existing certificate has a certain domain in its subjectAltName 805 openssl_certificate: 806 path: /etc/ssl/crt/example.com.crt 807 provider: assertonly 808 subject_alt_name: 809 - www.example.com 810 - test.example.com 811''' 812 813RETURN = r''' 814filename: 815 description: Path to the generated Certificate 816 returned: changed or success 817 type: str 818 sample: /etc/ssl/crt/www.ansible.com.crt 819backup_file: 820 description: Name of backup file created. 821 returned: changed and if I(backup) is C(yes) 822 type: str 823 sample: /path/to/www.ansible.com.crt.2019-03-09@11:22~ 824''' 825 826 827from random import randint 828import abc 829import datetime 830import time 831import os 832import traceback 833from distutils.version import LooseVersion 834 835from ansible.module_utils import crypto as crypto_utils 836from ansible.module_utils.basic import AnsibleModule, missing_required_lib 837from ansible.module_utils._text import to_native, to_bytes, to_text 838from ansible.module_utils.compat import ipaddress as compat_ipaddress 839from ansible.module_utils.ecs.api import ECSClient, RestOperationException, SessionConfigurationException 840 841MINIMAL_CRYPTOGRAPHY_VERSION = '1.6' 842MINIMAL_PYOPENSSL_VERSION = '0.15' 843 844PYOPENSSL_IMP_ERR = None 845try: 846 import OpenSSL 847 from OpenSSL import crypto 848 PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) 849except ImportError: 850 PYOPENSSL_IMP_ERR = traceback.format_exc() 851 PYOPENSSL_FOUND = False 852else: 853 PYOPENSSL_FOUND = True 854 855CRYPTOGRAPHY_IMP_ERR = None 856try: 857 import cryptography 858 from cryptography import x509 859 from cryptography.hazmat.backends import default_backend 860 from cryptography.hazmat.primitives.serialization import Encoding 861 from cryptography.x509 import NameAttribute, Name 862 from cryptography.x509.oid import NameOID 863 CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) 864except ImportError: 865 CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() 866 CRYPTOGRAPHY_FOUND = False 867else: 868 CRYPTOGRAPHY_FOUND = True 869 870 871class CertificateError(crypto_utils.OpenSSLObjectError): 872 pass 873 874 875class Certificate(crypto_utils.OpenSSLObject): 876 877 def __init__(self, module, backend): 878 super(Certificate, self).__init__( 879 module.params['path'], 880 module.params['state'], 881 module.params['force'], 882 module.check_mode 883 ) 884 885 self.provider = module.params['provider'] 886 self.privatekey_path = module.params['privatekey_path'] 887 self.privatekey_passphrase = module.params['privatekey_passphrase'] 888 self.csr_path = module.params['csr_path'] 889 self.cert = None 890 self.privatekey = None 891 self.csr = None 892 self.backend = backend 893 self.module = module 894 895 # The following are default values which make sure check() works as 896 # before if providers do not explicitly change these properties. 897 self.create_subject_key_identifier = 'never_create' 898 self.create_authority_key_identifier = False 899 900 self.backup = module.params['backup'] 901 self.backup_file = None 902 903 def get_relative_time_option(self, input_string, input_name): 904 """Return an ASN1 formatted string if a relative timespec 905 or an ASN1 formatted string is provided.""" 906 result = to_native(input_string) 907 if result is None: 908 raise CertificateError( 909 'The timespec "%s" for %s is not valid' % 910 input_string, input_name) 911 if result.startswith("+") or result.startswith("-"): 912 result_datetime = crypto_utils.convert_relative_to_datetime( 913 result) 914 if self.backend == 'pyopenssl': 915 return result_datetime.strftime("%Y%m%d%H%M%SZ") 916 elif self.backend == 'cryptography': 917 return result_datetime 918 if self.backend == 'cryptography': 919 for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']: 920 try: 921 return datetime.datetime.strptime(result, date_fmt) 922 except ValueError: 923 pass 924 925 raise CertificateError( 926 'The time spec "%s" for %s is invalid' % 927 (input_string, input_name) 928 ) 929 return input_string 930 931 def _validate_privatekey(self): 932 if self.backend == 'pyopenssl': 933 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) 934 ctx.use_privatekey(self.privatekey) 935 ctx.use_certificate(self.cert) 936 try: 937 ctx.check_privatekey() 938 return True 939 except OpenSSL.SSL.Error: 940 return False 941 elif self.backend == 'cryptography': 942 return crypto_utils.cryptography_compare_public_keys(self.cert.public_key(), self.privatekey.public_key()) 943 944 def _validate_csr(self): 945 if self.backend == 'pyopenssl': 946 # Verify that CSR is signed by certificate's private key 947 try: 948 self.csr.verify(self.cert.get_pubkey()) 949 except OpenSSL.crypto.Error: 950 return False 951 # Check subject 952 if self.csr.get_subject() != self.cert.get_subject(): 953 return False 954 # Check extensions 955 csr_extensions = self.csr.get_extensions() 956 cert_extension_count = self.cert.get_extension_count() 957 if len(csr_extensions) != cert_extension_count: 958 return False 959 for extension_number in range(0, cert_extension_count): 960 cert_extension = self.cert.get_extension(extension_number) 961 csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions) 962 if cert_extension.get_data() != list(csr_extension)[0].get_data(): 963 return False 964 return True 965 elif self.backend == 'cryptography': 966 # Verify that CSR is signed by certificate's private key 967 if not self.csr.is_signature_valid: 968 return False 969 if not crypto_utils.cryptography_compare_public_keys(self.csr.public_key(), self.cert.public_key()): 970 return False 971 # Check subject 972 if self.csr.subject != self.cert.subject: 973 return False 974 # Check extensions 975 cert_exts = list(self.cert.extensions) 976 csr_exts = list(self.csr.extensions) 977 if self.create_subject_key_identifier != 'never_create': 978 # Filter out SubjectKeyIdentifier extension before comparison 979 cert_exts = list(filter(lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier), cert_exts)) 980 csr_exts = list(filter(lambda x: not isinstance(x.value, x509.SubjectKeyIdentifier), csr_exts)) 981 if self.create_authority_key_identifier: 982 # Filter out AuthorityKeyIdentifier extension before comparison 983 cert_exts = list(filter(lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier), cert_exts)) 984 csr_exts = list(filter(lambda x: not isinstance(x.value, x509.AuthorityKeyIdentifier), csr_exts)) 985 if len(cert_exts) != len(csr_exts): 986 return False 987 for cert_ext in cert_exts: 988 try: 989 csr_ext = self.csr.extensions.get_extension_for_oid(cert_ext.oid) 990 if cert_ext != csr_ext: 991 return False 992 except cryptography.x509.ExtensionNotFound as dummy: 993 return False 994 return True 995 996 def remove(self, module): 997 if self.backup: 998 self.backup_file = module.backup_local(self.path) 999 super(Certificate, self).remove(module) 1000 1001 def check(self, module, perms_required=True): 1002 """Ensure the resource is in its desired state.""" 1003 1004 state_and_perms = super(Certificate, self).check(module, perms_required) 1005 1006 if not state_and_perms: 1007 return False 1008 1009 try: 1010 self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) 1011 except Exception as dummy: 1012 return False 1013 1014 if self.privatekey_path: 1015 try: 1016 self.privatekey = crypto_utils.load_privatekey( 1017 self.privatekey_path, 1018 self.privatekey_passphrase, 1019 backend=self.backend 1020 ) 1021 except crypto_utils.OpenSSLBadPassphraseError as exc: 1022 raise CertificateError(exc) 1023 if not self._validate_privatekey(): 1024 return False 1025 1026 if self.csr_path: 1027 self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) 1028 if not self._validate_csr(): 1029 return False 1030 1031 # Check SubjectKeyIdentifier 1032 if self.backend == 'cryptography' and self.create_subject_key_identifier != 'never_create': 1033 # Get hold of certificate's SKI 1034 try: 1035 ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) 1036 except cryptography.x509.ExtensionNotFound as dummy: 1037 return False 1038 # Get hold of CSR's SKI for 'create_if_not_provided' 1039 csr_ext = None 1040 if self.create_subject_key_identifier == 'create_if_not_provided': 1041 try: 1042 csr_ext = self.csr.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) 1043 except cryptography.x509.ExtensionNotFound as dummy: 1044 pass 1045 if csr_ext is None: 1046 # If CSR had no SKI, or we chose to ignore it ('always_create'), compare with created SKI 1047 if ext.value.digest != x509.SubjectKeyIdentifier.from_public_key(self.cert.public_key()).digest: 1048 return False 1049 else: 1050 # If CSR had SKI and we didn't ignore it ('create_if_not_provided'), compare SKIs 1051 if ext.value.digest != csr_ext.value.digest: 1052 return False 1053 1054 return True 1055 1056 1057class CertificateAbsent(Certificate): 1058 def __init__(self, module): 1059 super(CertificateAbsent, self).__init__(module, 'cryptography') # backend doesn't matter 1060 1061 def generate(self, module): 1062 pass 1063 1064 def dump(self, check_mode=False): 1065 # Use only for absent 1066 1067 result = { 1068 'changed': self.changed, 1069 'filename': self.path, 1070 'privatekey': self.privatekey_path, 1071 'csr': self.csr_path 1072 } 1073 if self.backup_file: 1074 result['backup_file'] = self.backup_file 1075 1076 return result 1077 1078 1079class SelfSignedCertificateCryptography(Certificate): 1080 """Generate the self-signed certificate, using the cryptography backend""" 1081 def __init__(self, module): 1082 super(SelfSignedCertificateCryptography, self).__init__(module, 'cryptography') 1083 self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier'] 1084 self.notBefore = self.get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before') 1085 self.notAfter = self.get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after') 1086 self.digest = crypto_utils.select_message_digest(module.params['selfsigned_digest']) 1087 self.version = module.params['selfsigned_version'] 1088 self.serial_number = x509.random_serial_number() 1089 1090 if not os.path.exists(self.csr_path): 1091 raise CertificateError( 1092 'The certificate signing request file {0} does not exist'.format(self.csr_path) 1093 ) 1094 if not os.path.exists(self.privatekey_path): 1095 raise CertificateError( 1096 'The private key file {0} does not exist'.format(self.privatekey_path) 1097 ) 1098 1099 self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) 1100 self._module = module 1101 1102 try: 1103 self.privatekey = crypto_utils.load_privatekey( 1104 self.privatekey_path, self.privatekey_passphrase, backend=self.backend 1105 ) 1106 except crypto_utils.OpenSSLBadPassphraseError as exc: 1107 module.fail_json(msg=to_native(exc)) 1108 1109 if crypto_utils.cryptography_key_needs_digest_for_signing(self.privatekey): 1110 if self.digest is None: 1111 raise CertificateError( 1112 'The digest %s is not supported with the cryptography backend' % module.params['selfsigned_digest'] 1113 ) 1114 else: 1115 self.digest = None 1116 1117 def generate(self, module): 1118 if not os.path.exists(self.privatekey_path): 1119 raise CertificateError( 1120 'The private key %s does not exist' % self.privatekey_path 1121 ) 1122 if not os.path.exists(self.csr_path): 1123 raise CertificateError( 1124 'The certificate signing request file %s does not exist' % self.csr_path 1125 ) 1126 if not self.check(module, perms_required=False) or self.force: 1127 try: 1128 cert_builder = x509.CertificateBuilder() 1129 cert_builder = cert_builder.subject_name(self.csr.subject) 1130 cert_builder = cert_builder.issuer_name(self.csr.subject) 1131 cert_builder = cert_builder.serial_number(self.serial_number) 1132 cert_builder = cert_builder.not_valid_before(self.notBefore) 1133 cert_builder = cert_builder.not_valid_after(self.notAfter) 1134 cert_builder = cert_builder.public_key(self.privatekey.public_key()) 1135 has_ski = False 1136 for extension in self.csr.extensions: 1137 if isinstance(extension.value, x509.SubjectKeyIdentifier): 1138 if self.create_subject_key_identifier == 'always_create': 1139 continue 1140 has_ski = True 1141 cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical) 1142 if not has_ski and self.create_subject_key_identifier != 'never_create': 1143 cert_builder = cert_builder.add_extension( 1144 x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()), 1145 critical=False 1146 ) 1147 except ValueError as e: 1148 raise CertificateError(str(e)) 1149 1150 try: 1151 certificate = cert_builder.sign( 1152 private_key=self.privatekey, algorithm=self.digest, 1153 backend=default_backend() 1154 ) 1155 except TypeError as e: 1156 if str(e) == 'Algorithm must be a registered hash algorithm.' and self.digest is None: 1157 module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.') 1158 raise 1159 1160 self.cert = certificate 1161 1162 if self.backup: 1163 self.backup_file = module.backup_local(self.path) 1164 crypto_utils.write_file(module, certificate.public_bytes(Encoding.PEM)) 1165 self.changed = True 1166 else: 1167 self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) 1168 1169 file_args = module.load_file_common_arguments(module.params) 1170 if module.set_fs_attributes_if_different(file_args, False): 1171 self.changed = True 1172 1173 def dump(self, check_mode=False): 1174 1175 result = { 1176 'changed': self.changed, 1177 'filename': self.path, 1178 'privatekey': self.privatekey_path, 1179 'csr': self.csr_path 1180 } 1181 if self.backup_file: 1182 result['backup_file'] = self.backup_file 1183 1184 if check_mode: 1185 result.update({ 1186 'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"), 1187 'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"), 1188 'serial_number': self.serial_number, 1189 }) 1190 else: 1191 result.update({ 1192 'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"), 1193 'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"), 1194 'serial_number': self.cert.serial_number, 1195 }) 1196 1197 return result 1198 1199 1200class SelfSignedCertificate(Certificate): 1201 """Generate the self-signed certificate.""" 1202 1203 def __init__(self, module): 1204 super(SelfSignedCertificate, self).__init__(module, 'pyopenssl') 1205 if module.params['selfsigned_create_subject_key_identifier'] != 'create_if_not_provided': 1206 module.fail_json(msg='selfsigned_create_subject_key_identifier cannot be used with the pyOpenSSL backend!') 1207 self.notBefore = self.get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before') 1208 self.notAfter = self.get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after') 1209 self.digest = module.params['selfsigned_digest'] 1210 self.version = module.params['selfsigned_version'] 1211 self.serial_number = randint(1000, 99999) 1212 1213 if not os.path.exists(self.csr_path): 1214 raise CertificateError( 1215 'The certificate signing request file {0} does not exist'.format(self.csr_path) 1216 ) 1217 if not os.path.exists(self.privatekey_path): 1218 raise CertificateError( 1219 'The private key file {0} does not exist'.format(self.privatekey_path) 1220 ) 1221 1222 self.csr = crypto_utils.load_certificate_request(self.csr_path) 1223 try: 1224 self.privatekey = crypto_utils.load_privatekey( 1225 self.privatekey_path, self.privatekey_passphrase 1226 ) 1227 except crypto_utils.OpenSSLBadPassphraseError as exc: 1228 module.fail_json(msg=str(exc)) 1229 1230 def generate(self, module): 1231 1232 if not os.path.exists(self.privatekey_path): 1233 raise CertificateError( 1234 'The private key %s does not exist' % self.privatekey_path 1235 ) 1236 1237 if not os.path.exists(self.csr_path): 1238 raise CertificateError( 1239 'The certificate signing request file %s does not exist' % self.csr_path 1240 ) 1241 1242 if not self.check(module, perms_required=False) or self.force: 1243 cert = crypto.X509() 1244 cert.set_serial_number(self.serial_number) 1245 cert.set_notBefore(to_bytes(self.notBefore)) 1246 cert.set_notAfter(to_bytes(self.notAfter)) 1247 cert.set_subject(self.csr.get_subject()) 1248 cert.set_issuer(self.csr.get_subject()) 1249 cert.set_version(self.version - 1) 1250 cert.set_pubkey(self.csr.get_pubkey()) 1251 cert.add_extensions(self.csr.get_extensions()) 1252 1253 cert.sign(self.privatekey, self.digest) 1254 self.cert = cert 1255 1256 if self.backup: 1257 self.backup_file = module.backup_local(self.path) 1258 crypto_utils.write_file(module, crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)) 1259 self.changed = True 1260 1261 file_args = module.load_file_common_arguments(module.params) 1262 if module.set_fs_attributes_if_different(file_args, False): 1263 self.changed = True 1264 1265 def dump(self, check_mode=False): 1266 1267 result = { 1268 'changed': self.changed, 1269 'filename': self.path, 1270 'privatekey': self.privatekey_path, 1271 'csr': self.csr_path 1272 } 1273 if self.backup_file: 1274 result['backup_file'] = self.backup_file 1275 1276 if check_mode: 1277 result.update({ 1278 'notBefore': self.notBefore, 1279 'notAfter': self.notAfter, 1280 'serial_number': self.serial_number, 1281 }) 1282 else: 1283 result.update({ 1284 'notBefore': self.cert.get_notBefore(), 1285 'notAfter': self.cert.get_notAfter(), 1286 'serial_number': self.cert.get_serial_number(), 1287 }) 1288 1289 return result 1290 1291 1292class OwnCACertificateCryptography(Certificate): 1293 """Generate the own CA certificate. Using the cryptography backend""" 1294 def __init__(self, module): 1295 super(OwnCACertificateCryptography, self).__init__(module, 'cryptography') 1296 self.create_subject_key_identifier = module.params['ownca_create_subject_key_identifier'] 1297 self.create_authority_key_identifier = module.params['ownca_create_authority_key_identifier'] 1298 self.notBefore = self.get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before') 1299 self.notAfter = self.get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after') 1300 self.digest = crypto_utils.select_message_digest(module.params['ownca_digest']) 1301 self.version = module.params['ownca_version'] 1302 self.serial_number = x509.random_serial_number() 1303 self.ca_cert_path = module.params['ownca_path'] 1304 self.ca_privatekey_path = module.params['ownca_privatekey_path'] 1305 self.ca_privatekey_passphrase = module.params['ownca_privatekey_passphrase'] 1306 1307 if not os.path.exists(self.csr_path): 1308 raise CertificateError( 1309 'The certificate signing request file {0} does not exist'.format(self.csr_path) 1310 ) 1311 if not os.path.exists(self.ca_cert_path): 1312 raise CertificateError( 1313 'The CA certificate file {0} does not exist'.format(self.ca_cert_path) 1314 ) 1315 if not os.path.exists(self.ca_privatekey_path): 1316 raise CertificateError( 1317 'The CA private key file {0} does not exist'.format(self.ca_privatekey_path) 1318 ) 1319 1320 self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) 1321 self.ca_cert = crypto_utils.load_certificate(self.ca_cert_path, backend=self.backend) 1322 try: 1323 self.ca_private_key = crypto_utils.load_privatekey( 1324 self.ca_privatekey_path, self.ca_privatekey_passphrase, backend=self.backend 1325 ) 1326 except crypto_utils.OpenSSLBadPassphraseError as exc: 1327 module.fail_json(msg=str(exc)) 1328 1329 if crypto_utils.cryptography_key_needs_digest_for_signing(self.ca_private_key): 1330 if self.digest is None: 1331 raise CertificateError( 1332 'The digest %s is not supported with the cryptography backend' % module.params['ownca_digest'] 1333 ) 1334 else: 1335 self.digest = None 1336 1337 def generate(self, module): 1338 1339 if not os.path.exists(self.ca_cert_path): 1340 raise CertificateError( 1341 'The CA certificate %s does not exist' % self.ca_cert_path 1342 ) 1343 1344 if not os.path.exists(self.ca_privatekey_path): 1345 raise CertificateError( 1346 'The CA private key %s does not exist' % self.ca_privatekey_path 1347 ) 1348 1349 if not os.path.exists(self.csr_path): 1350 raise CertificateError( 1351 'The certificate signing request file %s does not exist' % self.csr_path 1352 ) 1353 1354 if not self.check(module, perms_required=False) or self.force: 1355 cert_builder = x509.CertificateBuilder() 1356 cert_builder = cert_builder.subject_name(self.csr.subject) 1357 cert_builder = cert_builder.issuer_name(self.ca_cert.subject) 1358 cert_builder = cert_builder.serial_number(self.serial_number) 1359 cert_builder = cert_builder.not_valid_before(self.notBefore) 1360 cert_builder = cert_builder.not_valid_after(self.notAfter) 1361 cert_builder = cert_builder.public_key(self.csr.public_key()) 1362 has_ski = False 1363 for extension in self.csr.extensions: 1364 if isinstance(extension.value, x509.SubjectKeyIdentifier): 1365 if self.create_subject_key_identifier == 'always_create': 1366 continue 1367 has_ski = True 1368 if self.create_authority_key_identifier and isinstance(extension.value, x509.AuthorityKeyIdentifier): 1369 continue 1370 cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical) 1371 if not has_ski and self.create_subject_key_identifier != 'never_create': 1372 cert_builder = cert_builder.add_extension( 1373 x509.SubjectKeyIdentifier.from_public_key(self.csr.public_key()), 1374 critical=False 1375 ) 1376 if self.create_authority_key_identifier: 1377 try: 1378 ext = self.ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) 1379 cert_builder = cert_builder.add_extension( 1380 x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext.value) 1381 if CRYPTOGRAPHY_VERSION >= LooseVersion('2.7') else 1382 x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext), 1383 critical=False 1384 ) 1385 except cryptography.x509.ExtensionNotFound: 1386 cert_builder = cert_builder.add_extension( 1387 x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()), 1388 critical=False 1389 ) 1390 1391 try: 1392 certificate = cert_builder.sign( 1393 private_key=self.ca_private_key, algorithm=self.digest, 1394 backend=default_backend() 1395 ) 1396 except TypeError as e: 1397 if str(e) == 'Algorithm must be a registered hash algorithm.' and self.digest is None: 1398 module.fail_json(msg='Signing with Ed25519 and Ed448 keys requires cryptography 2.8 or newer.') 1399 raise 1400 1401 self.cert = certificate 1402 1403 if self.backup: 1404 self.backup_file = module.backup_local(self.path) 1405 crypto_utils.write_file(module, certificate.public_bytes(Encoding.PEM)) 1406 self.changed = True 1407 else: 1408 self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) 1409 1410 file_args = module.load_file_common_arguments(module.params) 1411 if module.set_fs_attributes_if_different(file_args, False): 1412 self.changed = True 1413 1414 def check(self, module, perms_required=True): 1415 """Ensure the resource is in its desired state.""" 1416 1417 if not super(OwnCACertificateCryptography, self).check(module, perms_required): 1418 return False 1419 1420 # Check AuthorityKeyIdentifier 1421 if self.create_authority_key_identifier: 1422 try: 1423 ext = self.ca_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) 1424 expected_ext = ( 1425 x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext.value) 1426 if CRYPTOGRAPHY_VERSION >= LooseVersion('2.7') else 1427 x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ext) 1428 ) 1429 except cryptography.x509.ExtensionNotFound: 1430 expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()) 1431 try: 1432 ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) 1433 if ext.value != expected_ext: 1434 return False 1435 except cryptography.x509.ExtensionNotFound as dummy: 1436 return False 1437 1438 return True 1439 1440 def dump(self, check_mode=False): 1441 1442 result = { 1443 'changed': self.changed, 1444 'filename': self.path, 1445 'privatekey': self.privatekey_path, 1446 'csr': self.csr_path, 1447 'ca_cert': self.ca_cert_path, 1448 'ca_privatekey': self.ca_privatekey_path 1449 } 1450 if self.backup_file: 1451 result['backup_file'] = self.backup_file 1452 1453 if check_mode: 1454 result.update({ 1455 'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"), 1456 'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"), 1457 'serial_number': self.serial_number, 1458 }) 1459 else: 1460 result.update({ 1461 'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"), 1462 'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"), 1463 'serial_number': self.cert.serial_number, 1464 }) 1465 1466 return result 1467 1468 1469class OwnCACertificate(Certificate): 1470 """Generate the own CA certificate.""" 1471 1472 def __init__(self, module): 1473 super(OwnCACertificate, self).__init__(module, 'pyopenssl') 1474 self.notBefore = self.get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before') 1475 self.notAfter = self.get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after') 1476 self.digest = module.params['ownca_digest'] 1477 self.version = module.params['ownca_version'] 1478 self.serial_number = randint(1000, 99999) 1479 if module.params['ownca_create_subject_key_identifier'] != 'create_if_not_provided': 1480 module.fail_json(msg='ownca_create_subject_key_identifier cannot be used with the pyOpenSSL backend!') 1481 if module.params['ownca_create_authority_key_identifier']: 1482 module.warn('ownca_create_authority_key_identifier is ignored by the pyOpenSSL backend!') 1483 self.ca_cert_path = module.params['ownca_path'] 1484 self.ca_privatekey_path = module.params['ownca_privatekey_path'] 1485 self.ca_privatekey_passphrase = module.params['ownca_privatekey_passphrase'] 1486 1487 if not os.path.exists(self.csr_path): 1488 raise CertificateError( 1489 'The certificate signing request file {0} does not exist'.format(self.csr_path) 1490 ) 1491 if not os.path.exists(self.ca_cert_path): 1492 raise CertificateError( 1493 'The CA certificate file {0} does not exist'.format(self.ca_cert_path) 1494 ) 1495 if not os.path.exists(self.ca_privatekey_path): 1496 raise CertificateError( 1497 'The CA private key file {0} does not exist'.format(self.ca_privatekey_path) 1498 ) 1499 1500 self.csr = crypto_utils.load_certificate_request(self.csr_path) 1501 self.ca_cert = crypto_utils.load_certificate(self.ca_cert_path) 1502 try: 1503 self.ca_privatekey = crypto_utils.load_privatekey( 1504 self.ca_privatekey_path, self.ca_privatekey_passphrase 1505 ) 1506 except crypto_utils.OpenSSLBadPassphraseError as exc: 1507 module.fail_json(msg=str(exc)) 1508 1509 def generate(self, module): 1510 1511 if not os.path.exists(self.ca_cert_path): 1512 raise CertificateError( 1513 'The CA certificate %s does not exist' % self.ca_cert_path 1514 ) 1515 1516 if not os.path.exists(self.ca_privatekey_path): 1517 raise CertificateError( 1518 'The CA private key %s does not exist' % self.ca_privatekey_path 1519 ) 1520 1521 if not os.path.exists(self.csr_path): 1522 raise CertificateError( 1523 'The certificate signing request file %s does not exist' % self.csr_path 1524 ) 1525 1526 if not self.check(module, perms_required=False) or self.force: 1527 cert = crypto.X509() 1528 cert.set_serial_number(self.serial_number) 1529 cert.set_notBefore(to_bytes(self.notBefore)) 1530 cert.set_notAfter(to_bytes(self.notAfter)) 1531 cert.set_subject(self.csr.get_subject()) 1532 cert.set_issuer(self.ca_cert.get_subject()) 1533 cert.set_version(self.version - 1) 1534 cert.set_pubkey(self.csr.get_pubkey()) 1535 cert.add_extensions(self.csr.get_extensions()) 1536 1537 cert.sign(self.ca_privatekey, self.digest) 1538 self.cert = cert 1539 1540 if self.backup: 1541 self.backup_file = module.backup_local(self.path) 1542 crypto_utils.write_file(module, crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)) 1543 self.changed = True 1544 1545 file_args = module.load_file_common_arguments(module.params) 1546 if module.set_fs_attributes_if_different(file_args, False): 1547 self.changed = True 1548 1549 def dump(self, check_mode=False): 1550 1551 result = { 1552 'changed': self.changed, 1553 'filename': self.path, 1554 'privatekey': self.privatekey_path, 1555 'csr': self.csr_path, 1556 'ca_cert': self.ca_cert_path, 1557 'ca_privatekey': self.ca_privatekey_path 1558 } 1559 if self.backup_file: 1560 result['backup_file'] = self.backup_file 1561 1562 if check_mode: 1563 result.update({ 1564 'notBefore': self.notBefore, 1565 'notAfter': self.notAfter, 1566 'serial_number': self.serial_number, 1567 }) 1568 else: 1569 result.update({ 1570 'notBefore': self.cert.get_notBefore(), 1571 'notAfter': self.cert.get_notAfter(), 1572 'serial_number': self.cert.get_serial_number(), 1573 }) 1574 1575 return result 1576 1577 1578def compare_sets(subset, superset, equality=False): 1579 if equality: 1580 return set(subset) == set(superset) 1581 else: 1582 return all(x in superset for x in subset) 1583 1584 1585def compare_dicts(subset, superset, equality=False): 1586 if equality: 1587 return subset == superset 1588 else: 1589 return all(superset.get(x) == v for x, v in subset.items()) 1590 1591 1592NO_EXTENSION = 'no extension' 1593 1594 1595class AssertOnlyCertificateBase(Certificate): 1596 1597 def __init__(self, module, backend): 1598 super(AssertOnlyCertificateBase, self).__init__(module, backend) 1599 1600 self.signature_algorithms = module.params['signature_algorithms'] 1601 if module.params['subject']: 1602 self.subject = crypto_utils.parse_name_field(module.params['subject']) 1603 else: 1604 self.subject = [] 1605 self.subject_strict = module.params['subject_strict'] 1606 if module.params['issuer']: 1607 self.issuer = crypto_utils.parse_name_field(module.params['issuer']) 1608 else: 1609 self.issuer = [] 1610 self.issuer_strict = module.params['issuer_strict'] 1611 self.has_expired = module.params['has_expired'] 1612 self.version = module.params['version'] 1613 self.key_usage = module.params['key_usage'] 1614 self.key_usage_strict = module.params['key_usage_strict'] 1615 self.extended_key_usage = module.params['extended_key_usage'] 1616 self.extended_key_usage_strict = module.params['extended_key_usage_strict'] 1617 self.subject_alt_name = module.params['subject_alt_name'] 1618 self.subject_alt_name_strict = module.params['subject_alt_name_strict'] 1619 self.not_before = module.params['not_before'] 1620 self.not_after = module.params['not_after'] 1621 self.valid_at = module.params['valid_at'] 1622 self.invalid_at = module.params['invalid_at'] 1623 self.valid_in = module.params['valid_in'] 1624 if self.valid_in and not self.valid_in.startswith("+") and not self.valid_in.startswith("-"): 1625 try: 1626 int(self.valid_in) 1627 except ValueError: 1628 module.fail_json(msg='The supplied value for "valid_in" (%s) is not an integer or a valid timespec' % self.valid_in) 1629 self.valid_in = "+" + self.valid_in + "s" 1630 1631 # Load objects 1632 self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) 1633 if self.privatekey_path is not None: 1634 try: 1635 self.privatekey = crypto_utils.load_privatekey( 1636 self.privatekey_path, 1637 self.privatekey_passphrase, 1638 backend=self.backend 1639 ) 1640 except crypto_utils.OpenSSLBadPassphraseError as exc: 1641 raise CertificateError(exc) 1642 if self.csr_path is not None: 1643 self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) 1644 1645 @abc.abstractmethod 1646 def _validate_privatekey(self): 1647 pass 1648 1649 @abc.abstractmethod 1650 def _validate_csr_signature(self): 1651 pass 1652 1653 @abc.abstractmethod 1654 def _validate_csr_subject(self): 1655 pass 1656 1657 @abc.abstractmethod 1658 def _validate_csr_extensions(self): 1659 pass 1660 1661 @abc.abstractmethod 1662 def _validate_signature_algorithms(self): 1663 pass 1664 1665 @abc.abstractmethod 1666 def _validate_subject(self): 1667 pass 1668 1669 @abc.abstractmethod 1670 def _validate_issuer(self): 1671 pass 1672 1673 @abc.abstractmethod 1674 def _validate_has_expired(self): 1675 pass 1676 1677 @abc.abstractmethod 1678 def _validate_version(self): 1679 pass 1680 1681 @abc.abstractmethod 1682 def _validate_key_usage(self): 1683 pass 1684 1685 @abc.abstractmethod 1686 def _validate_extended_key_usage(self): 1687 pass 1688 1689 @abc.abstractmethod 1690 def _validate_subject_alt_name(self): 1691 pass 1692 1693 @abc.abstractmethod 1694 def _validate_not_before(self): 1695 pass 1696 1697 @abc.abstractmethod 1698 def _validate_not_after(self): 1699 pass 1700 1701 @abc.abstractmethod 1702 def _validate_valid_at(self): 1703 pass 1704 1705 @abc.abstractmethod 1706 def _validate_invalid_at(self): 1707 pass 1708 1709 @abc.abstractmethod 1710 def _validate_valid_in(self): 1711 pass 1712 1713 def assertonly(self, module): 1714 messages = [] 1715 if self.privatekey_path is not None: 1716 if not self._validate_privatekey(): 1717 messages.append( 1718 'Certificate %s and private key %s do not match' % 1719 (self.path, self.privatekey_path) 1720 ) 1721 1722 if self.csr_path is not None: 1723 if not self._validate_csr_signature(): 1724 messages.append( 1725 'Certificate %s and CSR %s do not match: private key mismatch' % 1726 (self.path, self.csr_path) 1727 ) 1728 if not self._validate_csr_subject(): 1729 messages.append( 1730 'Certificate %s and CSR %s do not match: subject mismatch' % 1731 (self.path, self.csr_path) 1732 ) 1733 if not self._validate_csr_extensions(): 1734 messages.append( 1735 'Certificate %s and CSR %s do not match: extensions mismatch' % 1736 (self.path, self.csr_path) 1737 ) 1738 1739 if self.signature_algorithms is not None: 1740 wrong_alg = self._validate_signature_algorithms() 1741 if wrong_alg: 1742 messages.append( 1743 'Invalid signature algorithm (got %s, expected one of %s)' % 1744 (wrong_alg, self.signature_algorithms) 1745 ) 1746 1747 if self.subject is not None: 1748 failure = self._validate_subject() 1749 if failure: 1750 dummy, cert_subject = failure 1751 messages.append( 1752 'Invalid subject component (got %s, expected all of %s to be present)' % 1753 (cert_subject, self.subject) 1754 ) 1755 1756 if self.issuer is not None: 1757 failure = self._validate_issuer() 1758 if failure: 1759 dummy, cert_issuer = failure 1760 messages.append( 1761 'Invalid issuer component (got %s, expected all of %s to be present)' % (cert_issuer, self.issuer) 1762 ) 1763 1764 if self.has_expired is not None: 1765 cert_expired = self._validate_has_expired() 1766 if cert_expired != self.has_expired: 1767 messages.append( 1768 'Certificate expiration check failed (certificate expiration is %s, expected %s)' % 1769 (cert_expired, self.has_expired) 1770 ) 1771 1772 if self.version is not None: 1773 cert_version = self._validate_version() 1774 if cert_version != self.version: 1775 messages.append( 1776 'Invalid certificate version number (got %s, expected %s)' % 1777 (cert_version, self.version) 1778 ) 1779 1780 if self.key_usage is not None: 1781 failure = self._validate_key_usage() 1782 if failure == NO_EXTENSION: 1783 messages.append('Found no keyUsage extension') 1784 elif failure: 1785 dummy, cert_key_usage = failure 1786 messages.append( 1787 'Invalid keyUsage components (got %s, expected all of %s to be present)' % 1788 (cert_key_usage, self.key_usage) 1789 ) 1790 1791 if self.extended_key_usage is not None: 1792 failure = self._validate_extended_key_usage() 1793 if failure == NO_EXTENSION: 1794 messages.append('Found no extendedKeyUsage extension') 1795 elif failure: 1796 dummy, ext_cert_key_usage = failure 1797 messages.append( 1798 'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (ext_cert_key_usage, self.extended_key_usage) 1799 ) 1800 1801 if self.subject_alt_name is not None: 1802 failure = self._validate_subject_alt_name() 1803 if failure == NO_EXTENSION: 1804 messages.append('Found no subjectAltName extension') 1805 elif failure: 1806 dummy, cert_san = failure 1807 messages.append( 1808 'Invalid subjectAltName component (got %s, expected all of %s to be present)' % 1809 (cert_san, self.subject_alt_name) 1810 ) 1811 1812 if self.not_before is not None: 1813 cert_not_valid_before = self._validate_not_before() 1814 if cert_not_valid_before != self.get_relative_time_option(self.not_before, 'not_before'): 1815 messages.append( 1816 'Invalid not_before component (got %s, expected %s to be present)' % 1817 (cert_not_valid_before, self.not_before) 1818 ) 1819 1820 if self.not_after is not None: 1821 cert_not_valid_after = self._validate_not_after() 1822 if cert_not_valid_after != self.get_relative_time_option(self.not_after, 'not_after'): 1823 messages.append( 1824 'Invalid not_after component (got %s, expected %s to be present)' % 1825 (cert_not_valid_after, self.not_after) 1826 ) 1827 1828 if self.valid_at is not None: 1829 not_before, valid_at, not_after = self._validate_valid_at() 1830 if not (not_before <= valid_at <= not_after): 1831 messages.append( 1832 'Certificate is not valid for the specified date (%s) - not_before: %s - not_after: %s' % 1833 (self.valid_at, not_before, not_after) 1834 ) 1835 1836 if self.invalid_at is not None: 1837 not_before, invalid_at, not_after = self._validate_invalid_at() 1838 if not_before <= invalid_at <= not_after: 1839 messages.append( 1840 'Certificate is not invalid for the specified date (%s) - not_before: %s - not_after: %s' % 1841 (self.invalid_at, not_before, not_after) 1842 ) 1843 1844 if self.valid_in is not None: 1845 not_before, valid_in, not_after = self._validate_valid_in() 1846 if not not_before <= valid_in <= not_after: 1847 messages.append( 1848 'Certificate is not valid in %s from now (that would be %s) - not_before: %s - not_after: %s' % 1849 (self.valid_in, valid_in, not_before, not_after) 1850 ) 1851 return messages 1852 1853 def generate(self, module): 1854 """Don't generate anything - only assert""" 1855 messages = self.assertonly(module) 1856 if messages: 1857 module.fail_json(msg=' | '.join(messages)) 1858 1859 def check(self, module, perms_required=False): 1860 """Ensure the resource is in its desired state.""" 1861 messages = self.assertonly(module) 1862 return len(messages) == 0 1863 1864 def dump(self, check_mode=False): 1865 result = { 1866 'changed': self.changed, 1867 'filename': self.path, 1868 'privatekey': self.privatekey_path, 1869 'csr': self.csr_path, 1870 } 1871 return result 1872 1873 1874class AssertOnlyCertificateCryptography(AssertOnlyCertificateBase): 1875 """Validate the supplied cert, using the cryptography backend""" 1876 def __init__(self, module): 1877 super(AssertOnlyCertificateCryptography, self).__init__(module, 'cryptography') 1878 1879 def _validate_privatekey(self): 1880 return crypto_utils.cryptography_compare_public_keys(self.cert.public_key(), self.privatekey.public_key()) 1881 1882 def _validate_csr_signature(self): 1883 if not self.csr.is_signature_valid: 1884 return False 1885 return crypto_utils.cryptography_compare_public_keys(self.csr.public_key(), self.cert.public_key()) 1886 1887 def _validate_csr_subject(self): 1888 return self.csr.subject == self.cert.subject 1889 1890 def _validate_csr_extensions(self): 1891 cert_exts = self.cert.extensions 1892 csr_exts = self.csr.extensions 1893 if len(cert_exts) != len(csr_exts): 1894 return False 1895 for cert_ext in cert_exts: 1896 try: 1897 csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid) 1898 if cert_ext != csr_ext: 1899 return False 1900 except cryptography.x509.ExtensionNotFound as dummy: 1901 return False 1902 return True 1903 1904 def _validate_signature_algorithms(self): 1905 if self.cert.signature_algorithm_oid._name not in self.signature_algorithms: 1906 return self.cert.signature_algorithm_oid._name 1907 1908 def _validate_subject(self): 1909 expected_subject = Name([NameAttribute(oid=crypto_utils.cryptography_name_to_oid(sub[0]), value=to_text(sub[1])) 1910 for sub in self.subject]) 1911 cert_subject = self.cert.subject 1912 if not compare_sets(expected_subject, cert_subject, self.subject_strict): 1913 return expected_subject, cert_subject 1914 1915 def _validate_issuer(self): 1916 expected_issuer = Name([NameAttribute(oid=crypto_utils.cryptography_name_to_oid(iss[0]), value=to_text(iss[1])) 1917 for iss in self.issuer]) 1918 cert_issuer = self.cert.issuer 1919 if not compare_sets(expected_issuer, cert_issuer, self.issuer_strict): 1920 return self.issuer, cert_issuer 1921 1922 def _validate_has_expired(self): 1923 cert_not_after = self.cert.not_valid_after 1924 cert_expired = cert_not_after < datetime.datetime.utcnow() 1925 return cert_expired 1926 1927 def _validate_version(self): 1928 if self.cert.version == x509.Version.v1: 1929 return 1 1930 if self.cert.version == x509.Version.v3: 1931 return 3 1932 return "unknown" 1933 1934 def _validate_key_usage(self): 1935 try: 1936 current_key_usage = self.cert.extensions.get_extension_for_class(x509.KeyUsage).value 1937 test_key_usage = dict( 1938 digital_signature=current_key_usage.digital_signature, 1939 content_commitment=current_key_usage.content_commitment, 1940 key_encipherment=current_key_usage.key_encipherment, 1941 data_encipherment=current_key_usage.data_encipherment, 1942 key_agreement=current_key_usage.key_agreement, 1943 key_cert_sign=current_key_usage.key_cert_sign, 1944 crl_sign=current_key_usage.crl_sign, 1945 encipher_only=False, 1946 decipher_only=False 1947 ) 1948 if test_key_usage['key_agreement']: 1949 test_key_usage.update(dict( 1950 encipher_only=current_key_usage.encipher_only, 1951 decipher_only=current_key_usage.decipher_only 1952 )) 1953 1954 key_usages = crypto_utils.cryptography_parse_key_usage_params(self.key_usage) 1955 if not compare_dicts(key_usages, test_key_usage, self.key_usage_strict): 1956 return self.key_usage, [k for k, v in test_key_usage.items() if v is True] 1957 1958 except cryptography.x509.ExtensionNotFound: 1959 # This is only bad if the user specified a non-empty list 1960 if self.key_usage: 1961 return NO_EXTENSION 1962 1963 def _validate_extended_key_usage(self): 1964 try: 1965 current_ext_keyusage = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value 1966 usages = [crypto_utils.cryptography_name_to_oid(usage) for usage in self.extended_key_usage] 1967 expected_ext_keyusage = x509.ExtendedKeyUsage(usages) 1968 if not compare_sets(expected_ext_keyusage, current_ext_keyusage, self.extended_key_usage_strict): 1969 return [eku.value for eku in expected_ext_keyusage], [eku.value for eku in current_ext_keyusage] 1970 1971 except cryptography.x509.ExtensionNotFound: 1972 # This is only bad if the user specified a non-empty list 1973 if self.extended_key_usage: 1974 return NO_EXTENSION 1975 1976 def _validate_subject_alt_name(self): 1977 try: 1978 current_san = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value 1979 expected_san = [crypto_utils.cryptography_get_name(san) for san in self.subject_alt_name] 1980 if not compare_sets(expected_san, current_san, self.subject_alt_name_strict): 1981 return self.subject_alt_name, current_san 1982 except cryptography.x509.ExtensionNotFound: 1983 # This is only bad if the user specified a non-empty list 1984 if self.subject_alt_name: 1985 return NO_EXTENSION 1986 1987 def _validate_not_before(self): 1988 return self.cert.not_valid_before 1989 1990 def _validate_not_after(self): 1991 return self.cert.not_valid_after 1992 1993 def _validate_valid_at(self): 1994 rt = self.get_relative_time_option(self.valid_at, 'valid_at') 1995 return self.cert.not_valid_before, rt, self.cert.not_valid_after 1996 1997 def _validate_invalid_at(self): 1998 rt = self.get_relative_time_option(self.invalid_at, 'invalid_at') 1999 return self.cert.not_valid_before, rt, self.cert.not_valid_after 2000 2001 def _validate_valid_in(self): 2002 valid_in_date = self.get_relative_time_option(self.valid_in, "valid_in") 2003 return self.cert.not_valid_before, valid_in_date, self.cert.not_valid_after 2004 2005 2006class AssertOnlyCertificate(AssertOnlyCertificateBase): 2007 """validate the supplied certificate.""" 2008 2009 def __init__(self, module): 2010 super(AssertOnlyCertificate, self).__init__(module, 'pyopenssl') 2011 2012 # Ensure inputs are properly sanitized before comparison. 2013 for param in ['signature_algorithms', 'key_usage', 'extended_key_usage', 2014 'subject_alt_name', 'subject', 'issuer', 'not_before', 2015 'not_after', 'valid_at', 'invalid_at']: 2016 attr = getattr(self, param) 2017 if isinstance(attr, list) and attr: 2018 if isinstance(attr[0], str): 2019 setattr(self, param, [to_bytes(item) for item in attr]) 2020 elif isinstance(attr[0], tuple): 2021 setattr(self, param, [(to_bytes(item[0]), to_bytes(item[1])) for item in attr]) 2022 elif isinstance(attr, tuple): 2023 setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items())) 2024 elif isinstance(attr, dict): 2025 setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items())) 2026 elif isinstance(attr, str): 2027 setattr(self, param, to_bytes(attr)) 2028 2029 def _validate_privatekey(self): 2030 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) 2031 ctx.use_privatekey(self.privatekey) 2032 ctx.use_certificate(self.cert) 2033 try: 2034 ctx.check_privatekey() 2035 return True 2036 except OpenSSL.SSL.Error: 2037 return False 2038 2039 def _validate_csr_signature(self): 2040 try: 2041 self.csr.verify(self.cert.get_pubkey()) 2042 except OpenSSL.crypto.Error: 2043 return False 2044 2045 def _validate_csr_subject(self): 2046 if self.csr.get_subject() != self.cert.get_subject(): 2047 return False 2048 2049 def _validate_csr_extensions(self): 2050 csr_extensions = self.csr.get_extensions() 2051 cert_extension_count = self.cert.get_extension_count() 2052 if len(csr_extensions) != cert_extension_count: 2053 return False 2054 for extension_number in range(0, cert_extension_count): 2055 cert_extension = self.cert.get_extension(extension_number) 2056 csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions) 2057 if cert_extension.get_data() != list(csr_extension)[0].get_data(): 2058 return False 2059 return True 2060 2061 def _validate_signature_algorithms(self): 2062 if self.cert.get_signature_algorithm() not in self.signature_algorithms: 2063 return self.cert.get_signature_algorithm() 2064 2065 def _validate_subject(self): 2066 expected_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in self.subject] 2067 cert_subject = self.cert.get_subject().get_components() 2068 current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in cert_subject] 2069 if not compare_sets(expected_subject, current_subject, self.subject_strict): 2070 return expected_subject, current_subject 2071 2072 def _validate_issuer(self): 2073 expected_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in self.issuer] 2074 cert_issuer = self.cert.get_issuer().get_components() 2075 current_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in cert_issuer] 2076 if not compare_sets(expected_issuer, current_issuer, self.issuer_strict): 2077 return self.issuer, cert_issuer 2078 2079 def _validate_has_expired(self): 2080 # The following 3 lines are the same as the current PyOpenSSL code for cert.has_expired(). 2081 # Older version of PyOpenSSL have a buggy implementation, 2082 # to avoid issues with those we added the code from a more recent release here. 2083 2084 time_string = to_native(self.cert.get_notAfter()) 2085 not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") 2086 cert_expired = not_after < datetime.datetime.utcnow() 2087 return cert_expired 2088 2089 def _validate_version(self): 2090 # Version numbers in certs are off by one: 2091 # v1: 0, v2: 1, v3: 2 ... 2092 return self.cert.get_version() + 1 2093 2094 def _validate_key_usage(self): 2095 found = False 2096 for extension_idx in range(0, self.cert.get_extension_count()): 2097 extension = self.cert.get_extension(extension_idx) 2098 if extension.get_short_name() == b'keyUsage': 2099 found = True 2100 expected_extension = crypto.X509Extension(b"keyUsage", False, b', '.join(self.key_usage)) 2101 key_usage = [usage.strip() for usage in to_text(expected_extension, errors='surrogate_or_strict').split(',')] 2102 current_ku = [usage.strip() for usage in to_text(extension, errors='surrogate_or_strict').split(',')] 2103 if not compare_sets(key_usage, current_ku, self.key_usage_strict): 2104 return self.key_usage, str(extension).split(', ') 2105 if not found: 2106 # This is only bad if the user specified a non-empty list 2107 if self.key_usage: 2108 return NO_EXTENSION 2109 2110 def _validate_extended_key_usage(self): 2111 found = False 2112 for extension_idx in range(0, self.cert.get_extension_count()): 2113 extension = self.cert.get_extension(extension_idx) 2114 if extension.get_short_name() == b'extendedKeyUsage': 2115 found = True 2116 extKeyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.extended_key_usage] 2117 current_xku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in 2118 to_bytes(extension, errors='surrogate_or_strict').split(b',')] 2119 if not compare_sets(extKeyUsage, current_xku, self.extended_key_usage_strict): 2120 return self.extended_key_usage, str(extension).split(', ') 2121 if not found: 2122 # This is only bad if the user specified a non-empty list 2123 if self.extended_key_usage: 2124 return NO_EXTENSION 2125 2126 def _normalize_san(self, san): 2127 # Apparently OpenSSL returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string 2128 # although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004) 2129 if san.startswith('IP Address:'): 2130 san = 'IP:' + san[len('IP Address:'):] 2131 if san.startswith('IP:'): 2132 ip = compat_ipaddress.ip_address(san[3:]) 2133 san = 'IP:{0}'.format(ip.compressed) 2134 return san 2135 2136 def _validate_subject_alt_name(self): 2137 found = False 2138 for extension_idx in range(0, self.cert.get_extension_count()): 2139 extension = self.cert.get_extension(extension_idx) 2140 if extension.get_short_name() == b'subjectAltName': 2141 found = True 2142 l_altnames = [self._normalize_san(altname.strip()) for altname in 2143 to_text(extension, errors='surrogate_or_strict').split(', ')] 2144 sans = [self._normalize_san(to_text(san, errors='surrogate_or_strict')) for san in self.subject_alt_name] 2145 if not compare_sets(sans, l_altnames, self.subject_alt_name_strict): 2146 return self.subject_alt_name, l_altnames 2147 if not found: 2148 # This is only bad if the user specified a non-empty list 2149 if self.subject_alt_name: 2150 return NO_EXTENSION 2151 2152 def _validate_not_before(self): 2153 return self.cert.get_notBefore() 2154 2155 def _validate_not_after(self): 2156 return self.cert.get_notAfter() 2157 2158 def _validate_valid_at(self): 2159 rt = self.get_relative_time_option(self.valid_at, "valid_at") 2160 rt = to_bytes(rt, errors='surrogate_or_strict') 2161 return self.cert.get_notBefore(), rt, self.cert.get_notAfter() 2162 2163 def _validate_invalid_at(self): 2164 rt = self.get_relative_time_option(self.invalid_at, "invalid_at") 2165 rt = to_bytes(rt, errors='surrogate_or_strict') 2166 return self.cert.get_notBefore(), rt, self.cert.get_notAfter() 2167 2168 def _validate_valid_in(self): 2169 valid_in_asn1 = self.get_relative_time_option(self.valid_in, "valid_in") 2170 valid_in_date = to_bytes(valid_in_asn1, errors='surrogate_or_strict') 2171 return self.cert.get_notBefore(), valid_in_date, self.cert.get_notAfter() 2172 2173 2174class EntrustCertificate(Certificate): 2175 """Retrieve a certificate using Entrust (ECS).""" 2176 2177 def __init__(self, module, backend): 2178 super(EntrustCertificate, self).__init__(module, backend) 2179 self.trackingId = None 2180 self.notAfter = self.get_relative_time_option(module.params['entrust_not_after'], 'entrust_not_after') 2181 2182 if not os.path.exists(self.csr_path): 2183 raise CertificateError( 2184 'The certificate signing request file {0} does not exist'.format(self.csr_path) 2185 ) 2186 2187 self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) 2188 2189 # ECS API defaults to using the validated organization tied to the account. 2190 # We want to always force behavior of trying to use the organization provided in the CSR. 2191 # To that end we need to parse out the organization from the CSR. 2192 self.csr_org = None 2193 if self.backend == 'pyopenssl': 2194 csr_subject = self.csr.get_subject() 2195 csr_subject_components = csr_subject.get_components() 2196 for k, v in csr_subject_components: 2197 if k.upper() == 'O': 2198 # Entrust does not support multiple validated organizations in a single certificate 2199 if self.csr_org is not None: 2200 module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations found in " 2201 "Subject DN: '{0}'. ".format(csr_subject))) 2202 else: 2203 self.csr_org = v 2204 elif self.backend == 'cryptography': 2205 csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME) 2206 if len(csr_subject_orgs) == 1: 2207 self.csr_org = csr_subject_orgs[0].value 2208 elif len(csr_subject_orgs) > 1: 2209 module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations found in " 2210 "Subject DN: '{0}'. ".format(self.csr.subject))) 2211 # If no organization in the CSR, explicitly tell ECS that it should be blank in issued cert, not defaulted to 2212 # organization tied to the account. 2213 if self.csr_org is None: 2214 self.csr_org = '' 2215 2216 try: 2217 self.ecs_client = ECSClient( 2218 entrust_api_user=module.params.get('entrust_api_user'), 2219 entrust_api_key=module.params.get('entrust_api_key'), 2220 entrust_api_cert=module.params.get('entrust_api_client_cert_path'), 2221 entrust_api_cert_key=module.params.get('entrust_api_client_cert_key_path'), 2222 entrust_api_specification_path=module.params.get('entrust_api_specification_path') 2223 ) 2224 except SessionConfigurationException as e: 2225 module.fail_json(msg='Failed to initialize Entrust Provider: {0}'.format(to_native(e.message))) 2226 2227 def generate(self, module): 2228 2229 if not self.check(module, perms_required=False) or self.force: 2230 # Read the CSR that was generated for us 2231 body = {} 2232 with open(self.csr_path, 'r') as csr_file: 2233 body['csr'] = csr_file.read() 2234 2235 body['certType'] = module.params['entrust_cert_type'] 2236 2237 # Handle expiration (30 days if not specified) 2238 expiry = self.notAfter 2239 if not expiry: 2240 gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime())) 2241 expiry = gmt_now + datetime.timedelta(days=365) 2242 2243 expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") 2244 body['certExpiryDate'] = expiry_iso3339 2245 body['org'] = self.csr_org 2246 body['tracking'] = { 2247 'requesterName': module.params['entrust_requester_name'], 2248 'requesterEmail': module.params['entrust_requester_email'], 2249 'requesterPhone': module.params['entrust_requester_phone'], 2250 } 2251 2252 try: 2253 result = self.ecs_client.NewCertRequest(Body=body) 2254 self.trackingId = result.get('trackingId') 2255 except RestOperationException as e: 2256 module.fail_json(msg='Failed to request new certificate from Entrust Certificate Services (ECS): {0}'.format(to_native(e.message))) 2257 2258 if self.backup: 2259 self.backup_file = module.backup_local(self.path) 2260 crypto_utils.write_file(module, to_bytes(result.get('endEntityCert'))) 2261 self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) 2262 self.changed = True 2263 2264 def check(self, module, perms_required=True): 2265 """Ensure the resource is in its desired state.""" 2266 2267 parent_check = super(EntrustCertificate, self).check(module, perms_required) 2268 2269 try: 2270 cert_details = self._get_cert_details() 2271 except RestOperationException as e: 2272 module.fail_json(msg='Failed to get status of existing certificate from Entrust Certificate Services (ECS): {0}.'.format(to_native(e.message))) 2273 2274 # Always issue a new certificate if the certificate is expired, suspended or revoked 2275 status = cert_details.get('status', False) 2276 if status == 'EXPIRED' or status == 'SUSPENDED' or status == 'REVOKED': 2277 return False 2278 2279 # If the requested cert type was specified and it is for a different certificate type than the initial certificate, a new one is needed 2280 if module.params['entrust_cert_type'] and cert_details.get('certType') and module.params['entrust_cert_type'] != cert_details.get('certType'): 2281 return False 2282 2283 return parent_check 2284 2285 def _get_cert_details(self): 2286 cert_details = {} 2287 if self.cert: 2288 serial_number = None 2289 expiry = None 2290 if self.backend == 'pyopenssl': 2291 serial_number = "{0:X}".format(self.cert.get_serial_number()) 2292 time_string = to_native(self.cert.get_notAfter()) 2293 expiry = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ") 2294 elif self.backend == 'cryptography': 2295 serial_number = "{0:X}".format(self.cert.serial_number) 2296 expiry = self.cert.not_valid_after 2297 2298 # get some information about the expiry of this certificate 2299 expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z") 2300 cert_details['expiresAfter'] = expiry_iso3339 2301 2302 # If a trackingId is not already defined (from the result of a generate) 2303 # use the serial number to identify the tracking Id 2304 if self.trackingId is None and serial_number is not None: 2305 cert_results = self.ecs_client.GetCertificates(serialNumber=serial_number).get('certificates', {}) 2306 2307 # Finding 0 or more than 1 result is a very unlikely use case, it simply means we cannot perform additional checks 2308 # on the 'state' as returned by Entrust Certificate Services (ECS). The general certificate validity is 2309 # still checked as it is in the rest of the module. 2310 if len(cert_results) == 1: 2311 self.trackingId = cert_results[0].get('trackingId') 2312 2313 if self.trackingId is not None: 2314 cert_details.update(self.ecs_client.GetCertificate(trackingId=self.trackingId)) 2315 2316 return cert_details 2317 2318 def dump(self, check_mode=False): 2319 2320 result = { 2321 'changed': self.changed, 2322 'filename': self.path, 2323 'privatekey': self.privatekey_path, 2324 'csr': self.csr_path, 2325 } 2326 2327 if self.backup_file: 2328 result['backup_file'] = self.backup_file 2329 2330 result.update(self._get_cert_details()) 2331 2332 return result 2333 2334 2335class AcmeCertificate(Certificate): 2336 """Retrieve a certificate using the ACME protocol.""" 2337 2338 # Since there's no real use of the backend, 2339 # other than the 'self.check' function, we just pass the backend to the constructor 2340 2341 def __init__(self, module, backend): 2342 super(AcmeCertificate, self).__init__(module, backend) 2343 self.accountkey_path = module.params['acme_accountkey_path'] 2344 self.challenge_path = module.params['acme_challenge_path'] 2345 self.use_chain = module.params['acme_chain'] 2346 2347 def generate(self, module): 2348 2349 if not os.path.exists(self.privatekey_path): 2350 raise CertificateError( 2351 'The private key %s does not exist' % self.privatekey_path 2352 ) 2353 2354 if not os.path.exists(self.csr_path): 2355 raise CertificateError( 2356 'The certificate signing request file %s does not exist' % self.csr_path 2357 ) 2358 2359 if not os.path.exists(self.accountkey_path): 2360 raise CertificateError( 2361 'The account key %s does not exist' % self.accountkey_path 2362 ) 2363 2364 if not os.path.exists(self.challenge_path): 2365 raise CertificateError( 2366 'The challenge path %s does not exist' % self.challenge_path 2367 ) 2368 2369 if not self.check(module, perms_required=False) or self.force: 2370 acme_tiny_path = self.module.get_bin_path('acme-tiny', required=True) 2371 command = [acme_tiny_path] 2372 if self.use_chain: 2373 command.append('--chain') 2374 command.extend(['--account-key', self.accountkey_path]) 2375 command.extend(['--csr', self.csr_path]) 2376 command.extend(['--acme-dir', self.challenge_path]) 2377 2378 try: 2379 crt = module.run_command(command, check_rc=True)[1] 2380 if self.backup: 2381 self.backup_file = module.backup_local(self.path) 2382 crypto_utils.write_file(module, to_bytes(crt)) 2383 self.changed = True 2384 except OSError as exc: 2385 raise CertificateError(exc) 2386 2387 file_args = module.load_file_common_arguments(module.params) 2388 if module.set_fs_attributes_if_different(file_args, False): 2389 self.changed = True 2390 2391 def dump(self, check_mode=False): 2392 2393 result = { 2394 'changed': self.changed, 2395 'filename': self.path, 2396 'privatekey': self.privatekey_path, 2397 'accountkey': self.accountkey_path, 2398 'csr': self.csr_path, 2399 } 2400 if self.backup_file: 2401 result['backup_file'] = self.backup_file 2402 2403 return result 2404 2405 2406def main(): 2407 module = AnsibleModule( 2408 argument_spec=dict( 2409 state=dict(type='str', default='present', choices=['present', 'absent']), 2410 path=dict(type='path', required=True), 2411 provider=dict(type='str', choices=['acme', 'assertonly', 'entrust', 'ownca', 'selfsigned']), 2412 force=dict(type='bool', default=False,), 2413 csr_path=dict(type='path'), 2414 backup=dict(type='bool', default=False), 2415 select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), 2416 2417 # General properties of a certificate 2418 privatekey_path=dict(type='path'), 2419 privatekey_passphrase=dict(type='str', no_log=True), 2420 2421 # provider: assertonly 2422 signature_algorithms=dict(type='list', elements='str', removed_in_version='2.13'), 2423 subject=dict(type='dict', removed_in_version='2.13'), 2424 subject_strict=dict(type='bool', default=False, removed_in_version='2.13'), 2425 issuer=dict(type='dict', removed_in_version='2.13'), 2426 issuer_strict=dict(type='bool', default=False, removed_in_version='2.13'), 2427 has_expired=dict(type='bool', default=False, removed_in_version='2.13'), 2428 version=dict(type='int', removed_in_version='2.13'), 2429 key_usage=dict(type='list', elements='str', aliases=['keyUsage'], removed_in_version='2.13'), 2430 key_usage_strict=dict(type='bool', default=False, aliases=['keyUsage_strict'], removed_in_version='2.13'), 2431 extended_key_usage=dict(type='list', elements='str', aliases=['extendedKeyUsage'], removed_in_version='2.13'), 2432 extended_key_usage_strict=dict(type='bool', default=False, aliases=['extendedKeyUsage_strict'], removed_in_version='2.13'), 2433 subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName'], removed_in_version='2.13'), 2434 subject_alt_name_strict=dict(type='bool', default=False, aliases=['subjectAltName_strict'], removed_in_version='2.13'), 2435 not_before=dict(type='str', aliases=['notBefore'], removed_in_version='2.13'), 2436 not_after=dict(type='str', aliases=['notAfter'], removed_in_version='2.13'), 2437 valid_at=dict(type='str', removed_in_version='2.13'), 2438 invalid_at=dict(type='str', removed_in_version='2.13'), 2439 valid_in=dict(type='str', removed_in_version='2.13'), 2440 2441 # provider: selfsigned 2442 selfsigned_version=dict(type='int', default=3), 2443 selfsigned_digest=dict(type='str', default='sha256'), 2444 selfsigned_not_before=dict(type='str', default='+0s', aliases=['selfsigned_notBefore']), 2445 selfsigned_not_after=dict(type='str', default='+3650d', aliases=['selfsigned_notAfter']), 2446 selfsigned_create_subject_key_identifier=dict( 2447 type='str', 2448 default='create_if_not_provided', 2449 choices=['create_if_not_provided', 'always_create', 'never_create'] 2450 ), 2451 2452 # provider: ownca 2453 ownca_path=dict(type='path'), 2454 ownca_privatekey_path=dict(type='path'), 2455 ownca_privatekey_passphrase=dict(type='str', no_log=True), 2456 ownca_digest=dict(type='str', default='sha256'), 2457 ownca_version=dict(type='int', default=3), 2458 ownca_not_before=dict(type='str', default='+0s'), 2459 ownca_not_after=dict(type='str', default='+3650d'), 2460 ownca_create_subject_key_identifier=dict( 2461 type='str', 2462 default='create_if_not_provided', 2463 choices=['create_if_not_provided', 'always_create', 'never_create'] 2464 ), 2465 ownca_create_authority_key_identifier=dict(type='bool', default=True), 2466 2467 # provider: acme 2468 acme_accountkey_path=dict(type='path'), 2469 acme_challenge_path=dict(type='path'), 2470 acme_chain=dict(type='bool', default=False), 2471 2472 # provider: entrust 2473 entrust_cert_type=dict(type='str', default='STANDARD_SSL', 2474 choices=['STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 2475 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT']), 2476 entrust_requester_email=dict(type='str'), 2477 entrust_requester_name=dict(type='str'), 2478 entrust_requester_phone=dict(type='str'), 2479 entrust_api_user=dict(type='str'), 2480 entrust_api_key=dict(type='str', no_log=True), 2481 entrust_api_client_cert_path=dict(type='path'), 2482 entrust_api_client_cert_key_path=dict(type='path', no_log=True), 2483 entrust_api_specification_path=dict(type='path', default='https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml'), 2484 entrust_not_after=dict(type='str', default='+365d'), 2485 ), 2486 supports_check_mode=True, 2487 add_file_common_args=True, 2488 required_if=[ 2489 ['state', 'present', ['provider']], 2490 ['provider', 'entrust', ['entrust_requester_email', 'entrust_requester_name', 'entrust_requester_phone', 2491 'entrust_api_user', 'entrust_api_key', 'entrust_api_client_cert_path', 2492 'entrust_api_client_cert_key_path']], 2493 ] 2494 ) 2495 2496 try: 2497 if module.params['state'] == 'absent': 2498 certificate = CertificateAbsent(module) 2499 2500 else: 2501 if module.params['provider'] != 'assertonly' and module.params['csr_path'] is None: 2502 module.fail_json(msg='csr_path is required when provider is not assertonly') 2503 2504 base_dir = os.path.dirname(module.params['path']) or '.' 2505 if not os.path.isdir(base_dir): 2506 module.fail_json( 2507 name=base_dir, 2508 msg='The directory %s does not exist or the file is not a directory' % base_dir 2509 ) 2510 2511 provider = module.params['provider'] 2512 if provider == 'assertonly': 2513 module.deprecate("The 'assertonly' provider is deprecated; please see the examples of " 2514 "the 'openssl_certificate' module on how to replace it with other modules", 2515 version='2.13') 2516 2517 backend = module.params['select_crypto_backend'] 2518 if backend == 'auto': 2519 # Detect what backend we can use 2520 can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) 2521 can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) 2522 2523 # If cryptography is available we'll use it 2524 if can_use_cryptography: 2525 backend = 'cryptography' 2526 elif can_use_pyopenssl: 2527 backend = 'pyopenssl' 2528 2529 if module.params['selfsigned_version'] == 2 or module.params['ownca_version'] == 2: 2530 module.warn('crypto backend forced to pyopenssl. The cryptography library does not support v2 certificates') 2531 backend = 'pyopenssl' 2532 2533 # Fail if no backend has been found 2534 if backend == 'auto': 2535 module.fail_json(msg=("Can't detect any of the required Python libraries " 2536 "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( 2537 MINIMAL_CRYPTOGRAPHY_VERSION, 2538 MINIMAL_PYOPENSSL_VERSION)) 2539 2540 if backend == 'pyopenssl': 2541 if not PYOPENSSL_FOUND: 2542 module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)), 2543 exception=PYOPENSSL_IMP_ERR) 2544 if module.params['provider'] in ['selfsigned', 'ownca', 'assertonly']: 2545 try: 2546 getattr(crypto.X509Req, 'get_extensions') 2547 except AttributeError: 2548 module.fail_json(msg='You need to have PyOpenSSL>=0.15') 2549 2550 module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13') 2551 if provider == 'selfsigned': 2552 certificate = SelfSignedCertificate(module) 2553 elif provider == 'acme': 2554 certificate = AcmeCertificate(module, 'pyopenssl') 2555 elif provider == 'ownca': 2556 certificate = OwnCACertificate(module) 2557 elif provider == 'entrust': 2558 certificate = EntrustCertificate(module, 'pyopenssl') 2559 else: 2560 certificate = AssertOnlyCertificate(module) 2561 elif backend == 'cryptography': 2562 if not CRYPTOGRAPHY_FOUND: 2563 module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)), 2564 exception=CRYPTOGRAPHY_IMP_ERR) 2565 if module.params['selfsigned_version'] == 2 or module.params['ownca_version'] == 2: 2566 module.fail_json(msg='The cryptography backend does not support v2 certificates, ' 2567 'use select_crypto_backend=pyopenssl for v2 certificates') 2568 if provider == 'selfsigned': 2569 certificate = SelfSignedCertificateCryptography(module) 2570 elif provider == 'acme': 2571 certificate = AcmeCertificate(module, 'cryptography') 2572 elif provider == 'ownca': 2573 certificate = OwnCACertificateCryptography(module) 2574 elif provider == 'entrust': 2575 certificate = EntrustCertificate(module, 'cryptography') 2576 else: 2577 certificate = AssertOnlyCertificateCryptography(module) 2578 2579 if module.params['state'] == 'present': 2580 if module.check_mode: 2581 result = certificate.dump(check_mode=True) 2582 result['changed'] = module.params['force'] or not certificate.check(module) 2583 module.exit_json(**result) 2584 2585 certificate.generate(module) 2586 else: 2587 if module.check_mode: 2588 result = certificate.dump(check_mode=True) 2589 result['changed'] = os.path.exists(module.params['path']) 2590 module.exit_json(**result) 2591 2592 certificate.remove(module) 2593 2594 result = certificate.dump() 2595 module.exit_json(**result) 2596 except crypto_utils.OpenSSLObjectError as exc: 2597 module.fail_json(msg=to_native(exc)) 2598 2599 2600if __name__ == "__main__": 2601 main() 2602