mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e4728e206 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,6 +1,3 @@
|
||||
# Community.crypt specific things
|
||||
/changelogs/.plugin-cache.yaml
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||
# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
|
||||
|
||||
119
CHANGELOG.rst
119
CHANGELOG.rst
@@ -1,119 +0,0 @@
|
||||
==============================
|
||||
Community Crypto Release Notes
|
||||
==============================
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
|
||||
v1.1.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
Release for Ansible 2.10.0.
|
||||
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- acme_account - add ``external_account_binding`` option to allow creation of ACME accounts with External Account Binding (https://github.com/ansible-collections/community.crypto/issues/89).
|
||||
- acme_certificate - allow new selector ``test_certificates: first`` for ``select_chain`` parameter (https://github.com/ansible-collections/community.crypto/pull/102).
|
||||
- cryptography backends - support arbitrary dotted OIDs (https://github.com/ansible-collections/community.crypto/issues/39).
|
||||
- get_certificate - add support for SNI (https://github.com/ansible-collections/community.crypto/issues/69).
|
||||
- luks_device - add support for encryption options on container creation (https://github.com/ansible-collections/community.crypto/pull/97).
|
||||
- openssh_cert - add support for PKCS#11 tokens (https://github.com/ansible-collections/community.crypto/pull/95).
|
||||
- openssl_certificate - the PyOpenSSL backend now uses 160 bits of randomness for serial numbers, instead of a random number between 1000 and 99999. Please note that this is not a high quality random number (https://github.com/ansible-collections/community.crypto/issues/76).
|
||||
- openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
|
||||
- openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- acme_inspect - fix problem with Python 3.5 that JSON was not decoded (https://github.com/ansible-collections/community.crypto/issues/86).
|
||||
- get_certificate - fix ``ca_cert`` option handling when ``proxy_host`` is used (https://github.com/ansible-collections/community.crypto/pull/84).
|
||||
- openssl_*, x509_* modules - fix handling of general names which refer to IP networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- openssl_signature - Sign data with openssl
|
||||
- openssl_signature_info - Verify signatures with openssl
|
||||
|
||||
v1.0.0
|
||||
======
|
||||
|
||||
Release Summary
|
||||
---------------
|
||||
|
||||
This is the first proper release of the ``community.crypto`` collection. This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2.9.0.
|
||||
|
||||
|
||||
Minor Changes
|
||||
-------------
|
||||
|
||||
- luks_device - accept ``passphrase``, ``new_passphrase`` and ``remove_passphrase``.
|
||||
- luks_device - add ``keysize`` parameter to set key size at LUKS container creation
|
||||
- luks_device - added support to use UUIDs, and labels with LUKS2 containers
|
||||
- luks_device - added the ``type`` option that allows user explicit define the LUKS container format version
|
||||
- openssh_keypair - instead of regenerating some broken or password protected keys, fail the module. Keys can still be regenerated by calling the module with ``force=yes``.
|
||||
- openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
|
||||
- openssl_* modules - the cryptography backend now properly supports ``dirName``, ``otherName`` and ``RID`` (Registered ID) names.
|
||||
- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be neccessary.)
|
||||
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
|
||||
- openssl_certificate - allow to provide content of some input files via the ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and ``ownca_content`` options.
|
||||
- openssl_certificate - allow to return the existing/generated certificate directly as ``certificate`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_certificate_info - allow to provide certificate content via ``content`` option (https://github.com/ansible/ansible/issues/64776).
|
||||
- openssl_csr - Add support for specifying the SAN ``otherName`` value in the OpenSSL ASN.1 UTF8 string format, ``otherName:<OID>;UTF8:string value``.
|
||||
- openssl_csr - allow to provide private key content via ``private_key_content`` option.
|
||||
- openssl_csr - allow to return the existing/generated CSR directly as ``csr`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_csr_info - allow to provide CSR content via ``content`` option.
|
||||
- openssl_dhparam - allow to return the existing/generated DH params directly as ``dhparams`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_dhparam - now supports a ``cryptography``-based backend. Auto-detection can be overwritten with the ``select_crypto_backend`` option.
|
||||
- openssl_pkcs12 - allow to return the existing/generated PKCS#12 directly as ``pkcs12`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_privatekey - add ``format`` and ``format_mismatch`` options.
|
||||
- openssl_privatekey - allow to return the existing/generated private key directly as ``privatekey`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_privatekey - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
|
||||
- openssl_privatekey_info - allow to provide private key content via ``content`` option.
|
||||
- openssl_publickey - allow to provide private key content via ``private_key_content`` option.
|
||||
- openssl_publickey - allow to return the existing/generated public key directly as ``publickey`` by setting ``return_content`` to ``yes``.
|
||||
|
||||
Deprecated Features
|
||||
-------------------
|
||||
|
||||
- openssl_csr - all values for the ``version`` option except ``1`` are deprecated. The value 1 denotes the current only standardized CSR version.
|
||||
|
||||
Removed Features (previously deprecated)
|
||||
----------------------------------------
|
||||
|
||||
- The ``letsencrypt`` module has been removed. Use ``acme_certificate`` instead.
|
||||
|
||||
Bugfixes
|
||||
--------
|
||||
|
||||
- ACME modules: fix bug in ACME v1 account update code
|
||||
- ACME modules: make sure some connection errors are handled properly
|
||||
- ACME modules: support Buypass' ACME v1 endpoint
|
||||
- acme_certificate - fix crash when module is used with Python 2.x.
|
||||
- acme_certificate - fix misbehavior when ACME v1 is used with ``modify_account`` set to ``false``.
|
||||
- ecs_certificate - Always specify header ``connection: keep-alive`` for ECS API connections.
|
||||
- ecs_certificate - Fix formatting of contents of ``full_chain_path``.
|
||||
- get_certificate - Fix cryptography backend when pyopenssl is unavailable (https://github.com/ansible/ansible/issues/67900)
|
||||
- openssh_keypair - add logic to avoid breaking password protected keys.
|
||||
- openssh_keypair - fixes idempotence issue with public key (https://github.com/ansible/ansible/issues/64969).
|
||||
- openssh_keypair - public key's file attributes (permissions, owner, group, etc.) are now set to the same values as the private key.
|
||||
- openssl_* modules - prevent crash on fingerprint determination in FIPS mode (https://github.com/ansible/ansible/issues/67213).
|
||||
- openssl_certificate - When provider is ``entrust``, use a ``connection: keep-alive`` header for ECS API connections.
|
||||
- openssl_certificate - ``provider`` option was documented as required, but it was not checked whether it was provided. It is now only required when ``state`` is ``present``.
|
||||
- openssl_certificate - fix ``assertonly`` provider certificate verification, causing 'private key mismatch' and 'subject mismatch' errors.
|
||||
- openssl_certificate and openssl_csr - fix Ed25519 and Ed448 private key support for ``cryptography`` backend. This probably needs at least cryptography 2.8, since older versions have problems with signing certificates or CSRs with such keys. (https://github.com/ansible/ansible/issues/59039, PR https://github.com/ansible/ansible/pull/63984)
|
||||
- openssl_csr - a warning is issued if an unsupported value for ``version`` is used for the ``cryptography`` backend.
|
||||
- openssl_csr - the module will now enforce that ``privatekey_path`` is specified when ``state=present``.
|
||||
- openssl_publickey - fix a module crash caused when pyOpenSSL is not installed (https://github.com/ansible/ansible/issues/67035).
|
||||
|
||||
New Modules
|
||||
-----------
|
||||
|
||||
- ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API
|
||||
- x509_crl - Generate Certificate Revocation Lists (CRLs)
|
||||
- x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs)
|
||||
9
COPYING
9
COPYING
@@ -1,7 +1,7 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@@ -664,11 +664,12 @@ might be different; for a GUI interface, you would use an "about box".
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
||||
|
||||
108
README.md
108
README.md
@@ -1,106 +1,4 @@
|
||||
# Ansible Community Crypto Collection
|
||||
[](https://app.shippable.com/projects/5e66776ca27f990007073a42)
|
||||
[](https://codecov.io/gh/ansible-collections/community.crypto)
|
||||
[](https://github.com/ansible-collection-migration/community.crypto/actions?query=workflow%3A%22Collection%20test%20suite%22)
|
||||
|
||||
Provides modules for [Ansible](https://www.ansible.com/community) for various cryptographic operations.
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with both the current Ansible 2.9 and 2.10 releases and the current development version of Ansible. Ansible versions before 2.9.10 are not supported.
|
||||
|
||||
## External requirements
|
||||
|
||||
The exact requirements for every module are listed in the module documentation.
|
||||
|
||||
Most modules require a recent enough version of [the Python cryptography library](https://pypi.org/project/cryptography/). See the module documentations for the minimal version supported for each module.
|
||||
|
||||
## Included content
|
||||
|
||||
- OpenSSL / PKI modules:
|
||||
- openssl_csr_info
|
||||
- openssl_csr
|
||||
- openssl_dhparam
|
||||
- openssl_pkcs12
|
||||
- openssl_privatekey_info
|
||||
- openssl_privatekey
|
||||
- openssl_publickey
|
||||
- openssl_signature_info
|
||||
- openssl_signature
|
||||
- x509_certificate_info
|
||||
- x509_certificate
|
||||
- x509_crl_info
|
||||
- x509_crl
|
||||
- certificate_complete_chain
|
||||
- OpenSSH modules:
|
||||
- openssh_cert
|
||||
- openssh_keypair
|
||||
- ACME modules:
|
||||
- acme_account_info
|
||||
- acme_account
|
||||
- acme_certificate
|
||||
- acme_certificate_revoke
|
||||
- acme_challenge_cert_helper
|
||||
- acme_inspect
|
||||
- ECS modules:
|
||||
- ecs_certificate
|
||||
- ecs_domain
|
||||
- Miscellaneous modules:
|
||||
- get_certificate
|
||||
- luks_device
|
||||
|
||||
## Using this collection
|
||||
|
||||
Before using the crypto community collection, you need to install the collection with the `ansible-galaxy` CLI:
|
||||
|
||||
ansible-galaxy collection install community.crypto
|
||||
|
||||
You can also include it in a `requirements.yml` file and install it via `ansible-galaxy collection install -r requirements.yml` using the format:
|
||||
|
||||
```yaml
|
||||
collections:
|
||||
- name: community.crypto
|
||||
```
|
||||
|
||||
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
|
||||
|
||||
## Contributing to this collection
|
||||
|
||||
<!--Describe how the community can contribute to your collection. At a minimum, include how and where users can create issues to report problems or request features for this collection. List contribution requirements, including preferred workflows and necessary testing, so you can benefit from community PRs. If you are following general Ansible contributor guidelines, you can link to - [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html). -->
|
||||
|
||||
We're following the general Ansible contributor guidelines; see [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html).
|
||||
|
||||
If you want to clone this repositority (or a fork of it) to improve it, you can proceed as follows:
|
||||
1. Create a directory `ansible_collections/community`;
|
||||
2. In there, checkout this repository (or a fork) as `crypto`;
|
||||
3. Add the directory containing `ansible_collections` to your [ANSIBLE_COLLECTIONS_PATH](https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths).
|
||||
|
||||
See [Ansible's dev guide](https://docs.ansible.com/ansible/devel/dev_guide/developing_collections.html#contributing-to-collections) for more information.
|
||||
|
||||
## Release notes
|
||||
|
||||
See the [changelog](https://github.com/ansible-collections/community.crypto/blob/main/CHANGELOG.rst).
|
||||
|
||||
## Roadmap
|
||||
|
||||
We plan to regularly release minor and patch versions, whenever new features are added or bugs fixed. Our collection follows [semantic versioning](https://semver.org/), so breaking changes will only happen in major releases.
|
||||
|
||||
Most modules will drop PyOpenSSL support in version 2.0.0 of the collection, i.e. in the next major version. We currently plan to release 2.0.0 somewhen during 2021. Around then, the supported versions of the most common distributions will contain a new enough version of ``cryptography``.
|
||||
|
||||
Once 2.0.0 has been released, bugfixes will still be backported to 1.0.0 for some time, and some features might also be backported. If we do not want to backport something ourselves because we think it is not worth the effort, backport PRs by non-maintainers are usually accepted.
|
||||
|
||||
In 2.0.0, the following notable features will be removed:
|
||||
* PyOpenSSL backends of all modules, except ``openssl_pkcs12`` which does not have a ``cryptography`` backend due to lack of support of PKCS#12 functionality in ``cryptography``.
|
||||
* The ``assertonly`` provider of ``x509_certificate`` will be removed.
|
||||
|
||||
## More information
|
||||
|
||||
- [Ansible Collection overview](https://github.com/ansible-collections/overview)
|
||||
- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html)
|
||||
- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html)
|
||||
- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
|
||||
|
||||
## Licensing
|
||||
|
||||
GNU General Public License v3.0 or later.
|
||||
|
||||
See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.
|
||||
Ansible Collection: community.crypto
|
||||
=================================================
|
||||
@@ -1,187 +0,0 @@
|
||||
ancestor: null
|
||||
releases:
|
||||
1.0.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- 'ACME modules: fix bug in ACME v1 account update code'
|
||||
- 'ACME modules: make sure some connection errors are handled properly'
|
||||
- 'ACME modules: support Buypass'' ACME v1 endpoint'
|
||||
- acme_certificate - fix crash when module is used with Python 2.x.
|
||||
- acme_certificate - fix misbehavior when ACME v1 is used with ``modify_account``
|
||||
set to ``false``.
|
||||
- 'ecs_certificate - Always specify header ``connection: keep-alive`` for ECS
|
||||
API connections.'
|
||||
- ecs_certificate - Fix formatting of contents of ``full_chain_path``.
|
||||
- get_certificate - Fix cryptography backend when pyopenssl is unavailable (https://github.com/ansible/ansible/issues/67900)
|
||||
- openssh_keypair - add logic to avoid breaking password protected keys.
|
||||
- openssh_keypair - fixes idempotence issue with public key (https://github.com/ansible/ansible/issues/64969).
|
||||
- openssh_keypair - public key's file attributes (permissions, owner, group,
|
||||
etc.) are now set to the same values as the private key.
|
||||
- openssl_* modules - prevent crash on fingerprint determination in FIPS mode
|
||||
(https://github.com/ansible/ansible/issues/67213).
|
||||
- 'openssl_certificate - When provider is ``entrust``, use a ``connection: keep-alive``
|
||||
header for ECS API connections.'
|
||||
- openssl_certificate - ``provider`` option was documented as required, but
|
||||
it was not checked whether it was provided. It is now only required when ``state``
|
||||
is ``present``.
|
||||
- openssl_certificate - fix ``assertonly`` provider certificate verification,
|
||||
causing 'private key mismatch' and 'subject mismatch' errors.
|
||||
- openssl_certificate and openssl_csr - fix Ed25519 and Ed448 private key support
|
||||
for ``cryptography`` backend. This probably needs at least cryptography 2.8,
|
||||
since older versions have problems with signing certificates or CSRs with
|
||||
such keys. (https://github.com/ansible/ansible/issues/59039, PR https://github.com/ansible/ansible/pull/63984)
|
||||
- openssl_csr - a warning is issued if an unsupported value for ``version``
|
||||
is used for the ``cryptography`` backend.
|
||||
- openssl_csr - the module will now enforce that ``privatekey_path`` is specified
|
||||
when ``state=present``.
|
||||
- openssl_publickey - fix a module crash caused when pyOpenSSL is not installed
|
||||
(https://github.com/ansible/ansible/issues/67035).
|
||||
deprecated_features:
|
||||
- openssl_csr - all values for the ``version`` option except ``1`` are deprecated.
|
||||
The value 1 denotes the current only standardized CSR version.
|
||||
minor_changes:
|
||||
- luks_device - accept ``passphrase``, ``new_passphrase`` and ``remove_passphrase``.
|
||||
- luks_device - add ``keysize`` parameter to set key size at LUKS container
|
||||
creation
|
||||
- luks_device - added support to use UUIDs, and labels with LUKS2 containers
|
||||
- luks_device - added the ``type`` option that allows user explicit define the
|
||||
LUKS container format version
|
||||
- openssh_keypair - instead of regenerating some broken or password protected
|
||||
keys, fail the module. Keys can still be regenerated by calling the module
|
||||
with ``force=yes``.
|
||||
- openssh_keypair - the ``regenerate`` option allows to configure the module's
|
||||
behavior when it should or needs to regenerate private keys.
|
||||
- openssl_* modules - the cryptography backend now properly supports ``dirName``,
|
||||
``otherName`` and ``RID`` (Registered ID) names.
|
||||
- openssl_certificate - Add option for changing which ACME directory to use
|
||||
with acme-tiny. Set the default ACME directory to Let's Encrypt instead of
|
||||
using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time
|
||||
being, so no action should be neccessary.)
|
||||
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
|
||||
- openssl_certificate - allow to provide content of some input files via the
|
||||
``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and
|
||||
``ownca_content`` options.
|
||||
- openssl_certificate - allow to return the existing/generated certificate directly
|
||||
as ``certificate`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_certificate_info - allow to provide certificate content via ``content``
|
||||
option (https://github.com/ansible/ansible/issues/64776).
|
||||
- openssl_csr - Add support for specifying the SAN ``otherName`` value in the
|
||||
OpenSSL ASN.1 UTF8 string format, ``otherName:<OID>;UTF8:string value``.
|
||||
- openssl_csr - allow to provide private key content via ``private_key_content``
|
||||
option.
|
||||
- openssl_csr - allow to return the existing/generated CSR directly as ``csr``
|
||||
by setting ``return_content`` to ``yes``.
|
||||
- openssl_csr_info - allow to provide CSR content via ``content`` option.
|
||||
- openssl_dhparam - allow to return the existing/generated DH params directly
|
||||
as ``dhparams`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_dhparam - now supports a ``cryptography``-based backend. Auto-detection
|
||||
can be overwritten with the ``select_crypto_backend`` option.
|
||||
- openssl_pkcs12 - allow to return the existing/generated PKCS#12 directly as
|
||||
``pkcs12`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_privatekey - add ``format`` and ``format_mismatch`` options.
|
||||
- openssl_privatekey - allow to return the existing/generated private key directly
|
||||
as ``privatekey`` by setting ``return_content`` to ``yes``.
|
||||
- openssl_privatekey - the ``regenerate`` option allows to configure the module's
|
||||
behavior when it should or needs to regenerate private keys.
|
||||
- openssl_privatekey_info - allow to provide private key content via ``content``
|
||||
option.
|
||||
- openssl_publickey - allow to provide private key content via ``private_key_content``
|
||||
option.
|
||||
- openssl_publickey - allow to return the existing/generated public key directly
|
||||
as ``publickey`` by setting ``return_content`` to ``yes``.
|
||||
release_summary: 'This is the first proper release of the ``community.crypto``
|
||||
collection. This changelog contains all changes to the modules in this collection
|
||||
that were added after the release of Ansible 2.9.0.
|
||||
|
||||
'
|
||||
removed_features:
|
||||
- The ``letsencrypt`` module has been removed. Use ``acme_certificate`` instead.
|
||||
fragments:
|
||||
- 1.0.0.yml
|
||||
- 52408-luks-device.yaml
|
||||
- 58973-luks_device_add-type-option.yml
|
||||
- 58973_luks_device-add-label-and-uuid-support.yml
|
||||
- 60388-openssl_privatekey-format.yml
|
||||
- 61522-luks-device-add-option-to-define-keysize.yml
|
||||
- 61658-openssh_keypair-public-key-permissions.yml
|
||||
- 61693-acme-buypass-acme-v1.yml
|
||||
- 61738-ecs-certificate-invalid-chain.yaml
|
||||
- 62218-fix-to-entrust-api.yml
|
||||
- 62790-openssl_certificate_fix_assert.yml
|
||||
- 62991-openssl_dhparam-cryptography-backend.yml
|
||||
- 63140-acme-fix-fetch-url-status-codes.yaml
|
||||
- 63432-openssl_csr-version.yml
|
||||
- 63984-openssl-ed25519-ed448.yml
|
||||
- 64436-openssh_keypair-add-password-protected-key-check.yml
|
||||
- 64501-fix-python2.x-backward-compatibility.yaml
|
||||
- 64648-acme_certificate-acmev1.yml
|
||||
- 65017-openssh_keypair-idempotence.yml
|
||||
- 65400-openssl-output.yml
|
||||
- 65435-openssl_csr-privatekey_path-required.yml
|
||||
- 65633-crypto-argspec-fixup.yml
|
||||
- 66384-openssl-content.yml
|
||||
- 67036-openssl_publickey-backend.yml
|
||||
- 67038-openssl-openssh-key-regenerate.yml
|
||||
- 67109-openssl_certificate-acme-directory.yaml
|
||||
- 67515-openssl-fingerprint-fips.yml
|
||||
- 67669-cryptography-names.yml
|
||||
- 67901-get_certificate-fix-cryptography.yml
|
||||
- letsencrypt.yml
|
||||
- openssl_csr-otherName.yml
|
||||
modules:
|
||||
- description: Request validation of a domain with the Entrust Certificate Services
|
||||
(ECS) API
|
||||
name: ecs_domain
|
||||
namespace: ''
|
||||
- description: Generate Certificate Revocation Lists (CRLs)
|
||||
name: x509_crl
|
||||
namespace: ''
|
||||
- description: Retrieve information on Certificate Revocation Lists (CRLs)
|
||||
name: x509_crl_info
|
||||
namespace: ''
|
||||
release_date: '2020-07-03'
|
||||
1.1.0:
|
||||
changes:
|
||||
bugfixes:
|
||||
- acme_inspect - fix problem with Python 3.5 that JSON was not decoded (https://github.com/ansible-collections/community.crypto/issues/86).
|
||||
- get_certificate - fix ``ca_cert`` option handling when ``proxy_host`` is used
|
||||
(https://github.com/ansible-collections/community.crypto/pull/84).
|
||||
- openssl_*, x509_* modules - fix handling of general names which refer to IP
|
||||
networks and not IP addresses (https://github.com/ansible-collections/community.crypto/pull/92).
|
||||
minor_changes:
|
||||
- acme_account - add ``external_account_binding`` option to allow creation of
|
||||
ACME accounts with External Account Binding (https://github.com/ansible-collections/community.crypto/issues/89).
|
||||
- 'acme_certificate - allow new selector ``test_certificates: first`` for ``select_chain``
|
||||
parameter (https://github.com/ansible-collections/community.crypto/pull/102).'
|
||||
- cryptography backends - support arbitrary dotted OIDs (https://github.com/ansible-collections/community.crypto/issues/39).
|
||||
- get_certificate - add support for SNI (https://github.com/ansible-collections/community.crypto/issues/69).
|
||||
- luks_device - add support for encryption options on container creation (https://github.com/ansible-collections/community.crypto/pull/97).
|
||||
- openssh_cert - add support for PKCS#11 tokens (https://github.com/ansible-collections/community.crypto/pull/95).
|
||||
- openssl_certificate - the PyOpenSSL backend now uses 160 bits of randomness
|
||||
for serial numbers, instead of a random number between 1000 and 99999. Please
|
||||
note that this is not a high quality random number (https://github.com/ansible-collections/community.crypto/issues/76).
|
||||
- openssl_csr - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
|
||||
- openssl_csr_info - add support for name constraints extension (https://github.com/ansible-collections/community.crypto/issues/46).
|
||||
release_summary: 'Release for Ansible 2.10.0.
|
||||
|
||||
'
|
||||
fragments:
|
||||
- 1.1.0.yml
|
||||
- 100-acme-account-external-account-binding.yml
|
||||
- 102-acme-certificate-select-chain-first.yml
|
||||
- 87-acme_inspect-python-3.5.yml
|
||||
- 90-cryptography-oids.yml
|
||||
- 90-openssl_certificate-pyopenssl-serial.yml
|
||||
- 92-ip-networks.yml
|
||||
- 92-openssl_csr-name-constraints.yml
|
||||
- get_certificate-add_support_for_SNI.yml
|
||||
- luks_device-add_encryption_option_on_create.yml
|
||||
- openssh_cert-pkcs11.yml
|
||||
modules:
|
||||
- description: Sign data with openssl
|
||||
name: openssl_signature
|
||||
namespace: ''
|
||||
- description: Verify signatures with openssl
|
||||
name: openssl_signature_info
|
||||
namespace: ''
|
||||
release_date: '2020-08-18'
|
||||
@@ -1,28 +0,0 @@
|
||||
changelog_filename_template: ../CHANGELOG.rst
|
||||
changelog_filename_version_depth: 0
|
||||
changes_file: changelog.yaml
|
||||
changes_format: combined
|
||||
keep_fragments: false
|
||||
mention_ancestor: true
|
||||
new_plugins_after_name: removed_features
|
||||
notesdir: fragments
|
||||
prelude_section_name: release_summary
|
||||
prelude_section_title: Release Summary
|
||||
sections:
|
||||
- - major_changes
|
||||
- Major Changes
|
||||
- - minor_changes
|
||||
- Minor Changes
|
||||
- - breaking_changes
|
||||
- Breaking Changes / Porting Guide
|
||||
- - deprecated_features
|
||||
- Deprecated Features
|
||||
- - removed_features
|
||||
- Removed Features (previously deprecated)
|
||||
- - security_fixes
|
||||
- Security Fixes
|
||||
- - bugfixes
|
||||
- Bugfixes
|
||||
- - known_issues
|
||||
- Known Issues
|
||||
title: Community Crypto
|
||||
35
galaxy.yml
35
galaxy.yml
@@ -1,30 +1,15 @@
|
||||
namespace: community
|
||||
name: crypto
|
||||
version: 1.1.0
|
||||
version: 0.1.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (github.com/ansible)
|
||||
authors: null
|
||||
description: null
|
||||
license: GPL-3.0-or-later
|
||||
license_file: COPYING
|
||||
tags:
|
||||
- acme
|
||||
- certificate
|
||||
- community
|
||||
- crl
|
||||
- cryptography
|
||||
- csr
|
||||
- dhparam
|
||||
- entrust
|
||||
- letsencrypt
|
||||
- luks
|
||||
- openssl
|
||||
- openssh
|
||||
- pkcs12
|
||||
repository: https://github.com/ansible-collections/community.crypto
|
||||
#documentation: https://github.com/ansible-collection-migration/community.crypto/tree/main/docs
|
||||
homepage: https://github.com/ansible-collections/community.crypto
|
||||
issues: https://github.com/ansible-collections/community.crypto/issues
|
||||
build_ignore:
|
||||
- 'community-crypto-*.tar.gz'
|
||||
- .gitignore
|
||||
- changelogs/.plugin-cache.yaml
|
||||
tags: null
|
||||
dependencies:
|
||||
ansible.netcommon: '>=0.1.0'
|
||||
repository: https://github.com:ansible-collections/community.crypto
|
||||
documentation: https://github.com/ansible-collection-migration/community.crypto/tree/master/docs
|
||||
homepage: https://github.com:ansible-collections/community.crypto
|
||||
issues: https://github.com:ansible-collections/community.crypto/issues
|
||||
|
||||
7
meta/action_groups.yml
Normal file
7
meta/action_groups.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
acme:
|
||||
- acme_inspect
|
||||
- acme_certificate_revoke
|
||||
- acme_certificate
|
||||
- acme_account
|
||||
- acme_account_facts
|
||||
- acme_account_info
|
||||
6
meta/routing.yml
Normal file
6
meta/routing.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
plugin_routing:
|
||||
modules:
|
||||
acme_account_facts:
|
||||
deprecation:
|
||||
removal_date: TBD
|
||||
warning_text: see plugin documentation for details
|
||||
@@ -1,25 +0,0 @@
|
||||
requires_ansible: '>=2.9.10'
|
||||
|
||||
action_groups:
|
||||
acme:
|
||||
- acme_inspect
|
||||
- acme_certificate_revoke
|
||||
- acme_certificate
|
||||
- acme_account
|
||||
- acme_account_facts
|
||||
- acme_account_info
|
||||
|
||||
plugin_routing:
|
||||
modules:
|
||||
acme_account_facts:
|
||||
deprecation:
|
||||
removal_version: '2.12'
|
||||
warning_text: The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'.
|
||||
openssl_certificate:
|
||||
deprecation:
|
||||
removal_version: '2.14'
|
||||
warning_text: The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'
|
||||
openssl_certificate_info:
|
||||
deprecation:
|
||||
removal_version: '2.14'
|
||||
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'
|
||||
@@ -31,12 +31,8 @@ options:
|
||||
description:
|
||||
- "Path to a file containing the ACME account RSA or Elliptic Curve
|
||||
key."
|
||||
- "Private keys can be created with the
|
||||
M(community.crypto.openssl_privatekey) module. If the requisites
|
||||
(pyOpenSSL or cryptography) are not available, keys can also be
|
||||
created directly with the C(openssl) command line tool: RSA keys
|
||||
can be created with C(openssl genrsa ...). Elliptic curve keys can be
|
||||
created with C(openssl ecparam -genkey ...). Any other tool creating
|
||||
- "RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys can
|
||||
be created with C(openssl ecparam -genkey ...). Any other tool creating
|
||||
private keys in PEM format can be used as well."
|
||||
- "Mutually exclusive with C(account_key_content)."
|
||||
- "Required if C(account_key_content) is not used."
|
||||
|
||||
@@ -29,22 +29,20 @@ import tempfile
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible.module_utils.six.moves.urllib.parse import unquote
|
||||
from ansible.module_utils._text import to_native, to_text, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
from ansible.module_utils.six.moves.urllib.parse import unquote
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.hazmat.backends
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
import cryptography.hazmat.primitives.hmac
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||
import cryptography.hazmat.primitives.asymmetric.ec
|
||||
import cryptography.hazmat.primitives.asymmetric.padding
|
||||
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
import cryptography.hazmat.primitives.asymmetric.utils
|
||||
import cryptography.hazmat.primitives.serialization
|
||||
import cryptography.x509
|
||||
import cryptography.x509.oid
|
||||
from distutils.version import LooseVersion
|
||||
@@ -272,42 +270,9 @@ def _parse_key_openssl(openssl_binary, module, key_file=None, key_content=None):
|
||||
}
|
||||
|
||||
|
||||
def _create_mac_key_openssl(openssl_bin, module, alg, key):
|
||||
if alg == 'HS256':
|
||||
hashalg = 'sha256'
|
||||
hashbytes = 32
|
||||
elif alg == 'HS384':
|
||||
hashalg = 'sha384'
|
||||
hashbytes = 48
|
||||
elif alg == 'HS512':
|
||||
hashalg = 'sha512'
|
||||
hashbytes = 64
|
||||
else:
|
||||
raise ModuleFailException('Unsupported MAC key algorithm for OpenSSL backend: {0}'.format(alg))
|
||||
key_bytes = base64.urlsafe_b64decode(key)
|
||||
if len(key_bytes) < hashbytes:
|
||||
raise ModuleFailException(
|
||||
'{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes))
|
||||
return {
|
||||
'type': 'hmac',
|
||||
'alg': alg,
|
||||
'jwk': {
|
||||
'kty': 'oct',
|
||||
'k': key,
|
||||
},
|
||||
'hash': hashalg,
|
||||
}
|
||||
|
||||
|
||||
def _sign_request_openssl(openssl_binary, module, payload64, protected64, key_data):
|
||||
openssl_sign_cmd = [openssl_binary, "dgst", "-{0}".format(key_data['hash']), "-sign", key_data['key_file']]
|
||||
sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8')
|
||||
if key_data['type'] == 'hmac':
|
||||
hex_key = to_native(binascii.hexlify(base64.urlsafe_b64decode(key_data['jwk']['k'])))
|
||||
cmd_postfix = ["-mac", "hmac", "-macopt", "hexkey:{0}".format(hex_key), "-binary"]
|
||||
else:
|
||||
cmd_postfix = ["-sign", key_data['key_file']]
|
||||
openssl_sign_cmd = [openssl_binary, "dgst", "-{0}".format(key_data['hash'])] + cmd_postfix
|
||||
|
||||
dummy, out, dummy = module.run_command(openssl_sign_cmd, data=sign_payload, check_rc=True, binary_data=True)
|
||||
|
||||
if key_data['type'] == 'ec':
|
||||
@@ -437,43 +402,9 @@ def _parse_key_cryptography(module, key_file=None, key_content=None):
|
||||
return 'unknown key type "{0}"'.format(type(key)), {}
|
||||
|
||||
|
||||
def _create_mac_key_cryptography(module, alg, key):
|
||||
if alg == 'HS256':
|
||||
hashalg = cryptography.hazmat.primitives.hashes.SHA256
|
||||
hashbytes = 32
|
||||
elif alg == 'HS384':
|
||||
hashalg = cryptography.hazmat.primitives.hashes.SHA384
|
||||
hashbytes = 48
|
||||
elif alg == 'HS512':
|
||||
hashalg = cryptography.hazmat.primitives.hashes.SHA512
|
||||
hashbytes = 64
|
||||
else:
|
||||
raise ModuleFailException('Unsupported MAC key algorithm for cryptography backend: {0}'.format(alg))
|
||||
key_bytes = base64.urlsafe_b64decode(key)
|
||||
if len(key_bytes) < hashbytes:
|
||||
raise ModuleFailException(
|
||||
'{0} key must be at least {1} bytes long (after Base64 decoding)'.format(alg, hashbytes))
|
||||
return {
|
||||
'mac_obj': lambda: cryptography.hazmat.primitives.hmac.HMAC(
|
||||
key_bytes,
|
||||
hashalg(),
|
||||
_cryptography_backend),
|
||||
'type': 'hmac',
|
||||
'alg': alg,
|
||||
'jwk': {
|
||||
'kty': 'oct',
|
||||
'k': key,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _sign_request_cryptography(module, payload64, protected64, key_data):
|
||||
sign_payload = "{0}.{1}".format(protected64, payload64).encode('utf8')
|
||||
if 'mac_obj' in key_data:
|
||||
mac = key_data['mac_obj']()
|
||||
mac.update(sign_payload)
|
||||
signature = mac.finalize()
|
||||
elif isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
if isinstance(key_data['key_obj'], cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
||||
hashalg = cryptography.hazmat.primitives.hashes.SHA256
|
||||
signature = key_data['key_obj'].sign(sign_payload, padding, hashalg())
|
||||
@@ -624,13 +555,6 @@ class ACMEAccount(object):
|
||||
else:
|
||||
return _sign_request_openssl(self._openssl_bin, self.module, payload64, protected64, key_data)
|
||||
|
||||
def _create_mac_key(self, alg, key):
|
||||
'''Create a MAC key.'''
|
||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||
return _create_mac_key_cryptography(self.module, alg, key)
|
||||
else:
|
||||
return _create_mac_key_openssl(self._openssl_bin, self.module, alg, key)
|
||||
|
||||
def _log(self, msg, data=None):
|
||||
'''
|
||||
Write arguments to acme.log when logging is enabled.
|
||||
@@ -758,19 +682,13 @@ class ACMEAccount(object):
|
||||
self.jws_header.pop('jwk')
|
||||
self.jws_header['kid'] = self.uri
|
||||
|
||||
def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True,
|
||||
external_account_binding=None):
|
||||
def _new_reg(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True):
|
||||
'''
|
||||
Registers a new ACME account. Returns a pair ``(created, data)``.
|
||||
Here, ``created`` is ``True`` if the account was created and
|
||||
``False`` if it already existed (e.g. it was not newly created),
|
||||
or does not exist. In case the account was created or exists,
|
||||
``data`` contains the account data; otherwise, it is ``None``.
|
||||
|
||||
If specified, ``external_account_binding`` should be a dictionary
|
||||
with keys ``kid``, ``alg`` and ``key``
|
||||
(https://tools.ietf.org/html/rfc8555#section-7.3.4).
|
||||
|
||||
https://tools.ietf.org/html/rfc8555#section-7.3
|
||||
'''
|
||||
contact = contact or []
|
||||
@@ -784,23 +702,8 @@ class ACMEAccount(object):
|
||||
new_reg['agreement'] = agreement
|
||||
else:
|
||||
new_reg['agreement'] = self.directory['meta']['terms-of-service']
|
||||
if external_account_binding is not None:
|
||||
raise ModuleFailException('External account binding is not supported for ACME v1')
|
||||
url = self.directory['new-reg']
|
||||
else:
|
||||
if (external_account_binding is not None or self.directory['meta'].get('externalAccountRequired')) and allow_creation:
|
||||
# Some ACME servers such as ZeroSSL do not like it when you try to register an existing account
|
||||
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
|
||||
# to see whether the account already exists.
|
||||
|
||||
# Note that we pass contact here: ZeroSSL does not accept regisration calls without contacts, even
|
||||
# if onlyReturnExisting is set to true.
|
||||
created, data = self._new_reg(contact=contact, allow_creation=False)
|
||||
if data:
|
||||
# An account already exists! Return data
|
||||
return created, data
|
||||
# An account does not yet exist. Try to create one next.
|
||||
|
||||
new_reg = {
|
||||
'contact': contact
|
||||
}
|
||||
@@ -810,21 +713,6 @@ class ACMEAccount(object):
|
||||
if terms_agreed:
|
||||
new_reg['termsOfServiceAgreed'] = True
|
||||
url = self.directory['newAccount']
|
||||
if external_account_binding is not None:
|
||||
new_reg['externalAccountBinding'] = self.sign_request(
|
||||
{
|
||||
'alg': external_account_binding['alg'],
|
||||
'kid': external_account_binding['kid'],
|
||||
'url': url,
|
||||
},
|
||||
self.jwk,
|
||||
self._create_mac_key(external_account_binding['alg'], external_account_binding['key'])
|
||||
)
|
||||
elif self.directory['meta'].get('externalAccountRequired') and allow_creation:
|
||||
raise ModuleFailException(
|
||||
'To create an account, an external account binding must be specified. '
|
||||
'Use the acme_account module with the external_account_binding option.'
|
||||
)
|
||||
|
||||
result, info = self.send_signed_request(url, new_reg)
|
||||
|
||||
@@ -894,9 +782,7 @@ class ACMEAccount(object):
|
||||
raise ModuleFailException("Error getting account data from {2}: {0} {1}".format(info['status'], result, self.uri))
|
||||
return result
|
||||
|
||||
def setup_account(self, contact=None, agreement=None, terms_agreed=False,
|
||||
allow_creation=True, remove_account_uri_if_not_exists=False,
|
||||
external_account_binding=None):
|
||||
def setup_account(self, contact=None, agreement=None, terms_agreed=False, allow_creation=True, remove_account_uri_if_not_exists=False):
|
||||
'''
|
||||
Detect or create an account on the ACME server. For ACME v1,
|
||||
as the only way (without knowing an account URI) to test if an
|
||||
@@ -916,10 +802,6 @@ class ACMEAccount(object):
|
||||
The account URI will be stored in ``self.uri``; if it is ``None``,
|
||||
the account does not exist.
|
||||
|
||||
If specified, ``external_account_binding`` should be a dictionary
|
||||
with keys ``kid``, ``alg`` and ``key``
|
||||
(https://tools.ietf.org/html/rfc8555#section-7.3.4).
|
||||
|
||||
https://tools.ietf.org/html/rfc8555#section-7.3
|
||||
'''
|
||||
|
||||
@@ -938,8 +820,7 @@ class ACMEAccount(object):
|
||||
contact,
|
||||
agreement=agreement,
|
||||
terms_agreed=terms_agreed,
|
||||
allow_creation=allow_creation and not self.module.check_mode,
|
||||
external_account_binding=external_account_binding,
|
||||
allow_creation=allow_creation and not self.module.check_mode
|
||||
)
|
||||
if self.module.check_mode and self.uri is None and allow_creation:
|
||||
created = True
|
||||
@@ -1131,13 +1012,11 @@ def handle_standard_module_arguments(module, needs_acme_v2=False):
|
||||
|
||||
if module.params['acme_version'] is None:
|
||||
module.params['acme_version'] = 1
|
||||
module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate("The option 'acme_version' will be required from Ansible 2.14 on", version='2.14')
|
||||
|
||||
if module.params['acme_directory'] is None:
|
||||
module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory'
|
||||
module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate("The option 'acme_directory' will be required from Ansible 2.14 on", version='2.14')
|
||||
|
||||
if needs_acme_v2 and module.params['acme_version'] < 2:
|
||||
module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
# THIS FILE IS FOR COMPATIBILITY ONLY! YOU SHALL NOT IMPORT IT!
|
||||
#
|
||||
# This fill will be removed eventually, so if you're using it,
|
||||
# please stop doing so.
|
||||
|
||||
from .basic import (
|
||||
HAS_PYOPENSSL,
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
HAS_CRYPTOGRAPHY,
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from .cryptography_crl import (
|
||||
REVOCATION_REASON_MAP,
|
||||
REVOCATION_REASON_MAP_INVERSE,
|
||||
cryptography_decode_revoked_certificate,
|
||||
)
|
||||
|
||||
from .cryptography_support import (
|
||||
cryptography_get_extensions_from_cert,
|
||||
cryptography_get_extensions_from_csr,
|
||||
cryptography_name_to_oid,
|
||||
cryptography_oid_to_name,
|
||||
cryptography_get_name,
|
||||
cryptography_decode_name,
|
||||
cryptography_parse_key_usage_params,
|
||||
cryptography_get_basic_constraints,
|
||||
cryptography_key_needs_digest_for_signing,
|
||||
cryptography_compare_public_keys,
|
||||
)
|
||||
|
||||
from .identify import (
|
||||
identify_private_key_format,
|
||||
)
|
||||
|
||||
from .math import (
|
||||
binary_exp_mod,
|
||||
simple_gcd,
|
||||
quick_is_not_prime,
|
||||
count_bits,
|
||||
)
|
||||
|
||||
from ._obj2txt import obj2txt as _obj2txt
|
||||
|
||||
from ._objects_data import OID_MAP as _OID_MAP
|
||||
|
||||
from ._objects import OID_LOOKUP as _OID_LOOKUP
|
||||
from ._objects import NORMALIZE_NAMES as _NORMALIZE_NAMES
|
||||
from ._objects import NORMALIZE_NAMES_SHORT as _NORMALIZE_NAMES_SHORT
|
||||
|
||||
from .pyopenssl_support import (
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_get_extensions_from_cert,
|
||||
pyopenssl_get_extensions_from_csr,
|
||||
)
|
||||
|
||||
from .support import (
|
||||
get_fingerprint_of_bytes,
|
||||
get_fingerprint,
|
||||
load_privatekey,
|
||||
load_certificate,
|
||||
load_certificate_request,
|
||||
parse_name_field,
|
||||
convert_relative_to_datetime,
|
||||
get_relative_time_option,
|
||||
select_message_digest,
|
||||
OpenSSLObject,
|
||||
)
|
||||
|
||||
from ..io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
@@ -1,153 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2020, Jordan Borean <jborean93@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
|
||||
from ansible.module_utils._text import to_bytes
|
||||
|
||||
|
||||
"""
|
||||
An ASN.1 serialized as a string in the OpenSSL format:
|
||||
[modifier,]type[:value]
|
||||
|
||||
modifier:
|
||||
The modifier can be 'IMPLICIT:<tag_number><tag_class>,' or 'EXPLICIT:<tag_number><tag_class>' where IMPLICIT
|
||||
changes the tag of the universal value to encode and EXPLICIT prefixes its tag to the existing universal value.
|
||||
The tag_number must be set while the tag_class can be 'U', 'A', 'P', or 'C" for 'Universal', 'Application',
|
||||
'Private', or 'Context Specific' with C being the default.
|
||||
|
||||
type:
|
||||
The underlying ASN.1 type of the value specified. Currently only the following have been implemented:
|
||||
UTF8: The value must be a UTF-8 encoded string.
|
||||
|
||||
value:
|
||||
The value to encode, the format of this value depends on the <type> specified.
|
||||
"""
|
||||
ASN1_STRING_REGEX = re.compile(r'^((?P<tag_type>IMPLICIT|EXPLICIT):(?P<tag_number>\d+)(?P<tag_class>U|A|P|C)?,)?'
|
||||
r'(?P<value_type>[\w\d]+):(?P<value>.*)')
|
||||
|
||||
|
||||
class TagClass:
|
||||
universal = 0
|
||||
application = 1
|
||||
context_specific = 2
|
||||
private = 3
|
||||
|
||||
|
||||
# Universal tag numbers that can be encoded.
|
||||
class TagNumber:
|
||||
utf8_string = 12
|
||||
|
||||
|
||||
def _pack_octet_integer(value):
|
||||
""" Packs an integer value into 1 or multiple octets. """
|
||||
# NOTE: This is *NOT* the same as packing an ASN.1 INTEGER like value.
|
||||
octets = bytearray()
|
||||
|
||||
# Continue to shift the number by 7 bits and pack into an octet until the
|
||||
# value is fully packed.
|
||||
while value:
|
||||
octet_value = value & 0b01111111
|
||||
|
||||
# First round (last octet) must have the MSB set.
|
||||
if len(octets):
|
||||
octet_value |= 0b10000000
|
||||
|
||||
octets.append(octet_value)
|
||||
value >>= 7
|
||||
|
||||
# Reverse to ensure the higher order octets are first.
|
||||
octets.reverse()
|
||||
return bytes(octets)
|
||||
|
||||
|
||||
def serialize_asn1_string_as_der(value):
|
||||
""" Deserializes an ASN.1 string to a DER encoded byte string. """
|
||||
asn1_match = ASN1_STRING_REGEX.match(value)
|
||||
if not asn1_match:
|
||||
raise ValueError("The ASN.1 serialized string must be in the format [modifier,]type[:value]")
|
||||
|
||||
tag_type = asn1_match.group('tag_type')
|
||||
tag_number = asn1_match.group('tag_number')
|
||||
tag_class = asn1_match.group('tag_class') or 'C'
|
||||
value_type = asn1_match.group('value_type')
|
||||
asn1_value = asn1_match.group('value')
|
||||
|
||||
if value_type != 'UTF8':
|
||||
raise ValueError('The ASN.1 serialized string is not a known type "{0}", only UTF8 types are '
|
||||
'supported'.format(value_type))
|
||||
|
||||
b_value = to_bytes(asn1_value, encoding='utf-8', errors='surrogate_or_strict')
|
||||
|
||||
# We should only do a universal type tag if not IMPLICITLY tagged or the tag class is not universal.
|
||||
if not tag_type or (tag_type == 'EXPLICIT' and tag_class != 'U'):
|
||||
b_value = pack_asn1(TagClass.universal, False, TagNumber.utf8_string, b_value)
|
||||
|
||||
if tag_type:
|
||||
tag_class = {
|
||||
'U': TagClass.universal,
|
||||
'A': TagClass.application,
|
||||
'P': TagClass.private,
|
||||
'C': TagClass.context_specific,
|
||||
}[tag_class]
|
||||
|
||||
# When adding support for more types this should be looked into further. For now it works with UTF8Strings.
|
||||
constructed = tag_type == 'EXPLICIT' and tag_class != TagClass.universal
|
||||
b_value = pack_asn1(tag_class, constructed, int(tag_number), b_value)
|
||||
|
||||
return b_value
|
||||
|
||||
|
||||
def pack_asn1(tag_class, constructed, tag_number, b_data):
|
||||
"""Pack the value into an ASN.1 data structure.
|
||||
|
||||
The structure for an ASN.1 element is
|
||||
|
||||
| Identifier Octet(s) | Length Octet(s) | Data Octet(s) |
|
||||
"""
|
||||
b_asn1_data = bytearray()
|
||||
|
||||
if tag_class < 0 or tag_class > 3:
|
||||
raise ValueError("tag_class must be between 0 and 3 not %s" % tag_class)
|
||||
|
||||
# Bit 8 and 7 denotes the class.
|
||||
identifier_octets = tag_class << 6
|
||||
# Bit 6 denotes whether the value is primitive or constructed.
|
||||
identifier_octets |= ((1 if constructed else 0) << 5)
|
||||
|
||||
# Bits 5-1 contain the tag number, if it cannot be encoded in these 5 bits
|
||||
# then they are set and another octet(s) is used to denote the tag number.
|
||||
if tag_number < 31:
|
||||
identifier_octets |= tag_number
|
||||
b_asn1_data.append(identifier_octets)
|
||||
else:
|
||||
identifier_octets |= 31
|
||||
b_asn1_data.append(identifier_octets)
|
||||
b_asn1_data.extend(_pack_octet_integer(tag_number))
|
||||
|
||||
length = len(b_data)
|
||||
|
||||
# If the length can be encoded in 7 bits only 1 octet is required.
|
||||
if length < 128:
|
||||
b_asn1_data.append(length)
|
||||
|
||||
else:
|
||||
# Otherwise the length must be encoded across multiple octets
|
||||
length_octets = bytearray()
|
||||
while length:
|
||||
length_octets.append(length & 0b11111111)
|
||||
length >>= 8
|
||||
|
||||
length_octets.reverse() # Reverse to make the higher octets first.
|
||||
|
||||
# The first length octet must have the MSB set alongside the number of
|
||||
# octets the length was encoded in.
|
||||
b_asn1_data.append(len(length_octets) | 0b10000000)
|
||||
b_asn1_data.extend(length_octets)
|
||||
|
||||
return bytes(b_asn1_data) + b_data
|
||||
@@ -1,43 +0,0 @@
|
||||
# This excerpt is dual licensed under the terms of the Apache License, Version
|
||||
# 2.0, and the BSD License. See the LICENSE file at
|
||||
# https://github.com/pyca/cryptography/blob/master/LICENSE for complete details.
|
||||
#
|
||||
# Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py
|
||||
#
|
||||
# Copyright (c) 2015, 2016 Paul Kehrer (@reaperhulk)
|
||||
# Copyright (c) 2017 Fraser Tweedale (@frasertweedale)
|
||||
|
||||
# Relevant commits from cryptography project (https://github.com/pyca/cryptography):
|
||||
# pyca/cryptography@719d536dd691e84e208534798f2eb4f82aaa2e07
|
||||
# pyca/cryptography@5ab6d6a5c05572bd1c75f05baf264a2d0001894a
|
||||
# pyca/cryptography@2e776e20eb60378e0af9b7439000d0e80da7c7e3
|
||||
# pyca/cryptography@fb309ed24647d1be9e319b61b1f2aa8ebb87b90b
|
||||
# pyca/cryptography@2917e460993c475c72d7146c50dc3bbc2414280d
|
||||
# pyca/cryptography@3057f91ea9a05fb593825006d87a391286a4d828
|
||||
# pyca/cryptography@d607dd7e5bc5c08854ec0c9baff70ba4a35be36f
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
def obj2txt(openssl_lib, openssl_ffi, obj):
|
||||
# Set to 80 on the recommendation of
|
||||
# https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
|
||||
#
|
||||
# But OIDs longer than this occur in real life (e.g. Active
|
||||
# Directory makes some very long OIDs). So we need to detect
|
||||
# and properly handle the case where the default buffer is not
|
||||
# big enough.
|
||||
#
|
||||
buf_len = 80
|
||||
buf = openssl_ffi.new("char[]", buf_len)
|
||||
|
||||
# 'res' is the number of bytes that *would* be written if the
|
||||
# buffer is large enough. If 'res' > buf_len - 1, we need to
|
||||
# alloc a big-enough buffer and go again.
|
||||
res = openssl_lib.OBJ_obj2txt(buf, buf_len, obj, 1)
|
||||
if res > buf_len - 1: # account for terminating null byte
|
||||
buf_len = res + 1
|
||||
buf = openssl_ffi.new("char[]", buf_len)
|
||||
res = openssl_lib.OBJ_obj2txt(buf, buf_len, obj, 1)
|
||||
return openssl_ffi.buffer(buf, res)[:].decode()
|
||||
@@ -1,46 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from ._objects_data import OID_MAP
|
||||
|
||||
OID_LOOKUP = dict()
|
||||
NORMALIZE_NAMES = dict()
|
||||
NORMALIZE_NAMES_SHORT = dict()
|
||||
|
||||
for dotted, names in OID_MAP.items():
|
||||
for name in names:
|
||||
if name in NORMALIZE_NAMES and OID_LOOKUP[name] != dotted:
|
||||
raise AssertionError(
|
||||
'Name collision during setup: "{0}" for OIDs {1} and {2}'
|
||||
.format(name, dotted, OID_LOOKUP[name])
|
||||
)
|
||||
NORMALIZE_NAMES[name] = names[0]
|
||||
NORMALIZE_NAMES_SHORT[name] = names[-1]
|
||||
OID_LOOKUP[name] = dotted
|
||||
for alias, original in [('userID', 'userId')]:
|
||||
if alias in NORMALIZE_NAMES:
|
||||
raise AssertionError(
|
||||
'Name collision during adding aliases: "{0}" (alias for "{1}") is already mapped to OID {2}'
|
||||
.format(alias, original, OID_LOOKUP[alias])
|
||||
)
|
||||
NORMALIZE_NAMES[alias] = original
|
||||
NORMALIZE_NAMES_SHORT[alias] = NORMALIZE_NAMES_SHORT[original]
|
||||
OID_LOOKUP[alias] = OID_LOOKUP[original]
|
||||
@@ -1,178 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# (c) 2020, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
try:
|
||||
import OpenSSL # noqa
|
||||
from OpenSSL import crypto # noqa
|
||||
HAS_PYOPENSSL = True
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
HAS_PYOPENSSL = False
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
|
||||
# Older versions of cryptography (< 2.1) do not have __hash__ functions for
|
||||
# general name objects (DNSName, IPAddress, ...), while providing overloaded
|
||||
# equality and string representation operations. This makes it impossible to
|
||||
# use them in hash-based data structures such as set or dict. Since we are
|
||||
# actually doing that in x509_certificate, and potentially in other code,
|
||||
# we need to monkey-patch __hash__ for these classes to make sure our code
|
||||
# works fine.
|
||||
if LooseVersion(cryptography.__version__) < LooseVersion('2.1'):
|
||||
# A very simply hash function which relies on the representation
|
||||
# of an object to be implemented. This is the case since at least
|
||||
# cryptography 1.0, see
|
||||
# https://github.com/pyca/cryptography/commit/7a9abce4bff36c05d26d8d2680303a6f64a0e84f
|
||||
def simple_hash(self):
|
||||
return hash(repr(self))
|
||||
|
||||
# The hash functions for the following types were added for cryptography 2.1:
|
||||
# https://github.com/pyca/cryptography/commit/fbfc36da2a4769045f2373b004ddf0aff906cf38
|
||||
x509.DNSName.__hash__ = simple_hash
|
||||
x509.DirectoryName.__hash__ = simple_hash
|
||||
x509.GeneralName.__hash__ = simple_hash
|
||||
x509.IPAddress.__hash__ = simple_hash
|
||||
x509.OtherName.__hash__ = simple_hash
|
||||
x509.RegisteredID.__hash__ = simple_hash
|
||||
|
||||
if LooseVersion(cryptography.__version__) < LooseVersion('1.2'):
|
||||
# The hash functions for the following types were added for cryptography 1.2:
|
||||
# https://github.com/pyca/cryptography/commit/b642deed88a8696e5f01ce6855ccf89985fc35d0
|
||||
# https://github.com/pyca/cryptography/commit/d1b5681f6db2bde7a14625538bd7907b08dfb486
|
||||
x509.RFC822Name.__hash__ = simple_hash
|
||||
x509.UniformResourceIdentifier.__hash__ = simple_hash
|
||||
|
||||
# Test whether we have support for DSA, EC, Ed25519, Ed448, RSA, X25519 and/or X448
|
||||
try:
|
||||
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/
|
||||
import cryptography.hazmat.primitives.asymmetric.dsa
|
||||
CRYPTOGRAPHY_HAS_DSA = True
|
||||
try:
|
||||
# added later in 1.5
|
||||
cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN = True
|
||||
except AttributeError:
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_DSA = False
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN = False
|
||||
try:
|
||||
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/
|
||||
import cryptography.hazmat.primitives.asymmetric.ed25519
|
||||
CRYPTOGRAPHY_HAS_ED25519 = True
|
||||
try:
|
||||
# added with the primitive in 2.6
|
||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.sign
|
||||
CRYPTOGRAPHY_HAS_ED25519_SIGN = True
|
||||
except AttributeError:
|
||||
CRYPTOGRAPHY_HAS_ED25519_SIGN = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_ED25519 = False
|
||||
CRYPTOGRAPHY_HAS_ED25519_SIGN = False
|
||||
try:
|
||||
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/
|
||||
import cryptography.hazmat.primitives.asymmetric.ed448
|
||||
CRYPTOGRAPHY_HAS_ED448 = True
|
||||
try:
|
||||
# added with the primitive in 2.6
|
||||
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.sign
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN = True
|
||||
except AttributeError:
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_ED448 = False
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN = False
|
||||
try:
|
||||
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
|
||||
import cryptography.hazmat.primitives.asymmetric.ec
|
||||
CRYPTOGRAPHY_HAS_EC = True
|
||||
try:
|
||||
# added later in 1.5
|
||||
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN = True
|
||||
except AttributeError:
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_EC = False
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN = False
|
||||
try:
|
||||
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
|
||||
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||
CRYPTOGRAPHY_HAS_RSA = True
|
||||
try:
|
||||
# added later in 1.4
|
||||
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN = True
|
||||
except AttributeError:
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_RSA = False
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN = False
|
||||
try:
|
||||
# added in 2.0 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x25519/
|
||||
import cryptography.hazmat.primitives.asymmetric.x25519
|
||||
CRYPTOGRAPHY_HAS_X25519 = True
|
||||
try:
|
||||
# added later in 2.5
|
||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.private_bytes
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL = True
|
||||
except AttributeError:
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_X25519 = False
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL = False
|
||||
try:
|
||||
# added in 2.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/
|
||||
import cryptography.hazmat.primitives.asymmetric.x448
|
||||
CRYPTOGRAPHY_HAS_X448 = True
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_X448 = False
|
||||
|
||||
HAS_CRYPTOGRAPHY = True
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
CRYPTOGRAPHY_HAS_EC = False
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN = False
|
||||
CRYPTOGRAPHY_HAS_ED25519 = False
|
||||
CRYPTOGRAPHY_HAS_ED25519_SIGN = False
|
||||
CRYPTOGRAPHY_HAS_ED448 = False
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN = False
|
||||
CRYPTOGRAPHY_HAS_DSA = False
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN = False
|
||||
CRYPTOGRAPHY_HAS_RSA = False
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN = False
|
||||
CRYPTOGRAPHY_HAS_X25519 = False
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL = False
|
||||
CRYPTOGRAPHY_HAS_X448 = False
|
||||
HAS_CRYPTOGRAPHY = False
|
||||
|
||||
|
||||
class OpenSSLObjectError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OpenSSLBadPassphraseError(OpenSSLObjectError):
|
||||
pass
|
||||
@@ -1,125 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
try:
|
||||
from cryptography import x509
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
pass
|
||||
|
||||
from .basic import (
|
||||
HAS_CRYPTOGRAPHY,
|
||||
)
|
||||
|
||||
from .cryptography_support import (
|
||||
cryptography_decode_name,
|
||||
)
|
||||
|
||||
from ._obj2txt import (
|
||||
obj2txt,
|
||||
)
|
||||
|
||||
|
||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
if HAS_CRYPTOGRAPHY:
|
||||
REVOCATION_REASON_MAP = {
|
||||
'unspecified': x509.ReasonFlags.unspecified,
|
||||
'key_compromise': x509.ReasonFlags.key_compromise,
|
||||
'ca_compromise': x509.ReasonFlags.ca_compromise,
|
||||
'affiliation_changed': x509.ReasonFlags.affiliation_changed,
|
||||
'superseded': x509.ReasonFlags.superseded,
|
||||
'cessation_of_operation': x509.ReasonFlags.cessation_of_operation,
|
||||
'certificate_hold': x509.ReasonFlags.certificate_hold,
|
||||
'privilege_withdrawn': x509.ReasonFlags.privilege_withdrawn,
|
||||
'aa_compromise': x509.ReasonFlags.aa_compromise,
|
||||
'remove_from_crl': x509.ReasonFlags.remove_from_crl,
|
||||
}
|
||||
REVOCATION_REASON_MAP_INVERSE = dict()
|
||||
for k, v in REVOCATION_REASON_MAP.items():
|
||||
REVOCATION_REASON_MAP_INVERSE[v] = k
|
||||
|
||||
else:
|
||||
REVOCATION_REASON_MAP = dict()
|
||||
REVOCATION_REASON_MAP_INVERSE = dict()
|
||||
|
||||
|
||||
def cryptography_decode_revoked_certificate(cert):
|
||||
result = {
|
||||
'serial_number': cert.serial_number,
|
||||
'revocation_date': cert.revocation_date,
|
||||
'issuer': None,
|
||||
'issuer_critical': False,
|
||||
'reason': None,
|
||||
'reason_critical': False,
|
||||
'invalidity_date': None,
|
||||
'invalidity_date_critical': False,
|
||||
}
|
||||
try:
|
||||
ext = cert.extensions.get_extension_for_class(x509.CertificateIssuer)
|
||||
result['issuer'] = list(ext.value)
|
||||
result['issuer_critical'] = ext.critical
|
||||
except x509.ExtensionNotFound:
|
||||
pass
|
||||
try:
|
||||
ext = cert.extensions.get_extension_for_class(x509.CRLReason)
|
||||
result['reason'] = ext.value.reason
|
||||
result['reason_critical'] = ext.critical
|
||||
except x509.ExtensionNotFound:
|
||||
pass
|
||||
try:
|
||||
ext = cert.extensions.get_extension_for_class(x509.InvalidityDate)
|
||||
result['invalidity_date'] = ext.value.invalidity_date
|
||||
result['invalidity_date_critical'] = ext.critical
|
||||
except x509.ExtensionNotFound:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
def cryptography_dump_revoked(entry):
|
||||
return {
|
||||
'serial_number': entry['serial_number'],
|
||||
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
|
||||
'issuer':
|
||||
[cryptography_decode_name(issuer) for issuer in entry['issuer']]
|
||||
if entry['issuer'] is not None else None,
|
||||
'issuer_critical': entry['issuer_critical'],
|
||||
'reason': REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
|
||||
'reason_critical': entry['reason_critical'],
|
||||
'invalidity_date':
|
||||
entry['invalidity_date'].strftime(TIMESTAMP_FORMAT)
|
||||
if entry['invalidity_date'] is not None else None,
|
||||
'invalidity_date_critical': entry['invalidity_date_critical'],
|
||||
}
|
||||
|
||||
|
||||
def cryptography_get_signature_algorithm_oid_from_crl(crl):
|
||||
try:
|
||||
return crl.signature_algorithm_oid
|
||||
except AttributeError:
|
||||
# Older cryptography versions don't have signature_algorithm_oid yet
|
||||
dotted = obj2txt(
|
||||
crl._backend._lib,
|
||||
crl._backend._ffi,
|
||||
crl._x509_crl.sig_alg.algorithm
|
||||
)
|
||||
return x509.oid.ObjectIdentifier(dotted)
|
||||
@@ -1,407 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import re
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ._asn1 import serialize_asn1_string_as_der
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
import ipaddress
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
pass
|
||||
|
||||
from .basic import (
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ._objects import (
|
||||
OID_LOOKUP,
|
||||
OID_MAP,
|
||||
NORMALIZE_NAMES_SHORT,
|
||||
NORMALIZE_NAMES,
|
||||
)
|
||||
|
||||
from ._obj2txt import obj2txt
|
||||
|
||||
|
||||
DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
|
||||
|
||||
|
||||
def cryptography_get_extensions_from_cert(cert):
|
||||
# Since cryptography won't give us the DER value for an extension
|
||||
# (that is only stored for unrecognized extensions), we have to re-do
|
||||
# the extension parsing outselves.
|
||||
result = dict()
|
||||
backend = cert._backend
|
||||
x509_obj = cert._x509
|
||||
|
||||
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
|
||||
ext = backend._lib.X509_get_ext(x509_obj, i)
|
||||
if ext == backend._ffi.NULL:
|
||||
continue
|
||||
crit = backend._lib.X509_EXTENSION_get_critical(ext)
|
||||
data = backend._lib.X509_EXTENSION_get_data(ext)
|
||||
backend.openssl_assert(data != backend._ffi.NULL)
|
||||
der = backend._ffi.buffer(data.data, data.length)[:]
|
||||
entry = dict(
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der),
|
||||
)
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
result[oid] = entry
|
||||
return result
|
||||
|
||||
|
||||
def cryptography_get_extensions_from_csr(csr):
|
||||
# Since cryptography won't give us the DER value for an extension
|
||||
# (that is only stored for unrecognized extensions), we have to re-do
|
||||
# the extension parsing outselves.
|
||||
result = dict()
|
||||
backend = csr._backend
|
||||
|
||||
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
|
||||
extensions = backend._ffi.gc(
|
||||
extensions,
|
||||
lambda ext: backend._lib.sk_X509_EXTENSION_pop_free(
|
||||
ext,
|
||||
backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free")
|
||||
)
|
||||
)
|
||||
|
||||
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
|
||||
ext = backend._lib.sk_X509_EXTENSION_value(extensions, i)
|
||||
if ext == backend._ffi.NULL:
|
||||
continue
|
||||
crit = backend._lib.X509_EXTENSION_get_critical(ext)
|
||||
data = backend._lib.X509_EXTENSION_get_data(ext)
|
||||
backend.openssl_assert(data != backend._ffi.NULL)
|
||||
der = backend._ffi.buffer(data.data, data.length)[:]
|
||||
entry = dict(
|
||||
critical=(crit == 1),
|
||||
value=base64.b64encode(der),
|
||||
)
|
||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||
result[oid] = entry
|
||||
return result
|
||||
|
||||
|
||||
def cryptography_name_to_oid(name):
|
||||
dotted = OID_LOOKUP.get(name)
|
||||
if dotted is None:
|
||||
if DOTTED_OID.match(name):
|
||||
return x509.oid.ObjectIdentifier(name)
|
||||
raise OpenSSLObjectError('Cannot find OID for "{0}"'.format(name))
|
||||
return x509.oid.ObjectIdentifier(dotted)
|
||||
|
||||
|
||||
def cryptography_oid_to_name(oid, short=False):
|
||||
dotted_string = oid.dotted_string
|
||||
names = OID_MAP.get(dotted_string)
|
||||
if names:
|
||||
name = names[0]
|
||||
else:
|
||||
name = oid._name
|
||||
if name == 'Unknown OID':
|
||||
name = dotted_string
|
||||
if short:
|
||||
return NORMALIZE_NAMES_SHORT.get(name, name)
|
||||
else:
|
||||
return NORMALIZE_NAMES.get(name, name)
|
||||
|
||||
|
||||
def _get_hex(bytesstr):
|
||||
if bytesstr is None:
|
||||
return bytesstr
|
||||
data = binascii.hexlify(bytesstr)
|
||||
data = to_text(b':'.join(data[i:i + 2] for i in range(0, len(data), 2)))
|
||||
return data
|
||||
|
||||
|
||||
def _parse_hex(bytesstr):
|
||||
if bytesstr is None:
|
||||
return bytesstr
|
||||
data = ''.join([('0' * (2 - len(p)) + p) if len(p) < 2 else p for p in to_text(bytesstr).split(':')])
|
||||
data = binascii.unhexlify(data)
|
||||
return data
|
||||
|
||||
|
||||
def _parse_dn(name):
|
||||
'''
|
||||
Parse a Distinguished Name.
|
||||
|
||||
Can be of the form ``CN=Test, O = Something`` or ``CN = Test,O= Something``.
|
||||
'''
|
||||
original_name = name
|
||||
name = name.lstrip()
|
||||
sep = ','
|
||||
if name.startswith('/'):
|
||||
sep = '/'
|
||||
name = name[1:]
|
||||
sep_str = sep + '\\'
|
||||
result = []
|
||||
start_re = re.compile(r'^ *([a-zA-z0-9]+) *= *')
|
||||
while name:
|
||||
m = start_re.match(name)
|
||||
if not m:
|
||||
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": cannot start part in "{1}"'.format(original_name, name))
|
||||
oid = cryptography_name_to_oid(m.group(1))
|
||||
idx = len(m.group(0))
|
||||
decoded_name = []
|
||||
length = len(name)
|
||||
while idx < length:
|
||||
i = idx
|
||||
while i < length and name[i] not in sep_str:
|
||||
i += 1
|
||||
if i > idx:
|
||||
decoded_name.append(name[idx:i])
|
||||
idx = i
|
||||
while idx + 1 < length and name[idx] == '\\':
|
||||
decoded_name.append(name[idx + 1])
|
||||
idx += 2
|
||||
if idx < length and name[idx] == sep:
|
||||
break
|
||||
result.append(x509.NameAttribute(oid, ''.join(decoded_name)))
|
||||
name = name[idx:]
|
||||
if name:
|
||||
if name[0] != sep or len(name) < 2:
|
||||
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": unexpected end of string'.format(original_name))
|
||||
name = name[1:]
|
||||
return result
|
||||
|
||||
|
||||
def cryptography_get_name(name):
|
||||
'''
|
||||
Given a name string, returns a cryptography x509.Name object.
|
||||
Raises an OpenSSLObjectError if the name is unknown or cannot be parsed.
|
||||
'''
|
||||
try:
|
||||
if name.startswith('DNS:'):
|
||||
return x509.DNSName(to_text(name[4:]))
|
||||
if name.startswith('IP:'):
|
||||
address = to_text(name[3:])
|
||||
if '/' in address:
|
||||
return x509.IPAddress(ipaddress.ip_network(address))
|
||||
return x509.IPAddress(ipaddress.ip_address(address))
|
||||
if name.startswith('email:'):
|
||||
return x509.RFC822Name(to_text(name[6:]))
|
||||
if name.startswith('URI:'):
|
||||
return x509.UniformResourceIdentifier(to_text(name[4:]))
|
||||
if name.startswith('RID:'):
|
||||
m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:]))
|
||||
if not m:
|
||||
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}"'.format(name))
|
||||
return x509.RegisteredID(x509.oid.ObjectIdentifier(m.group(1)))
|
||||
if name.startswith('otherName:'):
|
||||
# otherName can either be a raw ASN.1 hex string or in the format that OpenSSL works with.
|
||||
m = re.match(r'^([0-9]+(?:\.[0-9]+)*);([0-9a-fA-F]{1,2}(?::[0-9a-fA-F]{1,2})*)$', to_text(name[10:]))
|
||||
if m:
|
||||
return x509.OtherName(x509.oid.ObjectIdentifier(m.group(1)), _parse_hex(m.group(2)))
|
||||
|
||||
# See https://www.openssl.org/docs/man1.0.2/man5/x509v3_config.html - Subject Alternative Name for more
|
||||
# defailts on the format expected.
|
||||
name = to_text(name[10:], errors='surrogate_or_strict')
|
||||
if ';' not in name:
|
||||
raise OpenSSLObjectError('Cannot parse Subject Alternative Name otherName "{0}", must be in the '
|
||||
'format "otherName:<OID>;<ASN.1 OpenSSL Encoded String>" or '
|
||||
'"otherName:<OID>;<hex string>"'.format(name))
|
||||
|
||||
oid, value = name.split(';', 1)
|
||||
b_value = serialize_asn1_string_as_der(value)
|
||||
return x509.OtherName(x509.ObjectIdentifier(oid), b_value)
|
||||
if name.startswith('dirName:'):
|
||||
return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:]))))
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}": {1}'.format(name, e))
|
||||
if ':' not in name:
|
||||
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (forgot "DNS:" prefix?)'.format(name))
|
||||
raise OpenSSLObjectError('Cannot parse Subject Alternative Name "{0}" (potentially unsupported by cryptography backend)'.format(name))
|
||||
|
||||
|
||||
def _dn_escape_value(value):
|
||||
'''
|
||||
Escape Distinguished Name's attribute value.
|
||||
'''
|
||||
value = value.replace('\\', '\\\\')
|
||||
for ch in [',', '#', '+', '<', '>', ';', '"', '=', '/']:
|
||||
value = value.replace(ch, '\\%s' % ch)
|
||||
if value.startswith(' '):
|
||||
value = r'\ ' + value[1:]
|
||||
return value
|
||||
|
||||
|
||||
def cryptography_decode_name(name):
|
||||
'''
|
||||
Given a cryptography x509.Name object, returns a string.
|
||||
Raises an OpenSSLObjectError if the name is not supported.
|
||||
'''
|
||||
if isinstance(name, x509.DNSName):
|
||||
return 'DNS:{0}'.format(name.value)
|
||||
if isinstance(name, x509.IPAddress):
|
||||
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
|
||||
return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
|
||||
return 'IP:{0}'.format(name.value.compressed)
|
||||
if isinstance(name, x509.RFC822Name):
|
||||
return 'email:{0}'.format(name.value)
|
||||
if isinstance(name, x509.UniformResourceIdentifier):
|
||||
return 'URI:{0}'.format(name.value)
|
||||
if isinstance(name, x509.DirectoryName):
|
||||
return 'dirName:' + ''.join([
|
||||
'/{0}={1}'.format(cryptography_oid_to_name(attribute.oid, short=True), _dn_escape_value(attribute.value))
|
||||
for attribute in name.value
|
||||
])
|
||||
if isinstance(name, x509.RegisteredID):
|
||||
return 'RID:{0}'.format(name.value.dotted_string)
|
||||
if isinstance(name, x509.OtherName):
|
||||
return 'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value))
|
||||
raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name))
|
||||
|
||||
|
||||
def _cryptography_get_keyusage(usage):
|
||||
'''
|
||||
Given a key usage identifier string, returns the parameter name used by cryptography's x509.KeyUsage().
|
||||
Raises an OpenSSLObjectError if the identifier is unknown.
|
||||
'''
|
||||
if usage in ('Digital Signature', 'digitalSignature'):
|
||||
return 'digital_signature'
|
||||
if usage in ('Non Repudiation', 'nonRepudiation'):
|
||||
return 'content_commitment'
|
||||
if usage in ('Key Encipherment', 'keyEncipherment'):
|
||||
return 'key_encipherment'
|
||||
if usage in ('Data Encipherment', 'dataEncipherment'):
|
||||
return 'data_encipherment'
|
||||
if usage in ('Key Agreement', 'keyAgreement'):
|
||||
return 'key_agreement'
|
||||
if usage in ('Certificate Sign', 'keyCertSign'):
|
||||
return 'key_cert_sign'
|
||||
if usage in ('CRL Sign', 'cRLSign'):
|
||||
return 'crl_sign'
|
||||
if usage in ('Encipher Only', 'encipherOnly'):
|
||||
return 'encipher_only'
|
||||
if usage in ('Decipher Only', 'decipherOnly'):
|
||||
return 'decipher_only'
|
||||
raise OpenSSLObjectError('Unknown key usage "{0}"'.format(usage))
|
||||
|
||||
|
||||
def cryptography_parse_key_usage_params(usages):
|
||||
'''
|
||||
Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage().
|
||||
Raises an OpenSSLObjectError if an identifier is unknown.
|
||||
'''
|
||||
params = dict(
|
||||
digital_signature=False,
|
||||
content_commitment=False,
|
||||
key_encipherment=False,
|
||||
data_encipherment=False,
|
||||
key_agreement=False,
|
||||
key_cert_sign=False,
|
||||
crl_sign=False,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
)
|
||||
for usage in usages:
|
||||
params[_cryptography_get_keyusage(usage)] = True
|
||||
return params
|
||||
|
||||
|
||||
def cryptography_get_basic_constraints(constraints):
|
||||
'''
|
||||
Given a list of constraints, returns a tuple (ca, path_length).
|
||||
Raises an OpenSSLObjectError if a constraint is unknown or cannot be parsed.
|
||||
'''
|
||||
ca = False
|
||||
path_length = None
|
||||
if constraints:
|
||||
for constraint in constraints:
|
||||
if constraint.startswith('CA:'):
|
||||
if constraint == 'CA:TRUE':
|
||||
ca = True
|
||||
elif constraint == 'CA:FALSE':
|
||||
ca = False
|
||||
else:
|
||||
raise OpenSSLObjectError('Unknown basic constraint value "{0}" for CA'.format(constraint[3:]))
|
||||
elif constraint.startswith('pathlen:'):
|
||||
v = constraint[len('pathlen:'):]
|
||||
try:
|
||||
path_length = int(v)
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError('Cannot parse path length constraint "{0}" ({1})'.format(v, e))
|
||||
else:
|
||||
raise OpenSSLObjectError('Unknown basic constraint "{0}"'.format(constraint))
|
||||
return ca, path_length
|
||||
|
||||
|
||||
def cryptography_key_needs_digest_for_signing(key):
|
||||
'''Tests whether the given private key requires a digest algorithm for signing.
|
||||
|
||||
Ed25519 and Ed448 keys do not; they need None to be passed as the digest algorithm.
|
||||
'''
|
||||
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
|
||||
return False
|
||||
if CRYPTOGRAPHY_HAS_ED448 and isinstance(key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def cryptography_compare_public_keys(key1, key2):
|
||||
'''Tests whether two public keys are the same.
|
||||
|
||||
Needs special logic for Ed25519 and Ed448 keys, since they do not have public_numbers().
|
||||
'''
|
||||
if CRYPTOGRAPHY_HAS_ED25519:
|
||||
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
|
||||
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
|
||||
if a or b:
|
||||
if not a or not b:
|
||||
return False
|
||||
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
return a == b
|
||||
if CRYPTOGRAPHY_HAS_ED448:
|
||||
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
|
||||
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
|
||||
if a or b:
|
||||
if not a or not b:
|
||||
return False
|
||||
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||||
return a == b
|
||||
return key1.public_numbers() == key2.public_numbers()
|
||||
|
||||
|
||||
def cryptography_serial_number_of_cert(cert):
|
||||
'''Returns cert.serial_number.
|
||||
|
||||
Also works for old versions of cryptography.
|
||||
'''
|
||||
try:
|
||||
return cert.serial_number
|
||||
except AttributeError:
|
||||
# The property was called "serial" before cryptography 1.4
|
||||
return cert.serial
|
||||
@@ -1,56 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
PEM_START = '-----BEGIN '
|
||||
PEM_END = '-----'
|
||||
PKCS8_PRIVATEKEY_NAMES = ('PRIVATE KEY', 'ENCRYPTED PRIVATE KEY')
|
||||
PKCS1_PRIVATEKEY_SUFFIX = ' PRIVATE KEY'
|
||||
|
||||
|
||||
def identify_pem_format(content):
|
||||
'''Given the contents of a binary file, tests whether this could be a PEM file.'''
|
||||
try:
|
||||
lines = content.decode('utf-8').splitlines(False)
|
||||
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
|
||||
return True
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def identify_private_key_format(content):
|
||||
'''Given the contents of a private key file, identifies its format.'''
|
||||
# See https://github.com/openssl/openssl/blob/master/crypto/pem/pem_pkey.c#L40-L85
|
||||
# (PEM_read_bio_PrivateKey)
|
||||
# and https://github.com/openssl/openssl/blob/master/include/openssl/pem.h#L46-L47
|
||||
# (PEM_STRING_PKCS8, PEM_STRING_PKCS8INF)
|
||||
try:
|
||||
lines = content.decode('utf-8').splitlines(False)
|
||||
if lines[0].startswith(PEM_START) and lines[0].endswith(PEM_END) and len(lines[0]) > len(PEM_START) + len(PEM_END):
|
||||
name = lines[0][len(PEM_START):-len(PEM_END)]
|
||||
if name in PKCS8_PRIVATEKEY_NAMES:
|
||||
return 'pkcs8'
|
||||
if len(name) > len(PKCS1_PRIVATEKEY_SUFFIX) and name.endswith(PKCS1_PRIVATEKEY_SUFFIX):
|
||||
return 'pkcs1'
|
||||
return 'unknown-pem'
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return 'raw'
|
||||
@@ -1,81 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def binary_exp_mod(f, e, m):
|
||||
'''Computes f^e mod m in O(log e) multiplications modulo m.'''
|
||||
# Compute len_e = floor(log_2(e))
|
||||
len_e = -1
|
||||
x = e
|
||||
while x > 0:
|
||||
x >>= 1
|
||||
len_e += 1
|
||||
# Compute f**e mod m
|
||||
result = 1
|
||||
for k in range(len_e, -1, -1):
|
||||
result = (result * result) % m
|
||||
if ((e >> k) & 1) != 0:
|
||||
result = (result * f) % m
|
||||
return result
|
||||
|
||||
|
||||
def simple_gcd(a, b):
|
||||
'''Compute GCD of its two inputs.'''
|
||||
while b != 0:
|
||||
a, b = b, a % b
|
||||
return a
|
||||
|
||||
|
||||
def quick_is_not_prime(n):
|
||||
'''Does some quick checks to see if we can poke a hole into the primality of n.
|
||||
|
||||
A result of `False` does **not** mean that the number is prime; it just means
|
||||
that we couldn't detect quickly whether it is not prime.
|
||||
'''
|
||||
if n <= 2:
|
||||
return True
|
||||
# The constant in the next line is the product of all primes < 200
|
||||
if simple_gcd(n, 7799922041683461553249199106329813876687996789903550945093032474868511536164700810) > 1:
|
||||
return True
|
||||
# TODO: maybe do some iterations of Miller-Rabin to increase confidence
|
||||
# (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test)
|
||||
return False
|
||||
|
||||
|
||||
python_version = (sys.version_info[0], sys.version_info[1])
|
||||
if python_version >= (2, 7) or python_version >= (3, 1):
|
||||
# Ansible still supports Python 2.6 on remote nodes
|
||||
def count_bits(no):
|
||||
no = abs(no)
|
||||
if no == 0:
|
||||
return 0
|
||||
return no.bit_length()
|
||||
else:
|
||||
# Slow, but works
|
||||
def count_bits(no):
|
||||
no = abs(no)
|
||||
count = 0
|
||||
while no > 0:
|
||||
no >>= 1
|
||||
count += 1
|
||||
return count
|
||||
@@ -1,154 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2019, Felix Fontein <felix@fontein.de>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import base64
|
||||
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
pass
|
||||
|
||||
from ._objects import (
|
||||
NORMALIZE_NAMES_SHORT,
|
||||
NORMALIZE_NAMES,
|
||||
)
|
||||
|
||||
from ._obj2txt import obj2txt
|
||||
|
||||
from .basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
|
||||
def pyopenssl_normalize_name(name, short=False):
|
||||
nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(name))
|
||||
if nid != 0:
|
||||
b_name = OpenSSL._util.lib.OBJ_nid2ln(nid)
|
||||
name = to_text(OpenSSL._util.ffi.string(b_name))
|
||||
if short:
|
||||
return NORMALIZE_NAMES_SHORT.get(name, name)
|
||||
else:
|
||||
return NORMALIZE_NAMES.get(name, name)
|
||||
|
||||
|
||||
def pyopenssl_normalize_name_attribute(san):
|
||||
# apparently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
|
||||
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
|
||||
if san.startswith('IP Address:'):
|
||||
san = 'IP:' + san[len('IP Address:'):]
|
||||
if san.startswith('IP:'):
|
||||
address = san[3:]
|
||||
if '/' in address:
|
||||
ip = compat_ipaddress.ip_network(address)
|
||||
san = 'IP:{0}/{1}'.format(ip.network_address.compressed, ip.prefixlen)
|
||||
else:
|
||||
ip = compat_ipaddress.ip_address(address)
|
||||
san = 'IP:{0}'.format(ip.compressed)
|
||||
if san.startswith('Registered ID:'):
|
||||
san = 'RID:' + san[len('Registered ID:'):]
|
||||
# Some versions of OpenSSL apparently forgot the colon. Happens in CI with Ubuntu 16.04 and FreeBSD 11.1
|
||||
if san.startswith('Registered ID'):
|
||||
san = 'RID:' + san[len('Registered ID'):]
|
||||
return san
|
||||
|
||||
|
||||
def pyopenssl_get_extensions_from_cert(cert):
|
||||
# While pyOpenSSL allows us to get an extension's DER value, it won't
|
||||
# give us the dotted string for an OID. So we have to do some magic to
|
||||
# get hold of it.
|
||||
result = dict()
|
||||
ext_count = cert.get_extension_count()
|
||||
for i in range(0, ext_count):
|
||||
ext = cert.get_extension(i)
|
||||
entry = dict(
|
||||
critical=bool(ext.get_critical()),
|
||||
value=base64.b64encode(ext.get_data()),
|
||||
)
|
||||
oid = obj2txt(
|
||||
OpenSSL._util.lib,
|
||||
OpenSSL._util.ffi,
|
||||
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
|
||||
)
|
||||
# This could also be done a bit simpler:
|
||||
#
|
||||
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
|
||||
#
|
||||
# Unfortunately this gives the wrong result in case the linked OpenSSL
|
||||
# doesn't know the OID. That's why we have to get the OID dotted string
|
||||
# similarly to how cryptography does it.
|
||||
result[oid] = entry
|
||||
return result
|
||||
|
||||
|
||||
def pyopenssl_get_extensions_from_csr(csr):
|
||||
# While pyOpenSSL allows us to get an extension's DER value, it won't
|
||||
# give us the dotted string for an OID. So we have to do some magic to
|
||||
# get hold of it.
|
||||
result = dict()
|
||||
for ext in csr.get_extensions():
|
||||
entry = dict(
|
||||
critical=bool(ext.get_critical()),
|
||||
value=base64.b64encode(ext.get_data()),
|
||||
)
|
||||
oid = obj2txt(
|
||||
OpenSSL._util.lib,
|
||||
OpenSSL._util.ffi,
|
||||
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
|
||||
)
|
||||
# This could also be done a bit simpler:
|
||||
#
|
||||
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
|
||||
#
|
||||
# Unfortunately this gives the wrong result in case the linked OpenSSL
|
||||
# doesn't know the OID. That's why we have to get the OID dotted string
|
||||
# similarly to how cryptography does it.
|
||||
result[oid] = entry
|
||||
return result
|
||||
|
||||
|
||||
def pyopenssl_parse_name_constraints(name_constraints_extension):
|
||||
lines = to_text(name_constraints_extension, errors='surrogate_or_strict').splitlines()
|
||||
exclude = None
|
||||
excluded = []
|
||||
permitted = []
|
||||
for line in lines:
|
||||
if line.startswith(' ') or line.startswith('\t'):
|
||||
name = pyopenssl_normalize_name_attribute(line.strip())
|
||||
if exclude is True:
|
||||
excluded.append(name)
|
||||
elif exclude is False:
|
||||
permitted.append(name)
|
||||
else:
|
||||
raise OpenSSLObjectError('Unexpected nameConstraint line: "{0}"'.format(line))
|
||||
else:
|
||||
line_lc = line.lower()
|
||||
if line_lc.startswith('exclud'):
|
||||
exclude = True
|
||||
elif line_lc.startswith('includ') or line_lc.startswith('permitt'):
|
||||
exclude = False
|
||||
else:
|
||||
raise OpenSSLObjectError('Cannot parse nameConstraint line: "{0}"'.format(line))
|
||||
return permitted, excluded
|
||||
@@ -1,354 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import abc
|
||||
import datetime
|
||||
import errno
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
|
||||
from ansible.module_utils import six
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
HAS_PYOPENSSL = True
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
HAS_PYOPENSSL = False
|
||||
|
||||
try:
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend as cryptography_backend
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
except ImportError:
|
||||
# Error handled in the calling module.
|
||||
pass
|
||||
|
||||
from .basic import (
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
|
||||
def get_fingerprint_of_bytes(source):
|
||||
"""Generate the fingerprint of the given bytes."""
|
||||
|
||||
fingerprint = {}
|
||||
|
||||
try:
|
||||
algorithms = hashlib.algorithms
|
||||
except AttributeError:
|
||||
try:
|
||||
algorithms = hashlib.algorithms_guaranteed
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
for algo in algorithms:
|
||||
f = getattr(hashlib, algo)
|
||||
try:
|
||||
h = f(source)
|
||||
except ValueError:
|
||||
# This can happen for hash algorithms not supported in FIPS mode
|
||||
# (https://github.com/ansible/ansible/issues/67213)
|
||||
continue
|
||||
try:
|
||||
# Certain hash functions have a hexdigest() which expects a length parameter
|
||||
pubkey_digest = h.hexdigest()
|
||||
except TypeError:
|
||||
pubkey_digest = h.hexdigest(32)
|
||||
fingerprint[algo] = ':'.join(pubkey_digest[i:i + 2] for i in range(0, len(pubkey_digest), 2))
|
||||
|
||||
return fingerprint
|
||||
|
||||
|
||||
def get_fingerprint(path, passphrase=None, content=None, backend='pyopenssl'):
|
||||
"""Generate the fingerprint of the public key. """
|
||||
|
||||
privatekey = load_privatekey(path, passphrase=passphrase, content=content, check_passphrase=False, backend=backend)
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
try:
|
||||
publickey = crypto.dump_publickey(crypto.FILETYPE_ASN1, privatekey)
|
||||
except AttributeError:
|
||||
# If PyOpenSSL < 16.0 crypto.dump_publickey() will fail.
|
||||
try:
|
||||
bio = crypto._new_mem_buf()
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, privatekey._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
publickey = crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
# By doing this we prevent the code from raising an error
|
||||
# yet we return no value in the fingerprint hash.
|
||||
return None
|
||||
elif backend == 'cryptography':
|
||||
publickey = privatekey.public_key().public_bytes(
|
||||
serialization.Encoding.DER,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
|
||||
return get_fingerprint_of_bytes(publickey)
|
||||
|
||||
|
||||
def load_privatekey(path, passphrase=None, check_passphrase=True, content=None, backend='pyopenssl'):
|
||||
"""Load the specified OpenSSL private key.
|
||||
|
||||
The content can also be specified via content; in that case,
|
||||
this function will not load the key from disk.
|
||||
"""
|
||||
|
||||
try:
|
||||
if content is None:
|
||||
with open(path, 'rb') as b_priv_key_fh:
|
||||
priv_key_detail = b_priv_key_fh.read()
|
||||
else:
|
||||
priv_key_detail = content
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
|
||||
# First try: try to load with real passphrase (resp. empty string)
|
||||
# Will work if this is the correct passphrase, or the key is not
|
||||
# password-protected.
|
||||
try:
|
||||
result = crypto.load_privatekey(crypto.FILETYPE_PEM,
|
||||
priv_key_detail,
|
||||
to_bytes(passphrase or ''))
|
||||
except crypto.Error as e:
|
||||
if len(e.args) > 0 and len(e.args[0]) > 0:
|
||||
if e.args[0][0][2] in ('bad decrypt', 'bad password read'):
|
||||
# This happens in case we have the wrong passphrase.
|
||||
if passphrase is not None:
|
||||
raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key!')
|
||||
else:
|
||||
raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!')
|
||||
raise OpenSSLObjectError('Error while deserializing key: {0}'.format(e))
|
||||
if check_passphrase:
|
||||
# Next we want to make sure that the key is actually protected by
|
||||
# a passphrase (in case we did try the empty string before, make
|
||||
# sure that the key is not protected by the empty string)
|
||||
try:
|
||||
crypto.load_privatekey(crypto.FILETYPE_PEM,
|
||||
priv_key_detail,
|
||||
to_bytes('y' if passphrase == 'x' else 'x'))
|
||||
if passphrase is not None:
|
||||
# Since we can load the key without an exception, the
|
||||
# key isn't password-protected
|
||||
raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!')
|
||||
except crypto.Error as e:
|
||||
if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0:
|
||||
if e.args[0][0][2] in ('bad decrypt', 'bad password read'):
|
||||
# The key is obviously protected by the empty string.
|
||||
# Don't do this at home (if it's possible at all)...
|
||||
raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!')
|
||||
elif backend == 'cryptography':
|
||||
try:
|
||||
result = load_pem_private_key(priv_key_detail,
|
||||
None if passphrase is None else to_bytes(passphrase),
|
||||
cryptography_backend())
|
||||
except TypeError:
|
||||
raise OpenSSLBadPassphraseError('Wrong or empty passphrase provided for private key')
|
||||
except ValueError:
|
||||
raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key')
|
||||
|
||||
return result
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
|
||||
|
||||
def load_certificate(path, content=None, backend='pyopenssl'):
|
||||
"""Load the specified certificate."""
|
||||
|
||||
try:
|
||||
if content is None:
|
||||
with open(path, 'rb') as cert_fh:
|
||||
cert_content = cert_fh.read()
|
||||
else:
|
||||
cert_content = content
|
||||
if backend == 'pyopenssl':
|
||||
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content)
|
||||
elif backend == 'cryptography':
|
||||
return x509.load_pem_x509_certificate(cert_content, cryptography_backend())
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
|
||||
|
||||
def load_certificate_request(path, content=None, backend='pyopenssl'):
|
||||
"""Load the specified certificate signing request."""
|
||||
try:
|
||||
if content is None:
|
||||
with open(path, 'rb') as csr_fh:
|
||||
csr_content = csr_fh.read()
|
||||
else:
|
||||
csr_content = content
|
||||
except (IOError, OSError) as exc:
|
||||
raise OpenSSLObjectError(exc)
|
||||
if backend == 'pyopenssl':
|
||||
return crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_content)
|
||||
elif backend == 'cryptography':
|
||||
return x509.load_pem_x509_csr(csr_content, cryptography_backend())
|
||||
|
||||
|
||||
def parse_name_field(input_dict):
|
||||
"""Take a dict with key: value or key: list_of_values mappings and return a list of tuples"""
|
||||
|
||||
result = []
|
||||
for key in input_dict:
|
||||
if isinstance(input_dict[key], list):
|
||||
for entry in input_dict[key]:
|
||||
result.append((key, entry))
|
||||
else:
|
||||
result.append((key, input_dict[key]))
|
||||
return result
|
||||
|
||||
|
||||
def convert_relative_to_datetime(relative_time_string):
|
||||
"""Get a datetime.datetime or None from a string in the time format described in sshd_config(5)"""
|
||||
|
||||
parsed_result = re.match(
|
||||
r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$",
|
||||
relative_time_string)
|
||||
|
||||
if parsed_result is None or len(relative_time_string) == 1:
|
||||
# not matched or only a single "+" or "-"
|
||||
return None
|
||||
|
||||
offset = datetime.timedelta(0)
|
||||
if parsed_result.group("weeks") is not None:
|
||||
offset += datetime.timedelta(weeks=int(parsed_result.group("weeks")))
|
||||
if parsed_result.group("days") is not None:
|
||||
offset += datetime.timedelta(days=int(parsed_result.group("days")))
|
||||
if parsed_result.group("hours") is not None:
|
||||
offset += datetime.timedelta(hours=int(parsed_result.group("hours")))
|
||||
if parsed_result.group("minutes") is not None:
|
||||
offset += datetime.timedelta(
|
||||
minutes=int(parsed_result.group("minutes")))
|
||||
if parsed_result.group("seconds") is not None:
|
||||
offset += datetime.timedelta(
|
||||
seconds=int(parsed_result.group("seconds")))
|
||||
|
||||
if parsed_result.group("prefix") == "+":
|
||||
return datetime.datetime.utcnow() + offset
|
||||
else:
|
||||
return datetime.datetime.utcnow() - offset
|
||||
|
||||
|
||||
def get_relative_time_option(input_string, input_name, backend='cryptography'):
|
||||
"""Return an absolute timespec if a relative timespec or an ASN1 formatted
|
||||
string is provided.
|
||||
|
||||
The return value will be a datetime object for the cryptography backend,
|
||||
and a ASN1 formatted string for the pyopenssl backend."""
|
||||
result = to_native(input_string)
|
||||
if result is None:
|
||||
raise OpenSSLObjectError(
|
||||
'The timespec "%s" for %s is not valid' %
|
||||
input_string, input_name)
|
||||
# Relative time
|
||||
if result.startswith("+") or result.startswith("-"):
|
||||
result_datetime = convert_relative_to_datetime(result)
|
||||
if backend == 'pyopenssl':
|
||||
return result_datetime.strftime("%Y%m%d%H%M%SZ")
|
||||
elif backend == 'cryptography':
|
||||
return result_datetime
|
||||
# Absolute time
|
||||
if backend == 'pyopenssl':
|
||||
return input_string
|
||||
elif backend == 'cryptography':
|
||||
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']:
|
||||
try:
|
||||
return datetime.datetime.strptime(result, date_fmt)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
raise OpenSSLObjectError(
|
||||
'The time spec "%s" for %s is invalid' %
|
||||
(input_string, input_name)
|
||||
)
|
||||
|
||||
|
||||
def select_message_digest(digest_string):
|
||||
digest = None
|
||||
if digest_string == 'sha256':
|
||||
digest = hashes.SHA256()
|
||||
elif digest_string == 'sha384':
|
||||
digest = hashes.SHA384()
|
||||
elif digest_string == 'sha512':
|
||||
digest = hashes.SHA512()
|
||||
elif digest_string == 'sha1':
|
||||
digest = hashes.SHA1()
|
||||
elif digest_string == 'md5':
|
||||
digest = hashes.MD5()
|
||||
return digest
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class OpenSSLObject(object):
|
||||
|
||||
def __init__(self, path, state, force, check_mode):
|
||||
self.path = path
|
||||
self.state = state
|
||||
self.force = force
|
||||
self.name = os.path.basename(path)
|
||||
self.changed = False
|
||||
self.check_mode = check_mode
|
||||
|
||||
def check(self, module, perms_required=True):
|
||||
"""Ensure the resource is in its desired state."""
|
||||
|
||||
def _check_state():
|
||||
return os.path.exists(self.path)
|
||||
|
||||
def _check_perms(module):
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
return not module.set_fs_attributes_if_different(file_args, False)
|
||||
|
||||
if not perms_required:
|
||||
return _check_state()
|
||||
|
||||
return _check_state() and _check_perms(module)
|
||||
|
||||
@abc.abstractmethod
|
||||
def dump(self):
|
||||
"""Serialize the object into a dictionary."""
|
||||
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate(self):
|
||||
"""Generate the resource."""
|
||||
|
||||
pass
|
||||
|
||||
def remove(self, module):
|
||||
"""Remove the resource from the filesystem."""
|
||||
|
||||
try:
|
||||
os.remove(self.path)
|
||||
self.changed = True
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.ENOENT:
|
||||
raise OpenSSLObjectError(exc)
|
||||
else:
|
||||
pass
|
||||
@@ -1,101 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import errno
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
||||
def load_file_if_exists(path, module=None, ignore_errors=False):
|
||||
'''
|
||||
Load the file as a bytes string. If the file does not exist, ``None`` is returned.
|
||||
|
||||
If ``ignore_errors`` is ``True``, will ignore errors. Otherwise, errors are
|
||||
raised as exceptions if ``module`` is not specified, and result in ``module.fail_json``
|
||||
being called when ``module`` is specified.
|
||||
'''
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
return f.read()
|
||||
except EnvironmentError as exc:
|
||||
if exc.errno == errno.ENOENT:
|
||||
return None
|
||||
if ignore_errors:
|
||||
return None
|
||||
if module is None:
|
||||
raise
|
||||
module.fail_json('Error while loading {0} - {1}'.format(path, str(exc)))
|
||||
except Exception as exc:
|
||||
if ignore_errors:
|
||||
return None
|
||||
if module is None:
|
||||
raise
|
||||
module.fail_json('Error while loading {0} - {1}'.format(path, str(exc)))
|
||||
|
||||
|
||||
def write_file(module, content, default_mode=None, path=None):
|
||||
'''
|
||||
Writes content into destination file as securely as possible.
|
||||
Uses file arguments from module.
|
||||
'''
|
||||
# Find out parameters for file
|
||||
try:
|
||||
file_args = module.load_file_common_arguments(module.params, path=path)
|
||||
except TypeError:
|
||||
# The path argument is only supported in Ansible 2.10+. Fall back to
|
||||
# pre-2.10 behavior of module_utils/crypto.py for older Ansible versions.
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
if path is not None:
|
||||
file_args['path'] = path
|
||||
if file_args['mode'] is None:
|
||||
file_args['mode'] = default_mode
|
||||
# Create tempfile name
|
||||
tmp_fd, tmp_name = tempfile.mkstemp(prefix=b'.ansible_tmp')
|
||||
try:
|
||||
os.close(tmp_fd)
|
||||
except Exception:
|
||||
pass
|
||||
module.add_cleanup_file(tmp_name) # if we fail, let Ansible try to remove the file
|
||||
try:
|
||||
try:
|
||||
# Create tempfile
|
||||
file = os.open(tmp_name, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
os.write(file, content)
|
||||
os.close(file)
|
||||
except Exception as e:
|
||||
try:
|
||||
os.remove(tmp_name)
|
||||
except Exception:
|
||||
pass
|
||||
module.fail_json(msg='Error while writing result into temporary file: {0}'.format(e))
|
||||
# Update destination to wanted permissions
|
||||
if os.path.exists(file_args['path']):
|
||||
module.set_fs_attributes_if_different(file_args, False)
|
||||
# Move tempfile to final destination
|
||||
module.atomic_move(tmp_name, file_args['path'])
|
||||
# Try to update permissions again
|
||||
module.set_fs_attributes_if_different(file_args, False)
|
||||
except Exception as e:
|
||||
try:
|
||||
os.remove(tmp_name)
|
||||
except Exception:
|
||||
pass
|
||||
module.fail_json(msg='Error while writing result: {0}'.format(e))
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: acme_account
|
||||
@@ -19,19 +24,19 @@ description:
|
||||
such as L(Let's Encrypt,https://letsencrypt.org/)."
|
||||
- "This module only works with the ACME v2 protocol."
|
||||
notes:
|
||||
- "The M(community.crypto.acme_certificate) module also allows to do basic account management.
|
||||
- "The M(acme_certificate) module also allows to do basic account management.
|
||||
When using both modules, it is recommended to disable account management
|
||||
for M(community.crypto.acme_certificate). For that, use the C(modify_account) option of
|
||||
M(community.crypto.acme_certificate)."
|
||||
for M(acme_certificate). For that, use the C(modify_account) option of
|
||||
M(acme_certificate)."
|
||||
seealso:
|
||||
- name: Automatic Certificate Management Environment (ACME)
|
||||
description: The specification of the ACME protocol (RFC 8555).
|
||||
link: https://tools.ietf.org/html/rfc8555
|
||||
- module: community.crypto.acme_account_info
|
||||
- module: acme_account_info
|
||||
description: Retrieves facts about an ACME account.
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: openssl_privatekey
|
||||
description: Can be used to create a private account key.
|
||||
- module: community.crypto.acme_inspect
|
||||
- module: acme_inspect
|
||||
description: Allows to debug problems.
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.acme
|
||||
@@ -86,38 +91,11 @@ options:
|
||||
- "Mutually exclusive with C(new_account_key_src)."
|
||||
- "Required if C(new_account_key_src) is not used and state is C(changed_key)."
|
||||
type: str
|
||||
external_account_binding:
|
||||
description:
|
||||
- Allows to provide external account binding data during account creation.
|
||||
- This is used by CAs like Sectigo to bind a new ACME account to an existing CA-specific
|
||||
account, to be able to properly identify a customer.
|
||||
- Only used when creating a new account. Can not be specified for ACME v1.
|
||||
type: dict
|
||||
suboptions:
|
||||
kid:
|
||||
description:
|
||||
- The key identifier provided by the CA.
|
||||
type: str
|
||||
required: true
|
||||
alg:
|
||||
description:
|
||||
- The MAC algorithm provided by the CA.
|
||||
- If not specified by the CA, this is probably C(HS256).
|
||||
type: str
|
||||
required: true
|
||||
choices: [ HS256, HS384, HS512 ]
|
||||
key:
|
||||
description:
|
||||
- Base64 URL encoded value of the MAC key provided by the CA.
|
||||
- Padding (C(=) symbols at the end) can be omitted.
|
||||
type: str
|
||||
required: true
|
||||
version_added: 1.1.0
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Make sure account exists and has given contacts. We agree to TOS.
|
||||
community.crypto.acme_account:
|
||||
acme_account:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
state: present
|
||||
terms_agreed: yes
|
||||
@@ -126,7 +104,7 @@ EXAMPLES = '''
|
||||
- mailto:myself@example.org
|
||||
|
||||
- name: Make sure account has given email address. Don't create account if it doesn't exist
|
||||
community.crypto.acme_account:
|
||||
acme_account:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
state: present
|
||||
allow_creation: no
|
||||
@@ -134,13 +112,13 @@ EXAMPLES = '''
|
||||
- mailto:me@example.com
|
||||
|
||||
- name: Change account's key to the one stored in the variable new_account_key
|
||||
community.crypto.acme_account:
|
||||
acme_account:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
new_account_key_content: '{{ new_account_key }}'
|
||||
state: changed_key
|
||||
|
||||
- name: Delete account (we have to use the new key)
|
||||
community.crypto.acme_account:
|
||||
acme_account:
|
||||
account_key_content: '{{ new_account_key }}'
|
||||
state: absent
|
||||
'''
|
||||
@@ -152,10 +130,6 @@ account_uri:
|
||||
type: str
|
||||
'''
|
||||
|
||||
import base64
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
ModuleFailException,
|
||||
ACMEAccount,
|
||||
@@ -163,6 +137,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
get_default_argspec,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = get_default_argspec()
|
||||
@@ -173,11 +149,6 @@ def main():
|
||||
contact=dict(type='list', elements='str', default=[]),
|
||||
new_account_key_src=dict(type='path'),
|
||||
new_account_key_content=dict(type='str', no_log=True),
|
||||
external_account_binding=dict(type='dict', options=dict(
|
||||
kid=dict(type='str', required=True),
|
||||
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
|
||||
key=dict(type='str', required=True, no_log=True),
|
||||
))
|
||||
))
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
@@ -197,18 +168,6 @@ def main():
|
||||
)
|
||||
handle_standard_module_arguments(module, needs_acme_v2=True)
|
||||
|
||||
if module.params['external_account_binding']:
|
||||
# Make sure padding is there
|
||||
key = module.params['external_account_binding']['key']
|
||||
if len(key) % 4 != 0:
|
||||
key = key + ('=' * (4 - (len(key) % 4)))
|
||||
# Make sure key is Base64 encoded
|
||||
try:
|
||||
base64.urlsafe_b64decode(key)
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Key for external_account_binding must be Base64 URL encoded (%s)' % e)
|
||||
module.params['external_account_binding']['key'] = key
|
||||
|
||||
try:
|
||||
account = ACMEAccount(module)
|
||||
changed = False
|
||||
@@ -235,14 +194,13 @@ def main():
|
||||
changed = True
|
||||
elif state == 'present':
|
||||
allow_creation = module.params.get('allow_creation')
|
||||
# Make sure contact is a list of strings (unfortunately, Ansible doesn't do that for us)
|
||||
contact = [str(v) for v in module.params.get('contact')]
|
||||
terms_agreed = module.params.get('terms_agreed')
|
||||
external_account_binding = module.params.get('external_account_binding')
|
||||
created, account_data = account.setup_account(
|
||||
contact,
|
||||
terms_agreed=terms_agreed,
|
||||
allow_creation=allow_creation,
|
||||
external_account_binding=external_account_binding,
|
||||
)
|
||||
if account_data is None:
|
||||
raise ModuleFailException(msg='Account does not exist or is deactivated.')
|
||||
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: acme_account_info
|
||||
@@ -19,8 +24,7 @@ description:
|
||||
such as L(Let's Encrypt,https://letsencrypt.org/)."
|
||||
- "This module only works with the ACME v2 protocol."
|
||||
notes:
|
||||
- "The M(community.crypto.acme_account) module allows to modify, create and delete ACME
|
||||
accounts."
|
||||
- "The M(acme_account) module allows to modify, create and delete ACME accounts."
|
||||
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage
|
||||
did not change."
|
||||
options:
|
||||
@@ -38,7 +42,7 @@ options:
|
||||
- object_list
|
||||
default: ignore
|
||||
seealso:
|
||||
- module: community.crypto.acme_account
|
||||
- module: acme_account
|
||||
description: Allows to create, modify or delete an ACME account.
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.acme
|
||||
@@ -47,7 +51,7 @@ extends_documentation_fragment:
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Check whether an account with the given account key exists
|
||||
community.crypto.acme_account_info:
|
||||
acme_account_info:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
register: account_data
|
||||
- name: Verify that account exists
|
||||
@@ -192,8 +196,6 @@ orders:
|
||||
returned: when certificate was issued
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
ModuleFailException,
|
||||
ACMEAccount,
|
||||
@@ -202,6 +204,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
get_default_argspec,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def get_orders_list(module, account, orders_url):
|
||||
'''
|
||||
@@ -255,9 +259,8 @@ def main():
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
if module._name in ('acme_account_facts', 'community.crypto.acme_account_facts'):
|
||||
module.deprecate("The 'acme_account_facts' module has been renamed to 'acme_account_info'",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
if module._name == 'acme_account_facts':
|
||||
module.deprecate("The 'acme_account_facts' module has been renamed to 'acme_account_info'", version='2.12')
|
||||
handle_standard_module_arguments(module, needs_acme_v2=True)
|
||||
|
||||
try:
|
||||
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: acme_certificate
|
||||
@@ -38,9 +43,9 @@ description:
|
||||
notes:
|
||||
- "At least one of C(dest) and C(fullchain_dest) must be specified."
|
||||
- "This module includes basic account management functionality.
|
||||
If you want to have more control over your ACME account, use the
|
||||
M(community.crypto.acme_account) module and disable account management
|
||||
for this module using the C(modify_account) option."
|
||||
If you want to have more control over your ACME account, use the M(acme_account)
|
||||
module and disable account management for this module using the C(modify_account)
|
||||
option."
|
||||
- "This module was called C(letsencrypt) before Ansible 2.6. The usage
|
||||
did not change."
|
||||
seealso:
|
||||
@@ -58,19 +63,19 @@ seealso:
|
||||
- name: ACME TLS ALPN Challenge Extension
|
||||
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
|
||||
link: https://www.rfc-editor.org/rfc/rfc8737.html-05
|
||||
- module: community.crypto.acme_challenge_cert_helper
|
||||
- module: acme_challenge_cert_helper
|
||||
description: Helps preparing C(tls-alpn-01) challenges.
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: openssl_privatekey
|
||||
description: Can be used to create private keys (both for certificates and accounts).
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: openssl_csr
|
||||
description: Can be used to create a Certificate Signing Request (CSR).
|
||||
- module: community.crypto.certificate_complete_chain
|
||||
- module: certificate_complete_chain
|
||||
description: Allows to find the root certificate for the returned fullchain.
|
||||
- module: community.crypto.acme_certificate_revoke
|
||||
- module: acme_certificate_revoke
|
||||
description: Allows to revoke certificates.
|
||||
- module: community.crypto.acme_account
|
||||
- module: acme_account
|
||||
description: Allows to create, modify or delete an ACME account.
|
||||
- module: community.crypto.acme_inspect
|
||||
- module: acme_inspect
|
||||
description: Allows to debug problems.
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.acme
|
||||
@@ -81,7 +86,7 @@ options:
|
||||
- "The email address associated with this account."
|
||||
- "It will be used for certificate expiration warnings."
|
||||
- "Note that when C(modify_account) is not set to C(no) and you also
|
||||
used the M(community.crypto.acme_account) module to specify more than one contact
|
||||
used the M(acme_account) module to specify more than one contact
|
||||
for your account, this module will update your account and restrict
|
||||
it to the (at most one) contact email address specified here."
|
||||
type: str
|
||||
@@ -103,9 +108,9 @@ options:
|
||||
description:
|
||||
- "Boolean indicating whether the module should create the account if
|
||||
necessary, and update its contact data."
|
||||
- "Set to C(no) if you want to use the M(community.crypto.acme_account) module to manage
|
||||
- "Set to C(no) if you want to use the M(acme_account) module to manage
|
||||
your account instead, and to avoid accidental creation of a new account
|
||||
using an old key if you changed the account key with M(community.crypto.acme_account)."
|
||||
using an old key if you changed the account key with M(acme_account)."
|
||||
- "If set to C(no), C(terms_agreed) and C(account_email) are ignored."
|
||||
type: bool
|
||||
default: yes
|
||||
@@ -212,21 +217,17 @@ options:
|
||||
to the same certificate in the chain."
|
||||
- "This option can only be used with the C(cryptography) backend."
|
||||
type: list
|
||||
elements: dict
|
||||
version_added: '1.0.0'
|
||||
suboptions:
|
||||
test_certificates:
|
||||
description:
|
||||
- "Determines which certificates in the chain will be tested."
|
||||
- "I(all) tests all certificates in the chain (excluding the leaf, which is
|
||||
identical in all chains)."
|
||||
- "I(first) only tests the first certificate in the chain, i.e. the one which
|
||||
signed the leaf."
|
||||
- "I(last) only tests the last certificate in the chain, i.e. the one furthest
|
||||
away from the leaf. Its issuer is the root certificate of this chain."
|
||||
type: str
|
||||
default: all
|
||||
choices: [first, last, all]
|
||||
choices: [last, all]
|
||||
issuer:
|
||||
description:
|
||||
- "Allows to specify parts of the issuer of a certificate in the chain must
|
||||
@@ -261,7 +262,7 @@ EXAMPLES = r'''
|
||||
### Example with HTTP challenge ###
|
||||
|
||||
- name: Create a challenge for sample.com using a account key from a variable.
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_content: "{{ account_private_key }}"
|
||||
csr: /etc/pki/cert/csr/sample.com.csr
|
||||
dest: /etc/httpd/ssl/sample.com.crt
|
||||
@@ -269,7 +270,7 @@ EXAMPLES = r'''
|
||||
|
||||
# Alternative first step:
|
||||
- name: Create a challenge for sample.com using a account key from hashi vault.
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_content: "{{ lookup('hashi_vault', 'secret=secret/account_private_key:value') }}"
|
||||
csr: /etc/pki/cert/csr/sample.com.csr
|
||||
fullchain_dest: /etc/httpd/ssl/sample.com-fullchain.crt
|
||||
@@ -277,7 +278,7 @@ EXAMPLES = r'''
|
||||
|
||||
# Alternative first step:
|
||||
- name: Create a challenge for sample.com using a account key file.
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
csr: /etc/pki/cert/csr/sample.com.csr
|
||||
dest: /etc/httpd/ssl/sample.com.crt
|
||||
@@ -290,18 +291,10 @@ EXAMPLES = r'''
|
||||
# - copy:
|
||||
# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }}
|
||||
# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}"
|
||||
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data']
|
||||
#
|
||||
# Alternative way:
|
||||
#
|
||||
# - copy:
|
||||
# dest: /var/www/{{ item.key }}/{{ item.value['http-01']['resource'] }}
|
||||
# content: "{{ item.value['http-01']['resource_value'] }}"
|
||||
# loop: "{{ sample_com_challenge.challenge_data | dictsort }}"
|
||||
# when: sample_com_challenge is changed
|
||||
# when: sample_com_challenge is changed
|
||||
|
||||
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
csr: /etc/pki/cert/csr/sample.com.csr
|
||||
dest: /etc/httpd/ssl/sample.com.crt
|
||||
@@ -312,7 +305,7 @@ EXAMPLES = r'''
|
||||
### Example with DNS challenge against production ACME server ###
|
||||
|
||||
- name: Create a challenge for sample.com using a account key file.
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
account_email: myself@sample.com
|
||||
src: /etc/pki/cert/csr/sample.com.csr
|
||||
@@ -326,7 +319,7 @@ EXAMPLES = r'''
|
||||
# perform the necessary steps to fulfill the challenge
|
||||
# for example:
|
||||
#
|
||||
# - community.aws.route53:
|
||||
# - route53:
|
||||
# zone: sample.com
|
||||
# record: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].record }}"
|
||||
# type: TXT
|
||||
@@ -335,11 +328,11 @@ EXAMPLES = r'''
|
||||
# wait: yes
|
||||
# # Note: route53 requires TXT entries to be enclosed in quotes
|
||||
# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}"
|
||||
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data
|
||||
# when: sample_com_challenge is changed
|
||||
#
|
||||
# Alternative way:
|
||||
#
|
||||
# - community.aws.route53:
|
||||
# - route53:
|
||||
# zone: sample.com
|
||||
# record: "{{ item.key }}"
|
||||
# type: TXT
|
||||
@@ -353,7 +346,7 @@ EXAMPLES = r'''
|
||||
# when: sample_com_challenge is changed
|
||||
|
||||
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
account_email: myself@sample.com
|
||||
src: /etc/pki/cert/csr/sample.com.csr
|
||||
@@ -368,7 +361,7 @@ EXAMPLES = r'''
|
||||
|
||||
# Alternative second step:
|
||||
- name: Let the challenge be validated and retrieve the cert and intermediate certificate
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
account_email: myself@sample.com
|
||||
src: /etc/pki/cert/csr/sample.com.csr
|
||||
@@ -419,6 +412,7 @@ challenge_data:
|
||||
returned: changed and challenge is C(tls-alpn-01)
|
||||
type: str
|
||||
sample: DNS:example.com
|
||||
version_added: "2.8"
|
||||
resource_value:
|
||||
description:
|
||||
- The value the resource has to produce for the validation.
|
||||
@@ -437,12 +431,14 @@ challenge_data:
|
||||
returned: changed and challenge is C(dns-01)
|
||||
type: str
|
||||
sample: _acme-challenge.example.com
|
||||
version_added: "2.5"
|
||||
challenge_data_dns:
|
||||
description:
|
||||
- List of TXT values per DNS record, in case challenge is C(dns-01).
|
||||
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
|
||||
returned: changed
|
||||
type: dict
|
||||
version_added: "2.5"
|
||||
authorizations:
|
||||
description:
|
||||
- ACME authorization data.
|
||||
@@ -454,14 +450,17 @@ order_uri:
|
||||
description: ACME order URI.
|
||||
returned: changed
|
||||
type: str
|
||||
version_added: "2.5"
|
||||
finalization_uri:
|
||||
description: ACME finalization URI.
|
||||
returned: changed
|
||||
type: str
|
||||
version_added: "2.5"
|
||||
account_uri:
|
||||
description: ACME account URI.
|
||||
returned: changed
|
||||
type: str
|
||||
version_added: "2.5"
|
||||
all_chains:
|
||||
description:
|
||||
- When I(retrieve_all_alternates) is set to C(yes), the module will query the ACME server
|
||||
@@ -490,28 +489,6 @@ all_chains:
|
||||
returned: always
|
||||
'''
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
parse_name_field,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_name_to_oid,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
ModuleFailException,
|
||||
write_file,
|
||||
@@ -526,7 +503,21 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
process_links,
|
||||
get_default_argspec,
|
||||
)
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
import time
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
|
||||
try:
|
||||
import cryptography
|
||||
@@ -1005,15 +996,13 @@ class ACMEClient(object):
|
||||
'''
|
||||
if criterium['test_certificates'] == 'last':
|
||||
chain = chain[-1:]
|
||||
elif criterium['test_certificates'] == 'first':
|
||||
chain = chain[:1]
|
||||
for cert in chain:
|
||||
try:
|
||||
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography.hazmat.backends.default_backend())
|
||||
matches = True
|
||||
if criterium['subject']:
|
||||
for k, v in parse_name_field(criterium['subject']):
|
||||
oid = cryptography_name_to_oid(k)
|
||||
for k, v in crypto_utils.parse_name_field(criterium['subject']):
|
||||
oid = crypto_utils.cryptography_name_to_oid(k)
|
||||
value = to_native(v)
|
||||
found = False
|
||||
for attribute in x509.subject:
|
||||
@@ -1024,8 +1013,8 @@ class ACMEClient(object):
|
||||
matches = False
|
||||
break
|
||||
if criterium['issuer']:
|
||||
for k, v in parse_name_field(criterium['issuer']):
|
||||
oid = cryptography_name_to_oid(k)
|
||||
for k, v in crypto_utils.parse_name_field(criterium['issuer']):
|
||||
oid = crypto_utils.cryptography_name_to_oid(k)
|
||||
value = to_native(v)
|
||||
found = False
|
||||
for attribute in x509.issuer:
|
||||
@@ -1187,7 +1176,7 @@ def main():
|
||||
force=dict(type='bool', default=False),
|
||||
retrieve_all_alternates=dict(type='bool', default=False),
|
||||
select_chain=dict(type='list', elements='dict', options=dict(
|
||||
test_certificates=dict(type='str', default='all', choices=['first', 'last', 'all']),
|
||||
test_certificates=dict(type='str', default='all', choices=['last', 'all']),
|
||||
issuer=dict(type='dict'),
|
||||
subject=dict(type='dict'),
|
||||
subject_key_identifier=dict(type='str'),
|
||||
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: acme_certificate_revoke
|
||||
@@ -33,7 +38,7 @@ seealso:
|
||||
- name: Automatic Certificate Management Environment (ACME)
|
||||
description: The specification of the ACME protocol (RFC 8555).
|
||||
link: https://tools.ietf.org/html/rfc8555
|
||||
- module: community.crypto.acme_inspect
|
||||
- module: acme_inspect
|
||||
description: Allows to debug problems.
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.acme
|
||||
@@ -104,12 +109,12 @@ options:
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Revoke certificate with account key
|
||||
community.crypto.acme_certificate_revoke:
|
||||
acme_certificate_revoke:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
certificate: /etc/httpd/ssl/sample.com.crt
|
||||
|
||||
- name: Revoke certificate with certificate's private key
|
||||
community.crypto.acme_certificate_revoke:
|
||||
acme_certificate_revoke:
|
||||
private_key_src: /etc/httpd/ssl/sample.com.key
|
||||
certificate: /etc/httpd/ssl/sample.com.crt
|
||||
'''
|
||||
@@ -117,8 +122,6 @@ EXAMPLES = '''
|
||||
RETURN = '''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
ModuleFailException,
|
||||
ACMEAccount,
|
||||
@@ -128,6 +131,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
get_default_argspec,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = get_default_argspec()
|
||||
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: acme_challenge_cert_helper
|
||||
@@ -15,7 +20,7 @@ author: "Felix Fontein (@felixfontein)"
|
||||
short_description: Prepare certificates required for ACME challenges such as C(tls-alpn-01)
|
||||
description:
|
||||
- "Prepares certificates for ACME challenges such as C(tls-alpn-01)."
|
||||
- "The raw data is provided by the M(community.crypto.acme_certificate) module, and needs to be
|
||||
- "The raw data is provided by the M(acme_certificate) module, and needs to be
|
||||
converted to a certificate to be used for challenge validation. This module
|
||||
provides a simple way to generate the required certificates."
|
||||
seealso:
|
||||
@@ -37,8 +42,7 @@ options:
|
||||
- tls-alpn-01
|
||||
challenge_data:
|
||||
description:
|
||||
- "The C(challenge_data) entry provided by M(community.crypto.acme_certificate) for the
|
||||
challenge."
|
||||
- "The C(challenge_data) entry provided by M(acme_certificate) for the challenge."
|
||||
type: dict
|
||||
required: yes
|
||||
private_key_src:
|
||||
@@ -56,7 +60,7 @@ options:
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Create challenges for a given CRT for sample.com
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
challenge: tls-alpn-01
|
||||
csr: /etc/pki/cert/csr/sample.com.csr
|
||||
@@ -64,7 +68,7 @@ EXAMPLES = '''
|
||||
register: sample_com_challenge
|
||||
|
||||
- name: Create certificates for challenges
|
||||
community.crypto.acme_challenge_cert_helper:
|
||||
acme_challenge_cert_helper:
|
||||
challenge: tls-alpn-01
|
||||
challenge_data: "{{ item.value['tls-alpn-01'] }}"
|
||||
private_key_src: /etc/pki/cert/key/sample.com.key
|
||||
@@ -88,7 +92,7 @@ EXAMPLES = '''
|
||||
loop: "{{ sample_com_challenge_certs.results }}"
|
||||
|
||||
- name: Create certificate for a given CSR for sample.com
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
challenge: tls-alpn-01
|
||||
csr: /etc/pki/cert/csr/sample.com.csr
|
||||
@@ -109,12 +113,14 @@ identifier_type:
|
||||
or C(ip)."
|
||||
returned: always
|
||||
type: str
|
||||
version_added: "2.8"
|
||||
identifier:
|
||||
description:
|
||||
- "The identifier for the actual resource. Will be a domain name if the
|
||||
type is C(dns), or an IP address if the type is C(ip)."
|
||||
returned: always
|
||||
type: str
|
||||
version_added: "2.8"
|
||||
challenge_certificate:
|
||||
description:
|
||||
- "The challenge certificate in PEM format."
|
||||
@@ -130,19 +136,19 @@ regular_certificate:
|
||||
type: str
|
||||
'''
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
ModuleFailException,
|
||||
read_file,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
import base64
|
||||
import datetime
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: acme_inspect
|
||||
@@ -18,14 +23,14 @@ description:
|
||||
L(ACME protocol,https://tools.ietf.org/html/rfc8555),
|
||||
which is supported by CAs such as L(Let's Encrypt,https://letsencrypt.org/)."
|
||||
- "This module can be used to debug failed certificate request attempts,
|
||||
for example when M(community.crypto.acme_certificate) fails or encounters a problem which
|
||||
for example when M(acme_certificate) fails or encounters a problem which
|
||||
you wish to investigate."
|
||||
- "The module can also be used to directly access features of an ACME servers
|
||||
which are not yet supported by the Ansible ACME modules."
|
||||
notes:
|
||||
- "The I(account_uri) option must be specified for properly authenticated
|
||||
ACME v2 requests (except a C(new-account) request)."
|
||||
- "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute
|
||||
- "Using the C(ansible) tool, M(acme_inspect) can be used to directly execute
|
||||
ACME requests without the need of writing a playbook. For example, the
|
||||
following command retrieves the ACME account with ID 1 from Let's Encrypt
|
||||
(assuming C(/path/to/key) is the correct private account key):
|
||||
@@ -80,14 +85,14 @@ options:
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get directory
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
method: directory-only
|
||||
register: directory
|
||||
|
||||
- name: Create an account
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -99,7 +104,7 @@ EXAMPLES = r'''
|
||||
# if creation was successful
|
||||
|
||||
- name: Get account information
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -108,7 +113,7 @@ EXAMPLES = r'''
|
||||
method: get
|
||||
|
||||
- name: Update account contacts
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -124,7 +129,7 @@ EXAMPLES = r'''
|
||||
- mailto:me@example.com
|
||||
|
||||
- name: Create certificate order
|
||||
community.crypto.acme_certificate:
|
||||
acme_certificate:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -138,7 +143,7 @@ EXAMPLES = r'''
|
||||
# the order URI.
|
||||
|
||||
- name: Get order information
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -148,7 +153,7 @@ EXAMPLES = r'''
|
||||
register: order
|
||||
|
||||
- name: Get first authz for order
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -158,7 +163,7 @@ EXAMPLES = r'''
|
||||
register: authz
|
||||
|
||||
- name: Get HTTP-01 challenge for authz
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -168,7 +173,7 @@ EXAMPLES = r'''
|
||||
register: http01challenge
|
||||
|
||||
- name: Activate HTTP-01 challenge manually
|
||||
community.crypto.acme_inspect:
|
||||
acme_inspect:
|
||||
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
acme_version: 2
|
||||
account_key_src: /etc/pki/cert/private/account.key
|
||||
@@ -240,11 +245,6 @@ output_json:
|
||||
- ...
|
||||
'''
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native, to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
ModuleFailException,
|
||||
ACMEAccount,
|
||||
@@ -252,6 +252,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||
get_default_argspec,
|
||||
)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
|
||||
import json
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = get_default_argspec()
|
||||
@@ -299,8 +304,7 @@ def main():
|
||||
))
|
||||
# See if we can parse the result as JSON
|
||||
try:
|
||||
# to_text() is needed only for Python 3.5 (and potentially 3.0 to 3.4 as well)
|
||||
result['output_json'] = json.loads(to_text(data))
|
||||
result['output_json'] = json.loads(data)
|
||||
except Exception as dummy:
|
||||
pass
|
||||
# Fail if error was returned
|
||||
|
||||
@@ -8,6 +8,11 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: certificate_complete_chain
|
||||
@@ -18,7 +23,7 @@ description:
|
||||
intermediate certificates from a given set of certificates, until it finds a root
|
||||
certificate in another given set of certificates."
|
||||
- "This can for example be used to find the root certificate for a certificate chain
|
||||
returned by M(community.crypto.acme_certificate)."
|
||||
returned by M(acme_certificate)."
|
||||
- "Note that this module does I(not) check for validity of the chains. It only
|
||||
checks that issuer and subject match, and that the signature is correct. It
|
||||
ignores validity dates and key usage completely. If you need to verify that a
|
||||
@@ -65,7 +70,7 @@ EXAMPLES = '''
|
||||
# Given a leaf certificate for www.ansible.com and one or more intermediate
|
||||
# certificates, finds the associated root certificate.
|
||||
- name: Find root certificate
|
||||
community.crypto.certificate_complete_chain:
|
||||
certificate_complete_chain:
|
||||
input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com-fullchain.pem') }}"
|
||||
root_certificates:
|
||||
- /etc/ca-certificates/
|
||||
@@ -78,7 +83,7 @@ EXAMPLES = '''
|
||||
# Given a leaf certificate for www.ansible.com, and a list of intermediate
|
||||
# certificates, finds the associated root certificate.
|
||||
- name: Find root certificate
|
||||
community.crypto.certificate_complete_chain:
|
||||
certificate_complete_chain:
|
||||
input_chain: "{{ lookup('file', '/etc/ssl/csr/www.ansible.com.pem') }}"
|
||||
intermediate_certificates:
|
||||
- /etc/ssl/csr/www.ansible.com-chain.pem
|
||||
|
||||
@@ -8,6 +8,10 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ecs_certificate
|
||||
@@ -332,9 +336,9 @@ options:
|
||||
type: str
|
||||
choices: [ P1Y, P2Y, P3Y ]
|
||||
seealso:
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: openssl_privatekey
|
||||
description: Can be used to create private keys (both for certificates and accounts).
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: openssl_csr
|
||||
description: Can be used to create a Certificate Signing Request (CSR).
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.ecs_credential
|
||||
@@ -345,7 +349,7 @@ EXAMPLES = r'''
|
||||
- name: Request a new certificate from Entrust with bare minimum parameters.
|
||||
Will request a new certificate if current one is valid but within 30
|
||||
days of expiry. If replacing an existing file in path, will back it up.
|
||||
community.crypto.ecs_certificate:
|
||||
ecs_certificate:
|
||||
backup: true
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
full_chain_path: /etc/ssl/crt/ansible.com.chain.crt
|
||||
@@ -363,7 +367,7 @@ EXAMPLES = r'''
|
||||
of type EV_SSL. Otherwise, if there is an Entrust managed certificate
|
||||
in path and it is within 63 days of expiration, request a renew of that
|
||||
certificate.
|
||||
community.crypto.ecs_certificate:
|
||||
ecs_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
csr: /etc/ssl/csr/ansible.com.csr
|
||||
cert_type: EV_SSL
|
||||
@@ -383,7 +387,7 @@ EXAMPLES = r'''
|
||||
certificate is within 79 days of expiration, request a renew of that
|
||||
certificate and save it in path. This can be used to "migrate" a
|
||||
certificate to be Ansible managed.
|
||||
community.crypto.ecs_certificate:
|
||||
ecs_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
csr: /etc/ssl/csr/ansible.com.csr
|
||||
tracking_id: 2378915
|
||||
@@ -395,7 +399,7 @@ EXAMPLES = r'''
|
||||
entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key
|
||||
|
||||
- name: Force a reissue of the certificate specified by tracking_id.
|
||||
community.crypto.ecs_certificate:
|
||||
ecs_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
force: true
|
||||
tracking_id: 2378915
|
||||
@@ -409,7 +413,7 @@ EXAMPLES = r'''
|
||||
issued certificate will have it's Subject Distinguished Name use the
|
||||
organization details associated with that client, rather than what is
|
||||
in the CSR.
|
||||
community.crypto.ecs_certificate:
|
||||
ecs_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
csr: /etc/ssl/csr/ansible.com.csr
|
||||
client_id: 2
|
||||
@@ -423,7 +427,7 @@ EXAMPLES = r'''
|
||||
|
||||
- name: Request a new certificate with a number of CSR parameters overridden
|
||||
and tracking information
|
||||
community.crypto.ecs_certificate:
|
||||
ecs_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
full_chain_path: /etc/ssl/crt/ansible.com.chain.crt
|
||||
csr: /etc/ssl/csr/ansible.com.csr
|
||||
@@ -514,24 +518,17 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
|
||||
)
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
load_certificate,
|
||||
)
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
@@ -603,7 +600,7 @@ class EcsCertificate(object):
|
||||
self.ecs_client = None
|
||||
if self.path and os.path.exists(self.path):
|
||||
try:
|
||||
self.cert = load_certificate(self.path, backend='cryptography')
|
||||
self.cert = crypto_utils.load_certificate(self.path, backend='cryptography')
|
||||
except Exception as dummy:
|
||||
self.cert = None
|
||||
# Instantiate the ECS client and then try a no-op connection to verify credentials are valid
|
||||
@@ -775,20 +772,20 @@ class EcsCertificate(object):
|
||||
if self.request_type != 'validate_only':
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
|
||||
crypto_utils.write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
|
||||
if self.full_chain_path and self.cert_details.get('chainCerts'):
|
||||
if self.backup:
|
||||
self.backup_full_chain_file = module.backup_local(self.full_chain_path)
|
||||
chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n'
|
||||
write_file(module, to_bytes(chain_string), path=self.full_chain_path)
|
||||
crypto_utils.write_file(module, to_bytes(chain_string), path=self.full_chain_path)
|
||||
self.changed = True
|
||||
# If there is no certificate present in path but a tracking ID was specified, save it to disk
|
||||
elif not os.path.exists(self.path) and self.tracking_id:
|
||||
if not module.check_mode:
|
||||
write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
|
||||
crypto_utils.write_file(module, to_bytes(self.cert_details.get('endEntityCert')))
|
||||
if self.full_chain_path and self.cert_details.get('chainCerts'):
|
||||
chain_string = '\n'.join(self.cert_details.get('chainCerts')) + '\n'
|
||||
write_file(module, to_bytes(chain_string), path=self.full_chain_path)
|
||||
crypto_utils.write_file(module, to_bytes(chain_string), path=self.full_chain_path)
|
||||
self.changed = True
|
||||
|
||||
def dump(self):
|
||||
|
||||
@@ -8,12 +8,16 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: ecs_domain
|
||||
author:
|
||||
- Chris Trufan (@ctrufan)
|
||||
version_added: '1.0.0'
|
||||
short_description: Request validation of a domain with the Entrust Certificate Services (ECS) API
|
||||
description:
|
||||
- Request validation or re-validation of a domain with the Entrust Certificate Services (ECS) API.
|
||||
@@ -74,9 +78,9 @@ options:
|
||||
- Only allowed if C(verification_method=email)
|
||||
type: str
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
- module: openssl_certificate
|
||||
description: Can be used to request certificates from ECS, with C(provider=entrust).
|
||||
- module: community.crypto.ecs_certificate
|
||||
- module: ecs_certificate
|
||||
description: Can be used to request a Certificate from ECS using a verified domain.
|
||||
extends_documentation_fragment:
|
||||
- community.crypto.ecs_credential
|
||||
@@ -85,7 +89,7 @@ extends_documentation_fragment:
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Request domain validation using email validation for client ID of 2.
|
||||
community.crypto.ecs_domain:
|
||||
ecs_domain:
|
||||
domain_name: ansible.com
|
||||
client_id: 2
|
||||
verification_method: email
|
||||
@@ -97,7 +101,7 @@ EXAMPLES = r'''
|
||||
|
||||
- name: Request domain validation using DNS. If domain is already valid,
|
||||
request revalidation if expires within 90 days
|
||||
community.crypto.ecs_domain:
|
||||
ecs_domain:
|
||||
domain_name: ansible.com
|
||||
verification_method: dns
|
||||
entrust_api_user: apiusername
|
||||
@@ -107,7 +111,7 @@ EXAMPLES = r'''
|
||||
|
||||
- name: Request domain validation using web server validation, and revalidate
|
||||
if fewer than 60 days remaining of EV eligibility.
|
||||
community.crypto.ecs_domain:
|
||||
ecs_domain:
|
||||
domain_name: ansible.com
|
||||
verification_method: web_server
|
||||
entrust_api_user: apiusername
|
||||
@@ -116,7 +120,7 @@ EXAMPLES = r'''
|
||||
entrust_api_client_cert_key_path: /etc/ssl/entrust/ecs-client.key
|
||||
|
||||
- name: Request domain validation using manual validation.
|
||||
community.crypto.ecs_domain:
|
||||
ecs_domain:
|
||||
domain_name: ansible.com
|
||||
verification_method: manual
|
||||
entrust_api_user: apiusername
|
||||
@@ -198,12 +202,6 @@ ev_days_remaining:
|
||||
|
||||
'''
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
|
||||
ecs_client_argument_spec,
|
||||
ECSClient,
|
||||
@@ -211,6 +209,11 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import (
|
||||
SessionConfigurationException,
|
||||
)
|
||||
|
||||
import datetime
|
||||
import time
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def calculate_days_remaining(expiry_date):
|
||||
days_remaining = None
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
@@ -14,11 +17,10 @@ author: "John Westcott IV (@john-westcott-iv)"
|
||||
short_description: Get a certificate from a host:port
|
||||
description:
|
||||
- Makes a secure connection and returns information about the presented certificate
|
||||
- "The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL
|
||||
backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
|
||||
- Support SNI only with python >= 2.7
|
||||
backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
@@ -126,7 +128,7 @@ version:
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Get the cert from an RDP port
|
||||
community.crypto.get_certificate:
|
||||
get_certificate:
|
||||
host: "1.2.3.4"
|
||||
port: 3389
|
||||
delegate_to: localhost
|
||||
@@ -134,7 +136,7 @@ EXAMPLES = '''
|
||||
register: cert
|
||||
|
||||
- name: Get a cert from an https port
|
||||
community.crypto.get_certificate:
|
||||
get_certificate:
|
||||
host: "www.google.com"
|
||||
port: 443
|
||||
delegate_to: localhost
|
||||
@@ -148,24 +150,20 @@ EXAMPLES = '''
|
||||
expire_days: "{{ (( cert.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime('%Y-%m-%dT%H:%M:%SZ')) ).days }}"
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
from os.path import isfile
|
||||
from socket import setdefaulttimeout, socket
|
||||
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_OPTIONAL
|
||||
|
||||
import atexit
|
||||
import base64
|
||||
import datetime
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
from os.path import isfile
|
||||
from socket import create_connection, setdefaulttimeout, socket
|
||||
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_oid_to_name,
|
||||
cryptography_get_extensions_from_cert,
|
||||
)
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
|
||||
@@ -246,8 +244,7 @@ def main():
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
@@ -264,47 +261,37 @@ def main():
|
||||
if not isfile(ca_cert):
|
||||
module.fail_json(msg="ca_cert file does not exist")
|
||||
|
||||
if not HAS_CREATE_DEFAULT_CONTEXT:
|
||||
# Python < 2.7.9
|
||||
if proxy_host:
|
||||
if proxy_host:
|
||||
if not HAS_CREATE_DEFAULT_CONTEXT:
|
||||
module.fail_json(msg='To use proxy_host, you must run the get_certificate module with Python 2.7 or newer.',
|
||||
exception=CREATE_DEFAULT_CONTEXT_IMP_ERR)
|
||||
|
||||
try:
|
||||
# Note: get_server_certificate does not support SNI!
|
||||
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
|
||||
else:
|
||||
# Python >= 2.7.9
|
||||
try:
|
||||
if proxy_host:
|
||||
connect = "CONNECT %s:%s HTTP/1.0\r\n\r\n" % (host, port)
|
||||
sock = socket()
|
||||
atexit.register(sock.close)
|
||||
sock.connect((proxy_host, proxy_port))
|
||||
sock.send(connect.encode())
|
||||
sock.recv(8192)
|
||||
else:
|
||||
sock = create_connection((host, port))
|
||||
atexit.register(sock.close)
|
||||
connect = "CONNECT %s:%s HTTP/1.0\r\n\r\n" % (host, port)
|
||||
sock = socket()
|
||||
atexit.register(sock.close)
|
||||
sock.connect((proxy_host, proxy_port))
|
||||
sock.send(connect.encode())
|
||||
sock.recv(8192)
|
||||
|
||||
ctx = create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = CERT_NONE
|
||||
|
||||
if ca_cert:
|
||||
ctx = create_default_context(cafile=ca_cert)
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = CERT_REQUIRED
|
||||
else:
|
||||
ctx = create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = CERT_NONE
|
||||
ctx.verify_mode = CERT_OPTIONAL
|
||||
ctx.load_verify_locations(cafile=ca_cert)
|
||||
|
||||
cert = ctx.wrap_socket(sock, server_hostname=host).getpeercert(True)
|
||||
cert = DER_cert_to_PEM_cert(cert)
|
||||
except Exception as e:
|
||||
if proxy_host:
|
||||
module.fail_json(msg="Failed to get cert via proxy {0}:{1} from {2}:{3}, error: {4}".format(
|
||||
proxy_host, proxy_port, host, port, e))
|
||||
else:
|
||||
module.fail_json(msg="Failed to get cert from {0}:{1}, error: {2}".format(host, port, e))
|
||||
module.fail_json(msg="Failed to get cert from port with error: {0}".format(e))
|
||||
|
||||
else:
|
||||
try:
|
||||
cert = get_server_certificate((host, port), ca_certs=ca_cert)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to get cert from port with error: {0}".format(e))
|
||||
|
||||
result['cert'] = cert
|
||||
|
||||
@@ -342,28 +329,28 @@ def main():
|
||||
x509 = cryptography.x509.load_pem_x509_certificate(to_bytes(cert), cryptography_backend())
|
||||
result['subject'] = {}
|
||||
for attribute in x509.subject:
|
||||
result['subject'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||
result['subject'][crypto_utils.cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||
|
||||
result['expired'] = x509.not_valid_after < datetime.datetime.utcnow()
|
||||
|
||||
result['extensions'] = []
|
||||
for dotted_number, entry in cryptography_get_extensions_from_cert(x509).items():
|
||||
for dotted_number, entry in crypto_utils.cryptography_get_extensions_from_cert(x509).items():
|
||||
oid = cryptography.x509.oid.ObjectIdentifier(dotted_number)
|
||||
result['extensions'].append({
|
||||
'critical': entry['critical'],
|
||||
'asn1_data': base64.b64decode(entry['value']),
|
||||
'name': cryptography_oid_to_name(oid, short=True),
|
||||
'name': crypto_utils.cryptography_oid_to_name(oid, short=True),
|
||||
})
|
||||
|
||||
result['issuer'] = {}
|
||||
for attribute in x509.issuer:
|
||||
result['issuer'][cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||
result['issuer'][crypto_utils.cryptography_oid_to_name(attribute.oid, short=True)] = attribute.value
|
||||
|
||||
result['not_after'] = x509.not_valid_after.strftime('%Y%m%d%H%M%SZ')
|
||||
result['not_before'] = x509.not_valid_before.strftime('%Y%m%d%H%M%SZ')
|
||||
|
||||
result['serial_number'] = x509.serial_number
|
||||
result['signature_algorithm'] = cryptography_oid_to_name(x509.signature_algorithm_oid)
|
||||
result['signature_algorithm'] = crypto_utils.cryptography_oid_to_name(x509.signature_algorithm_oid)
|
||||
|
||||
# We need the -1 offset to get the same values as pyOpenSSL
|
||||
if x509.version == cryptography.x509.Version.v1:
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
@@ -69,12 +72,10 @@ options:
|
||||
I(keyfile) is needed for most of the operations. Parameter
|
||||
value is a string with the passphrase."
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
keysize:
|
||||
description:
|
||||
- "Sets the key size only if LUKS container does not exist."
|
||||
type: int
|
||||
version_added: '1.0.0'
|
||||
new_keyfile:
|
||||
description:
|
||||
- "Adds additional key to given container on I(device).
|
||||
@@ -97,7 +98,6 @@ options:
|
||||
new keyslot will be used even if another keyslot already exists
|
||||
for this passphrase."
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
remove_keyfile:
|
||||
description:
|
||||
- "Removes given key from the container on I(device). Does not
|
||||
@@ -121,7 +121,6 @@ options:
|
||||
container, the I(force_remove_last_key) option must be set
|
||||
to C(yes)."
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
force_remove_last_key:
|
||||
description:
|
||||
- "If set to C(yes), allows removing the last key from a container."
|
||||
@@ -138,36 +137,19 @@ options:
|
||||
not specified."
|
||||
- "This cannot be specified if I(type) is set to C(luks1)."
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
uuid:
|
||||
description:
|
||||
- "With this option user can identify the LUKS container by UUID."
|
||||
- "Will only be used when I(device) and I(label) are not specified."
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
type:
|
||||
description:
|
||||
- "This option allow the user explicit define the format of LUKS
|
||||
container that wants to work with. Options are C(luks1) or C(luks2)"
|
||||
type: str
|
||||
choices: [luks1, luks2]
|
||||
version_added: '1.0.0'
|
||||
cipher:
|
||||
description:
|
||||
- "This option allows the user to define the cipher specification
|
||||
string for the LUKS container."
|
||||
- "Will only be used on container creation."
|
||||
- "For pre-2.6.10 kernels, use C(aes-plain) as they don't understand
|
||||
the new cipher spec strings. To use ESSIV, use C(aes-cbc-essiv:sha256)."
|
||||
type: str
|
||||
version_added: '1.1.0'
|
||||
hash:
|
||||
description:
|
||||
- "This option allows the user to specify the hash function used in LUKS
|
||||
key setup scheme and volume key digest."
|
||||
- "Will only be used on container creation."
|
||||
type: str
|
||||
version_added: '1.1.0'
|
||||
|
||||
|
||||
|
||||
requirements:
|
||||
- "cryptsetup"
|
||||
@@ -180,99 +162,92 @@ author: Jan Pokorny (@japokorn)
|
||||
|
||||
EXAMPLES = '''
|
||||
|
||||
- name: Create LUKS container (remains unchanged if it already exists)
|
||||
community.crypto.luks_device:
|
||||
- name: create LUKS container (remains unchanged if it already exists)
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "present"
|
||||
keyfile: "/vault/keyfile"
|
||||
|
||||
- name: Create LUKS container with a passphrase
|
||||
community.crypto.luks_device:
|
||||
- name: create LUKS container with a passphrase
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "present"
|
||||
passphrase: "foo"
|
||||
|
||||
- name: Create LUKS container with specific encryption
|
||||
community.crypto.luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "present"
|
||||
cipher: "aes"
|
||||
hash: "sha256"
|
||||
|
||||
- name: (Create and) open the LUKS container; name it "mycrypt"
|
||||
community.crypto.luks_device:
|
||||
- name: (create and) open the LUKS container; name it "mycrypt"
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "opened"
|
||||
name: "mycrypt"
|
||||
keyfile: "/vault/keyfile"
|
||||
|
||||
- name: Close the existing LUKS container "mycrypt"
|
||||
community.crypto.luks_device:
|
||||
- name: close the existing LUKS container "mycrypt"
|
||||
luks_device:
|
||||
state: "closed"
|
||||
name: "mycrypt"
|
||||
|
||||
- name: Make sure LUKS container exists and is closed
|
||||
community.crypto.luks_device:
|
||||
- name: make sure LUKS container exists and is closed
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "closed"
|
||||
keyfile: "/vault/keyfile"
|
||||
|
||||
- name: Create container if it does not exist and add new key to it
|
||||
community.crypto.luks_device:
|
||||
- name: create container if it does not exist and add new key to it
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "present"
|
||||
keyfile: "/vault/keyfile"
|
||||
new_keyfile: "/vault/keyfile2"
|
||||
|
||||
- name: Add new key to the LUKS container (container has to exist)
|
||||
community.crypto.luks_device:
|
||||
- name: add new key to the LUKS container (container has to exist)
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
keyfile: "/vault/keyfile"
|
||||
new_keyfile: "/vault/keyfile2"
|
||||
|
||||
- name: Add new passphrase to the LUKS container
|
||||
community.crypto.luks_device:
|
||||
- name: add new passphrase to the LUKS container
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
keyfile: "/vault/keyfile"
|
||||
new_passphrase: "foo"
|
||||
|
||||
- name: Remove existing keyfile from the LUKS container
|
||||
community.crypto.luks_device:
|
||||
- name: remove existing keyfile from the LUKS container
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
remove_keyfile: "/vault/keyfile2"
|
||||
|
||||
- name: Remove existing passphrase from the LUKS container
|
||||
community.crypto.luks_device:
|
||||
- name: remove existing passphrase from the LUKS container
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
remove_passphrase: "foo"
|
||||
|
||||
- name: Completely remove the LUKS container and its contents
|
||||
community.crypto.luks_device:
|
||||
- name: completely remove the LUKS container and its contents
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "absent"
|
||||
|
||||
- name: Create a container with label
|
||||
community.crypto.luks_device:
|
||||
- name: create a container with label
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "present"
|
||||
keyfile: "/vault/keyfile"
|
||||
label: personalLabelName
|
||||
|
||||
- name: Open the LUKS container based on label without device; name it "mycrypt"
|
||||
community.crypto.luks_device:
|
||||
- name: open the LUKS container based on label without device; name it "mycrypt"
|
||||
luks_device:
|
||||
label: "personalLabelName"
|
||||
state: "opened"
|
||||
name: "mycrypt"
|
||||
keyfile: "/vault/keyfile"
|
||||
|
||||
- name: Close container based on UUID
|
||||
community.crypto.luks_device:
|
||||
- name: close container based on UUID
|
||||
luks_device:
|
||||
uuid: 03ecd578-fad4-4e6c-9348-842e3e8fa340
|
||||
state: "closed"
|
||||
name: "mycrypt"
|
||||
|
||||
- name: Create a container using luks2 format
|
||||
community.crypto.luks_device:
|
||||
- name: create a container using luks2 format
|
||||
luks_device:
|
||||
device: "/dev/loop0"
|
||||
state: "present"
|
||||
keyfile: "/vault/keyfile"
|
||||
@@ -397,7 +372,7 @@ class CryptHandler(Handler):
|
||||
result = self._run_command([self._cryptsetup_bin, 'isLuks', device])
|
||||
return result[RETURN_CODE] == 0
|
||||
|
||||
def run_luks_create(self, device, keyfile, passphrase, keysize, cipher, hash_):
|
||||
def run_luks_create(self, device, keyfile, passphrase, keysize):
|
||||
# create a new luks container; use batch mode to auto confirm
|
||||
luks_type = self._module.params['type']
|
||||
label = self._module.params['label']
|
||||
@@ -410,10 +385,6 @@ class CryptHandler(Handler):
|
||||
luks_type = 'luks2'
|
||||
if luks_type is not None:
|
||||
options.extend(['--type', luks_type])
|
||||
if cipher is not None:
|
||||
options.extend(['--cipher', cipher])
|
||||
if hash_ is not None:
|
||||
options.extend(['--hash', hash_])
|
||||
|
||||
args = [self._cryptsetup_bin, 'luksFormat']
|
||||
args.extend(options)
|
||||
@@ -665,8 +636,6 @@ def run_module():
|
||||
label=dict(type='str'),
|
||||
uuid=dict(type='str'),
|
||||
type=dict(type='str', choices=['luks1', 'luks2']),
|
||||
cipher=dict(type='str'),
|
||||
hash=dict(type='str'),
|
||||
)
|
||||
|
||||
mutually_exclusive = [
|
||||
@@ -711,10 +680,7 @@ def run_module():
|
||||
crypt.run_luks_create(conditions.device,
|
||||
module.params['keyfile'],
|
||||
module.params['passphrase'],
|
||||
module.params['keysize'],
|
||||
module.params['cipher'],
|
||||
module.params['hash'],
|
||||
)
|
||||
module.params['keysize'])
|
||||
except ValueError as e:
|
||||
module.fail_json(msg="luks_device error: %s" % e)
|
||||
result['changed'] = True
|
||||
|
||||
@@ -8,6 +8,12 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: openssh_cert
|
||||
@@ -43,16 +49,8 @@ options:
|
||||
signing_key:
|
||||
description:
|
||||
- The path to the private openssh key that is used for signing the public key in order to generate the certificate.
|
||||
- If the private key is on a PKCS#11 token (I(pkcs11_provider)), set this to the path to the public key instead.
|
||||
- Required if I(state) is C(present).
|
||||
type: path
|
||||
pkcs11_provider:
|
||||
description:
|
||||
- To use a signing key that resides on a PKCS#11 token, set this to the name (or full path) of the shared library to use with the token.
|
||||
Usually C(libpkcs11.so).
|
||||
- If this is set, I(signing_key) needs to point to a file containing the public key of the CA.
|
||||
type: str
|
||||
version_added: 1.1.0
|
||||
public_key:
|
||||
description:
|
||||
- The path to the public key that will be signed with the signing key in order to generate the certificate.
|
||||
@@ -124,8 +122,8 @@ extends_documentation_fragment: files
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Generate an OpenSSH user certificate that is valid forever and for all users
|
||||
community.crypto.openssh_cert:
|
||||
# Generate an OpenSSH user certificate that is valid forever and for all users
|
||||
- openssh_cert:
|
||||
type: user
|
||||
signing_key: /path/to/private_key
|
||||
public_key: /path/to/public_key.pub
|
||||
@@ -135,8 +133,7 @@ EXAMPLES = '''
|
||||
|
||||
# Generate an OpenSSH host certificate that is valid for 32 weeks from now and will be regenerated
|
||||
# if it is valid for less than 2 weeks from the time the module is being run
|
||||
- name: Generate an OpenSSH host certificate with valid_from, valid_to and valid_at parameters
|
||||
community.crypto.openssh_cert:
|
||||
- openssh_cert:
|
||||
type: host
|
||||
signing_key: /path/to/private_key
|
||||
public_key: /path/to/public_key.pub
|
||||
@@ -145,8 +142,8 @@ EXAMPLES = '''
|
||||
valid_to: +32w
|
||||
valid_at: +2w
|
||||
|
||||
- name: Generate an OpenSSH host certificate that is valid forever and only for example.com and examplehost
|
||||
community.crypto.openssh_cert:
|
||||
# Generate an OpenSSH host certificate that is valid forever and only for example.com and examplehost
|
||||
- openssh_cert:
|
||||
type: host
|
||||
signing_key: /path/to/private_key
|
||||
public_key: /path/to/public_key.pub
|
||||
@@ -157,8 +154,8 @@ EXAMPLES = '''
|
||||
- example.com
|
||||
- examplehost
|
||||
|
||||
- name: Generate an OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019
|
||||
community.crypto.openssh_cert:
|
||||
# Generate an OpenSSH host Certificate that is valid from 21.1.2001 to 21.1.2019
|
||||
- openssh_cert:
|
||||
type: host
|
||||
signing_key: /path/to/private_key
|
||||
public_key: /path/to/public_key.pub
|
||||
@@ -166,8 +163,8 @@ EXAMPLES = '''
|
||||
valid_from: "2001-01-21"
|
||||
valid_to: "2019-01-21"
|
||||
|
||||
- name: Generate an OpenSSH user Certificate with clear and force-command option
|
||||
community.crypto.openssh_cert:
|
||||
# Generate an OpenSSH user Certificate with clear and force-command option:
|
||||
- openssh_cert:
|
||||
type: user
|
||||
signing_key: /path/to/private_key
|
||||
public_key: /path/to/public_key.pub
|
||||
@@ -178,16 +175,6 @@ EXAMPLES = '''
|
||||
- "clear"
|
||||
- "force-command=/tmp/bla/foo"
|
||||
|
||||
- name: Generate an OpenSSH user certificate using a PKCS#11 token
|
||||
community.crypto.openssh_cert:
|
||||
type: user
|
||||
signing_key: /path/to/ca_public_key.pub
|
||||
pkcs11_provider: libpkcs11.so
|
||||
public_key: /path/to/public_key.pub
|
||||
path: /path/to/certificate
|
||||
valid_from: always
|
||||
valid_to: forever
|
||||
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
@@ -209,20 +196,19 @@ info:
|
||||
|
||||
'''
|
||||
|
||||
import errno
|
||||
import os
|
||||
import errno
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import MINYEAR, MAXYEAR
|
||||
from shutil import copy2, rmtree
|
||||
|
||||
from shutil import copy2
|
||||
from shutil import rmtree
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto import convert_relative_to_datetime
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import convert_relative_to_datetime
|
||||
|
||||
|
||||
class CertificateError(Exception):
|
||||
pass
|
||||
@@ -235,7 +221,6 @@ class Certificate(object):
|
||||
self.force = module.params['force']
|
||||
self.type = module.params['type']
|
||||
self.signing_key = module.params['signing_key']
|
||||
self.pkcs11_provider = module.params['pkcs11_provider']
|
||||
self.public_key = module.params['public_key']
|
||||
self.path = module.params['path']
|
||||
self.identifier = module.params['identifier']
|
||||
@@ -270,9 +255,6 @@ class Certificate(object):
|
||||
'-s', self.signing_key
|
||||
]
|
||||
|
||||
if self.pkcs11_provider:
|
||||
args.extend(['-D', self.pkcs11_provider])
|
||||
|
||||
validity = ""
|
||||
|
||||
if not (self.valid_from == "always" and self.valid_to == "forever"):
|
||||
@@ -547,7 +529,6 @@ def main():
|
||||
force=dict(type='bool', default=False),
|
||||
type=dict(type='str', choices=['host', 'user']),
|
||||
signing_key=dict(type='path'),
|
||||
pkcs11_provider=dict(type='str'),
|
||||
public_key=dict(type='path'),
|
||||
path=dict(type='path', required=True),
|
||||
identifier=dict(type='str'),
|
||||
|
||||
@@ -8,6 +8,12 @@ from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'
|
||||
}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: openssh_keypair
|
||||
@@ -53,7 +59,7 @@ options:
|
||||
required: true
|
||||
comment:
|
||||
description:
|
||||
- Provides a new comment to the public key.
|
||||
- Provides a new comment to the public key. When checking if the key is in the correct state this will be ignored.
|
||||
type: str
|
||||
regenerate:
|
||||
description:
|
||||
@@ -87,31 +93,29 @@ options:
|
||||
- full_idempotence
|
||||
- always
|
||||
default: partial_idempotence
|
||||
version_added: '1.0.0'
|
||||
notes:
|
||||
- In case the ssh key is broken or password protected, the module will fail.
|
||||
Set the I(force) option to C(yes) if you want to regenerate the keypair.
|
||||
- In case the ssh key is broken or password protected, the module will fail. Set the I(force) option to C(yes) if you want to regenerate the keypair.
|
||||
|
||||
extends_documentation_fragment: files
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Generate an OpenSSH keypair with the default values (4096 bits, rsa)
|
||||
community.crypto.openssh_keypair:
|
||||
# Generate an OpenSSH keypair with the default values (4096 bits, rsa)
|
||||
- openssh_keypair:
|
||||
path: /tmp/id_ssh_rsa
|
||||
|
||||
- name: Generate an OpenSSH rsa keypair with a different size (2048 bits)
|
||||
community.crypto.openssh_keypair:
|
||||
# Generate an OpenSSH rsa keypair with a different size (2048 bits)
|
||||
- openssh_keypair:
|
||||
path: /tmp/id_ssh_rsa
|
||||
size: 2048
|
||||
|
||||
- name: Force regenerate an OpenSSH keypair if it already exists
|
||||
community.crypto.openssh_keypair:
|
||||
# Force regenerate an OpenSSH keypair if it already exists
|
||||
- openssh_keypair:
|
||||
path: /tmp/id_ssh_rsa
|
||||
force: True
|
||||
|
||||
- name: Generate an OpenSSH keypair with a different algorithm (dsa)
|
||||
community.crypto.openssh_keypair:
|
||||
# Generate an OpenSSH keypair with a different algorithm (dsa)
|
||||
- openssh_keypair:
|
||||
path: /tmp/id_ssh_dsa
|
||||
type: dsa
|
||||
'''
|
||||
@@ -149,9 +153,9 @@ comment:
|
||||
sample: test@comment
|
||||
'''
|
||||
|
||||
import errno
|
||||
import os
|
||||
import stat
|
||||
import errno
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
x509_certificate.py
|
||||
2723
plugins/modules/openssl_certificate.py
Normal file
2723
plugins/modules/openssl_certificate.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
x509_certificate_info.py
|
||||
861
plugins/modules/openssl_certificate_info.py
Normal file
861
plugins/modules/openssl_certificate_info.py
Normal file
@@ -0,0 +1,861 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: openssl_certificate_info
|
||||
short_description: Provide information of OpenSSL X.509 certificates
|
||||
description:
|
||||
- This module allows one to query information on OpenSSL certificates.
|
||||
- It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the
|
||||
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
|
||||
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
|
||||
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
|
||||
and will be removed in Ansible 2.13.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.6
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
- Yanis Guenane (@Spredzy)
|
||||
- Markus Teufelberger (@MarkusTeufelberger)
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Remote absolute path where the certificate file is loaded from.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: path
|
||||
content:
|
||||
description:
|
||||
- Content of the X.509 certificate in PEM format.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: str
|
||||
valid_at:
|
||||
description:
|
||||
- A dict of names mapping to time specifications. Every time specified here
|
||||
will be checked whether the certificate is valid at this point. See the
|
||||
C(valid_at) return value for informations on the result.
|
||||
- Time can be specified either as relative time or as absolute timestamp.
|
||||
- Time will always be interpreted as UTC.
|
||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (i.e. pattern C(YYYYMMDDHHMMSSZ)).
|
||||
Note that all timestamps will be treated as being in UTC.
|
||||
type: dict
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
|
||||
notes:
|
||||
- All timestamp values are provided in ASN.1 TIME format, i.e. following the C(YYYYMMDDHHMMSSZ) pattern.
|
||||
They are all in UTC.
|
||||
seealso:
|
||||
- module: openssl_certificate
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate a Self Signed OpenSSL certificate
|
||||
openssl_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
csr_path: /etc/ssl/csr/ansible.com.csr
|
||||
provider: selfsigned
|
||||
|
||||
|
||||
# Get information on the certificate
|
||||
|
||||
- name: Get information on generated certificate
|
||||
openssl_certificate_info:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
register: result
|
||||
|
||||
- name: Dump information
|
||||
debug:
|
||||
var: result
|
||||
|
||||
|
||||
# Check whether the certificate is valid or not valid at certain times, fail
|
||||
# if this is not the case. The first task (openssl_certificate_info) collects
|
||||
# the information, and the second task (assert) validates the result and
|
||||
# makes the playbook fail in case something is not as expected.
|
||||
|
||||
- name: Test whether that certificate is valid tomorrow and/or in three weeks
|
||||
openssl_certificate_info:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
valid_at:
|
||||
point_1: "+1d"
|
||||
point_2: "+3w"
|
||||
register: result
|
||||
|
||||
- name: Validate that certificate is valid tomorrow, but not in three weeks
|
||||
assert:
|
||||
that:
|
||||
- result.valid_at.point_1 # valid in one day
|
||||
- not result.valid_at.point_2 # not valid in three weeks
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
expired:
|
||||
description: Whether the certificate is expired (i.e. C(notAfter) is in the past)
|
||||
returned: success
|
||||
type: bool
|
||||
basic_constraints:
|
||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[CA:TRUE, pathlen:1]"
|
||||
basic_constraints_critical:
|
||||
description: Whether the C(basic_constraints) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extended_key_usage:
|
||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[Biometric Info, DVCS, Time Stamping]"
|
||||
extended_key_usage_critical:
|
||||
description: Whether the C(extended_key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extensions_by_oid:
|
||||
description: Returns a dictionary for every extension OID
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
critical:
|
||||
description: Whether the extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
value:
|
||||
description: The Base64 encoded value (in DER format) of the extension
|
||||
returned: success
|
||||
type: str
|
||||
sample: "MAMCAQU="
|
||||
sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}'
|
||||
key_usage:
|
||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "[Key Agreement, Data Encipherment]"
|
||||
key_usage_critical:
|
||||
description: Whether the C(key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
subject_alt_name:
|
||||
description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
subject_alt_name_critical:
|
||||
description: Whether the C(subject_alt_name) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple:
|
||||
description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple_critical:
|
||||
description: Whether the C(ocsp_must_staple) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
issuer:
|
||||
description:
|
||||
- The certificate's issuer.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}'
|
||||
issuer_ordered:
|
||||
description: The certificate's issuer as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]'
|
||||
version_added: "2.9"
|
||||
subject:
|
||||
description:
|
||||
- The certificate's subject as a dictionary.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}'
|
||||
subject_ordered:
|
||||
description: The certificate's subject as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]'
|
||||
version_added: "2.9"
|
||||
not_after:
|
||||
description: C(notAfter) date as ASN.1 TIME
|
||||
returned: success
|
||||
type: str
|
||||
sample: 20190413202428Z
|
||||
not_before:
|
||||
description: C(notBefore) date as ASN.1 TIME
|
||||
returned: success
|
||||
type: str
|
||||
sample: 20190331202428Z
|
||||
public_key:
|
||||
description: Certificate's public key in PEM format
|
||||
returned: success
|
||||
type: str
|
||||
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
|
||||
public_key_fingerprints:
|
||||
description:
|
||||
- Fingerprints of certificate's public key.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
signature_algorithm:
|
||||
description: The signature algorithm used to sign the certificate.
|
||||
returned: success
|
||||
type: str
|
||||
sample: sha256WithRSAEncryption
|
||||
serial_number:
|
||||
description: The certificate's serial number.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 1234
|
||||
version:
|
||||
description: The certificate version.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 3
|
||||
valid_at:
|
||||
description: For every time stamp provided in the I(valid_at) option, a
|
||||
boolean whether the certificate is valid at that point in time
|
||||
or not.
|
||||
returned: success
|
||||
type: dict
|
||||
subject_key_identifier:
|
||||
description:
|
||||
- The certificate's subject key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
version_added: "2.9"
|
||||
authority_key_identifier:
|
||||
description:
|
||||
- The certificate's authority key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
version_added: "2.9"
|
||||
authority_cert_issuer:
|
||||
description:
|
||||
- The certificate's authority cert issuer as a list of general names.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
version_added: "2.9"
|
||||
authority_cert_serial_number:
|
||||
description:
|
||||
- The certificate's authority cert serial number.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: int
|
||||
sample: '12345'
|
||||
version_added: "2.9"
|
||||
ocsp_uri:
|
||||
description: The OCSP responder URI, if included in the certificate. Will be
|
||||
C(none) if no OCSP responder URI is included.
|
||||
returned: success
|
||||
type: str
|
||||
version_added: "2.9"
|
||||
'''
|
||||
|
||||
|
||||
import abc
|
||||
import binascii
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_native, to_text, to_bytes
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# OpenSSL 1.1.0 or newer
|
||||
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
|
||||
else:
|
||||
# OpenSSL 1.0.x or older
|
||||
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
|
||||
except ImportError:
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_FOUND = False
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
|
||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CertificateInfo(crypto_utils.OpenSSLObject):
|
||||
def __init__(self, module, backend):
|
||||
super(CertificateInfo, self).__init__(
|
||||
module.params['path'] or '',
|
||||
'present',
|
||||
False,
|
||||
module.check_mode,
|
||||
)
|
||||
self.backend = backend
|
||||
self.module = module
|
||||
self.content = module.params['content']
|
||||
if self.content is not None:
|
||||
self.content = self.content.encode('utf-8')
|
||||
|
||||
self.valid_at = module.params['valid_at']
|
||||
if self.valid_at:
|
||||
for k, v in self.valid_at.items():
|
||||
if not isinstance(v, string_types):
|
||||
self.module.fail_json(
|
||||
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
|
||||
)
|
||||
self.valid_at[k] = crypto_utils.get_relative_time_option(v, 'valid_at.{0}'.format(k))
|
||||
|
||||
def generate(self):
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_signature_algorithm(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_subject_ordered(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_issuer_ordered(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_version(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_extended_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_basic_constraints(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_ocsp_must_staple(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_subject_alt_name(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_not_before(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_not_after(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_public_key(self, binary):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_subject_key_identifier(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_authority_key_identifier(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_serial_number(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_all_extensions(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_ocsp_uri(self):
|
||||
pass
|
||||
|
||||
def get_info(self):
|
||||
result = dict()
|
||||
self.cert = crypto_utils.load_certificate(self.path, content=self.content, backend=self.backend)
|
||||
|
||||
result['signature_algorithm'] = self._get_signature_algorithm()
|
||||
subject = self._get_subject_ordered()
|
||||
issuer = self._get_issuer_ordered()
|
||||
result['subject'] = dict()
|
||||
for k, v in subject:
|
||||
result['subject'][k] = v
|
||||
result['subject_ordered'] = subject
|
||||
result['issuer'] = dict()
|
||||
for k, v in issuer:
|
||||
result['issuer'][k] = v
|
||||
result['issuer_ordered'] = issuer
|
||||
result['version'] = self._get_version()
|
||||
result['key_usage'], result['key_usage_critical'] = self._get_key_usage()
|
||||
result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage()
|
||||
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
|
||||
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
|
||||
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
|
||||
|
||||
not_before = self._get_not_before()
|
||||
not_after = self._get_not_after()
|
||||
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
|
||||
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
|
||||
result['expired'] = not_after < datetime.datetime.utcnow()
|
||||
|
||||
result['valid_at'] = dict()
|
||||
if self.valid_at:
|
||||
for k, v in self.valid_at.items():
|
||||
result['valid_at'][k] = not_before <= v <= not_after
|
||||
|
||||
result['public_key'] = self._get_public_key(binary=False)
|
||||
pk = self._get_public_key(binary=True)
|
||||
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
|
||||
|
||||
if self.backend != 'pyopenssl':
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
|
||||
result['serial_number'] = self._get_serial_number()
|
||||
result['extensions_by_oid'] = self._get_all_extensions()
|
||||
result['ocsp_uri'] = self._get_ocsp_uri()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class CertificateInfoCryptography(CertificateInfo):
|
||||
"""Validate the supplied cert, using the cryptography backend"""
|
||||
def __init__(self, module):
|
||||
super(CertificateInfoCryptography, self).__init__(module, 'cryptography')
|
||||
|
||||
def _get_signature_algorithm(self):
|
||||
return crypto_utils.cryptography_oid_to_name(self.cert.signature_algorithm_oid)
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
result = []
|
||||
for attribute in self.cert.subject:
|
||||
result.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
return result
|
||||
|
||||
def _get_issuer_ordered(self):
|
||||
result = []
|
||||
for attribute in self.cert.issuer:
|
||||
result.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
return result
|
||||
|
||||
def _get_version(self):
|
||||
if self.cert.version == x509.Version.v1:
|
||||
return 1
|
||||
if self.cert.version == x509.Version.v3:
|
||||
return 3
|
||||
return "unknown"
|
||||
|
||||
def _get_key_usage(self):
|
||||
try:
|
||||
current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage)
|
||||
current_key_usage = current_key_ext.value
|
||||
key_usage = dict(
|
||||
digital_signature=current_key_usage.digital_signature,
|
||||
content_commitment=current_key_usage.content_commitment,
|
||||
key_encipherment=current_key_usage.key_encipherment,
|
||||
data_encipherment=current_key_usage.data_encipherment,
|
||||
key_agreement=current_key_usage.key_agreement,
|
||||
key_cert_sign=current_key_usage.key_cert_sign,
|
||||
crl_sign=current_key_usage.crl_sign,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
)
|
||||
if key_usage['key_agreement']:
|
||||
key_usage.update(dict(
|
||||
encipher_only=current_key_usage.encipher_only,
|
||||
decipher_only=current_key_usage.decipher_only
|
||||
))
|
||||
|
||||
key_usage_names = dict(
|
||||
digital_signature='Digital Signature',
|
||||
content_commitment='Non Repudiation',
|
||||
key_encipherment='Key Encipherment',
|
||||
data_encipherment='Data Encipherment',
|
||||
key_agreement='Key Agreement',
|
||||
key_cert_sign='Certificate Sign',
|
||||
crl_sign='CRL Sign',
|
||||
encipher_only='Encipher Only',
|
||||
decipher_only='Decipher Only',
|
||||
)
|
||||
return sorted([
|
||||
key_usage_names[name] for name, value in key_usage.items() if value
|
||||
]), current_key_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
try:
|
||||
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
|
||||
return sorted([
|
||||
crypto_utils.cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
|
||||
]), ext_keyusage_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
try:
|
||||
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints)
|
||||
result = []
|
||||
result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE'))
|
||||
if ext_keyusage_ext.value.path_length is not None:
|
||||
result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length))
|
||||
return sorted(result), ext_keyusage_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
try:
|
||||
try:
|
||||
# This only works with cryptography >= 2.1
|
||||
tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature)
|
||||
value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
|
||||
except AttributeError as dummy:
|
||||
# Fallback for cryptography < 2.1
|
||||
oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
|
||||
tlsfeature_ext = self.cert.extensions.get_extension_for_oid(oid)
|
||||
value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05"
|
||||
return value, tlsfeature_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
try:
|
||||
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
result = [crypto_utils.cryptography_decode_name(san) for san in san_ext.value]
|
||||
return result, san_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_not_before(self):
|
||||
return self.cert.not_valid_before
|
||||
|
||||
def _get_not_after(self):
|
||||
return self.cert.not_valid_after
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
return self.cert.public_key().public_bytes(
|
||||
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
try:
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
|
||||
return ext.value.digest
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
try:
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
||||
issuer = None
|
||||
if ext.value.authority_cert_issuer is not None:
|
||||
issuer = [crypto_utils.cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
|
||||
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, None
|
||||
|
||||
def _get_serial_number(self):
|
||||
return self.cert.serial_number
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return crypto_utils.cryptography_get_extensions_from_cert(self.cert)
|
||||
|
||||
def _get_ocsp_uri(self):
|
||||
try:
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
|
||||
for desc in ext.value:
|
||||
if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP:
|
||||
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
|
||||
return desc.access_location.value
|
||||
except x509.ExtensionNotFound as dummy:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class CertificateInfoPyOpenSSL(CertificateInfo):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module):
|
||||
super(CertificateInfoPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
def _get_signature_algorithm(self):
|
||||
return to_text(self.cert.get_signature_algorithm())
|
||||
|
||||
def __get_name(self, name):
|
||||
result = []
|
||||
for sub in name.get_components():
|
||||
result.append([crypto_utils.pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
return result
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
return self.__get_name(self.cert.get_subject())
|
||||
|
||||
def _get_issuer_ordered(self):
|
||||
return self.__get_name(self.cert.get_issuer())
|
||||
|
||||
def _get_version(self):
|
||||
# Version numbers in certs are off by one:
|
||||
# v1: 0, v2: 1, v3: 2 ...
|
||||
return self.cert.get_version() + 1
|
||||
|
||||
def _get_extension(self, short_name):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == short_name:
|
||||
result = [
|
||||
crypto_utils.pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
]
|
||||
return sorted(result), bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_key_usage(self):
|
||||
return self._get_extension(b'keyUsage')
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
return self._get_extension(b'extendedKeyUsage')
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
return self._get_extension(b'basicConstraints')
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
|
||||
oms_ext = [
|
||||
ext for ext in extensions
|
||||
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
|
||||
]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
# Older versions of libssl don't know about OCSP Must Staple
|
||||
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
|
||||
if oms_ext:
|
||||
return True, bool(oms_ext[0].get_critical())
|
||||
else:
|
||||
return None, False
|
||||
|
||||
def _normalize_san(self, san):
|
||||
if san.startswith('IP Address:'):
|
||||
san = 'IP:' + san[len('IP Address:'):]
|
||||
if san.startswith('IP:'):
|
||||
ip = compat_ipaddress.ip_address(san[3:])
|
||||
san = 'IP:{0}'.format(ip.compressed)
|
||||
return san
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
result = [self._normalize_san(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
return result, bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_not_before(self):
|
||||
time_string = to_native(self.cert.get_notBefore())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def _get_not_after(self):
|
||||
time_string = to_native(self.cert.get_notAfter())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
|
||||
self.cert.get_pubkey()
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
if binary:
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey)
|
||||
else:
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None, None, None
|
||||
|
||||
def _get_serial_number(self):
|
||||
return self.cert.get_serial_number()
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return crypto_utils.pyopenssl_get_extensions_from_cert(self.cert)
|
||||
|
||||
def _get_ocsp_uri(self):
|
||||
for i in range(self.cert.get_extension_count()):
|
||||
ext = self.cert.get_extension(i)
|
||||
if ext.get_short_name() == b'authorityInfoAccess':
|
||||
v = str(ext)
|
||||
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
valid_at=dict(type='dict'),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
try:
|
||||
if module.params['path'] is not None:
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||
)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
# Detect what backend we can use
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# If cryptography is available we'll use it
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Fail if no backend has been found
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
certificate = CertificateInfoPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
certificate = CertificateInfoCryptography(module)
|
||||
|
||||
result = certificate.get_info()
|
||||
module.exit_json(**result)
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -7,6 +7,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -20,10 +23,10 @@ description:
|
||||
- "Please note that the module regenerates existing CSR if it doesn't match the module's
|
||||
options, or if it seems to be corrupt. If you are concerned that this could overwrite
|
||||
your existing CSR, consider using the I(backup) option."
|
||||
- "The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the
|
||||
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
|
||||
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
|
||||
requirements:
|
||||
- Either cryptography >= 1.3
|
||||
- Or pyOpenSSL >= 0.15
|
||||
@@ -51,7 +54,6 @@ options:
|
||||
- The content of the private key to use when signing the certificate signing request.
|
||||
- Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both.
|
||||
type: str
|
||||
version_added: "1.0.0"
|
||||
privatekey_passphrase:
|
||||
description:
|
||||
- The passphrase for the private key.
|
||||
@@ -120,7 +122,7 @@ options:
|
||||
- SAN extension to attach to the certificate signing request.
|
||||
- This can either be a 'comma separated string' or a YAML list.
|
||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||
C(otherName) and the ones specific to your CA).
|
||||
C(otherName) and the ones specific to your CA)
|
||||
- Note that if no SAN is specified, but a common name, the common
|
||||
name will be added as a SAN except if C(useCommonNameForSAN) is
|
||||
set to I(false).
|
||||
@@ -183,43 +185,20 @@ options:
|
||||
aliases: [ ocspMustStaple ]
|
||||
ocsp_must_staple_critical:
|
||||
description:
|
||||
- Should the OCSP Must Staple extension be considered as critical.
|
||||
- Should the OCSP Must Staple extension be considered as critical
|
||||
- Note that according to the RFC, this extension should not be marked
|
||||
as critical, as old clients not knowing about OCSP Must Staple
|
||||
are required to reject such certificates
|
||||
(see U(https://tools.ietf.org/html/rfc7633#section-4)).
|
||||
type: bool
|
||||
aliases: [ ocspMustStaple_critical ]
|
||||
name_constraints_permitted:
|
||||
description:
|
||||
- For CA certificates, this specifies a list of identifiers which describe
|
||||
subtrees of names that this CA is allowed to issue certificates for.
|
||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||
C(otherName) and the ones specific to your CA).
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 1.1.0
|
||||
name_constraints_excluded:
|
||||
description:
|
||||
- For CA certificates, this specifies a list of identifiers which describe
|
||||
subtrees of names that this CA is *not* allowed to issue certificates for.
|
||||
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
|
||||
C(otherName) and the ones specific to your CA).
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 1.1.0
|
||||
name_constraints_critical:
|
||||
description:
|
||||
- Should the Name Constraints extension be considered as critical.
|
||||
type: bool
|
||||
version_added: 1.1.0
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
@@ -291,7 +270,6 @@ options:
|
||||
- If set to C(yes), will return the (current or generated) CSR's content as I(csr).
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.0.0"
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
notes:
|
||||
@@ -299,35 +277,35 @@ notes:
|
||||
keyUsage, extendedKeyUsage and basicConstraints only contain the requested values, whether
|
||||
OCSP Must Staple is as requested, and if the request was signed by the given private key.
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
- module: community.crypto.openssl_dhparam
|
||||
- module: community.crypto.openssl_pkcs12
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: community.crypto.openssl_publickey
|
||||
- module: openssl_certificate
|
||||
- module: openssl_dhparam
|
||||
- module: openssl_pkcs12
|
||||
- module: openssl_privatekey
|
||||
- module: openssl_publickey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate an OpenSSL Certificate Signing Request
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
common_name: www.ansible.com
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request with an inline key
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_content: "{{ private_key_content }}"
|
||||
common_name: www.ansible.com
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request with a passphrase protected private key
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
privatekey_passphrase: ansible
|
||||
common_name: www.ansible.com
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request with Subject information
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
country_name: FR
|
||||
@@ -336,13 +314,13 @@ EXAMPLES = r'''
|
||||
common_name: www.ansible.com
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request with subjectAltName extension
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
subject_alt_name: 'DNS:www.ansible.com,DNS:m.ansible.com'
|
||||
|
||||
- name: Generate an OpenSSL CSR with subjectAltName extension with dynamic list
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
subject_alt_name: "{{ item.value | map('regex_replace', '^', 'DNS:') | list }}"
|
||||
@@ -352,14 +330,14 @@ EXAMPLES = r'''
|
||||
- m.ansible.com
|
||||
|
||||
- name: Force regenerate an OpenSSL Certificate Signing Request
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
force: yes
|
||||
common_name: www.ansible.com
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request with special key usages
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
common_name: www.ansible.com
|
||||
@@ -370,20 +348,11 @@ EXAMPLES = r'''
|
||||
- clientAuth
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request with OCSP Must Staple
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
common_name: www.ansible.com
|
||||
ocsp_must_staple: yes
|
||||
|
||||
- name: Generate an OpenSSL Certificate Signing Request for WinRM Certificate authentication
|
||||
community.crypto.openssl_csr:
|
||||
path: /etc/ssl/csr/winrm.auth.csr
|
||||
privatekey_path: /etc/ssl/private/winrm.auth.pem
|
||||
common_name: username
|
||||
extended_key_usage:
|
||||
- clientAuth
|
||||
subject_alt_name: otherName:1.3.6.1.4.1.311.20.2.3;UTF8:username@localhost
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
@@ -435,20 +404,6 @@ ocsp_must_staple:
|
||||
returned: changed or success
|
||||
type: bool
|
||||
sample: false
|
||||
name_constraints_permitted:
|
||||
description: List of permitted subtrees to sign certificates for.
|
||||
returned: changed or success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['email:.somedomain.com']
|
||||
version_added: 1.1.0
|
||||
name_constraints_excluded:
|
||||
description: List of excluded subtrees the CA cannot sign certificates for.
|
||||
returned: changed or success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['email:.com']
|
||||
version_added: 1.1.0
|
||||
backup_file:
|
||||
description: Name of backup file created.
|
||||
returned: changed and if I(backup) is C(yes)
|
||||
@@ -458,48 +413,19 @@ csr:
|
||||
description: The (current or generated) CSR's content.
|
||||
returned: if I(state) is C(present) and I(return_content) is C(yes)
|
||||
type: str
|
||||
version_added: "1.0.0"
|
||||
version_added: "2.10"
|
||||
'''
|
||||
|
||||
import abc
|
||||
import binascii
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_bytes, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
load_certificate_request,
|
||||
parse_name_field,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_get_basic_constraints,
|
||||
cryptography_get_name,
|
||||
cryptography_name_to_oid,
|
||||
cryptography_key_needs_digest_for_signing,
|
||||
cryptography_parse_key_usage_params,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_normalize_name_attribute,
|
||||
pyopenssl_parse_name_constraints,
|
||||
)
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||
@@ -542,11 +468,11 @@ else:
|
||||
CRYPTOGRAPHY_MUST_STAPLE_VALUE = b"\x30\x03\x02\x01\x05"
|
||||
|
||||
|
||||
class CertificateSigningRequestError(OpenSSLObjectError):
|
||||
class CertificateSigningRequestError(crypto_utils.OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
class CertificateSigningRequestBase(OpenSSLObject):
|
||||
class CertificateSigningRequestBase(crypto_utils.OpenSSLObject):
|
||||
|
||||
def __init__(self, module):
|
||||
super(CertificateSigningRequestBase, self).__init__(
|
||||
@@ -572,9 +498,6 @@ class CertificateSigningRequestBase(OpenSSLObject):
|
||||
self.basicConstraints_critical = module.params['basic_constraints_critical']
|
||||
self.ocspMustStaple = module.params['ocsp_must_staple']
|
||||
self.ocspMustStaple_critical = module.params['ocsp_must_staple_critical']
|
||||
self.name_constraints_permitted = module.params['name_constraints_permitted'] or []
|
||||
self.name_constraints_excluded = module.params['name_constraints_excluded'] or []
|
||||
self.name_constraints_critical = module.params['name_constraints_critical']
|
||||
self.create_subject_key_identifier = module.params['create_subject_key_identifier']
|
||||
self.subject_key_identifier = module.params['subject_key_identifier']
|
||||
self.authority_key_identifier = module.params['authority_key_identifier']
|
||||
@@ -602,7 +525,7 @@ class CertificateSigningRequestBase(OpenSSLObject):
|
||||
]
|
||||
|
||||
if module.params['subject']:
|
||||
self.subject = self.subject + parse_name_field(module.params['subject'])
|
||||
self.subject = self.subject + crypto_utils.parse_name_field(module.params['subject'])
|
||||
self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
|
||||
|
||||
if not self.subjectAltName and module.params['use_common_name_for_san']:
|
||||
@@ -635,7 +558,7 @@ class CertificateSigningRequestBase(OpenSSLObject):
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
if self.return_content:
|
||||
self.csr_bytes = result
|
||||
write_file(module, result)
|
||||
crypto_utils.write_file(module, result)
|
||||
self.changed = True
|
||||
|
||||
file_args = module.load_file_common_arguments(module.params)
|
||||
@@ -678,15 +601,13 @@ class CertificateSigningRequestBase(OpenSSLObject):
|
||||
'extendedKeyUsage': self.extendedKeyUsage,
|
||||
'basicConstraints': self.basicConstraints,
|
||||
'ocspMustStaple': self.ocspMustStaple,
|
||||
'changed': self.changed,
|
||||
'name_constraints_permitted': self.name_constraints_permitted,
|
||||
'name_constraints_excluded': self.name_constraints_excluded,
|
||||
'changed': self.changed
|
||||
}
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
if self.return_content:
|
||||
if self.csr_bytes is None:
|
||||
self.csr_bytes = load_file_if_exists(self.path, ignore_errors=True)
|
||||
self.csr_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['csr'] = self.csr_bytes.decode('utf-8') if self.csr_bytes else None
|
||||
|
||||
return result
|
||||
@@ -740,13 +661,6 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
|
||||
usages = ', '.join(self.basicConstraints)
|
||||
extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii')))
|
||||
|
||||
if self.name_constraints_permitted or self.name_constraints_excluded:
|
||||
usages = ', '.join(
|
||||
['permitted;{0}'.format(name) for name in self.name_constraints_permitted] +
|
||||
['excluded;{0}'.format(name) for name in self.name_constraints_excluded]
|
||||
)
|
||||
extensions.append(crypto.X509Extension(b"nameConstraints", self.name_constraints_critical, usages.encode('ascii')))
|
||||
|
||||
if self.ocspMustStaple:
|
||||
extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE))
|
||||
|
||||
@@ -761,14 +675,24 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
|
||||
|
||||
def _load_private_key(self):
|
||||
try:
|
||||
self.privatekey = load_privatekey(
|
||||
self.privatekey = crypto_utils.load_privatekey(
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||
raise CertificateSigningRequestError(exc)
|
||||
|
||||
def _normalize_san(self, san):
|
||||
# Apparently OpenSSL returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
|
||||
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
|
||||
if san.startswith('IP Address:'):
|
||||
san = 'IP:' + san[len('IP Address:'):]
|
||||
if san.startswith('IP:'):
|
||||
ip = compat_ipaddress.ip_address(san[3:])
|
||||
san = 'IP:{0}'.format(ip.compressed)
|
||||
return san
|
||||
|
||||
def _check_csr(self):
|
||||
def _check_subject(csr):
|
||||
subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in self.subject]
|
||||
@@ -780,10 +704,10 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
|
||||
|
||||
def _check_subjectAltName(extensions):
|
||||
altnames_ext = next((ext for ext in extensions if ext.get_short_name() == b'subjectAltName'), '')
|
||||
altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
altnames = [self._normalize_san(altname.strip()) for altname in
|
||||
to_text(altnames_ext, errors='surrogate_or_strict').split(',') if altname.strip()]
|
||||
if self.subjectAltName:
|
||||
if (set(altnames) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.subjectAltName]) or
|
||||
if (set(altnames) != set([self._normalize_san(to_text(name)) for name in self.subjectAltName]) or
|
||||
altnames_ext.get_critical() != self.subjectAltName_critical):
|
||||
return False
|
||||
else:
|
||||
@@ -823,22 +747,6 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
|
||||
def _check_basicConstraints(extensions):
|
||||
return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical)
|
||||
|
||||
def _check_nameConstraints(extensions):
|
||||
nc_ext = next((ext for ext in extensions if ext.get_short_name() == b'nameConstraints'), '')
|
||||
permitted, excluded = pyopenssl_parse_name_constraints(nc_ext)
|
||||
if self.name_constraints_permitted or self.name_constraints_excluded:
|
||||
if set(permitted) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_permitted]):
|
||||
return False
|
||||
if set(excluded) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_excluded]):
|
||||
return False
|
||||
if nc_ext.get_critical() != self.name_constraints_critical:
|
||||
return False
|
||||
else:
|
||||
if permitted or excluded:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _check_ocspMustStaple(extensions):
|
||||
oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
@@ -853,7 +761,7 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
|
||||
extensions = csr.get_extensions()
|
||||
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
|
||||
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
|
||||
_check_ocspMustStaple(extensions) and _check_nameConstraints(extensions))
|
||||
_check_ocspMustStaple(extensions))
|
||||
|
||||
def _check_signature(csr):
|
||||
try:
|
||||
@@ -862,7 +770,7 @@ class CertificateSigningRequestPyOpenSSL(CertificateSigningRequestBase):
|
||||
return False
|
||||
|
||||
try:
|
||||
csr = load_certificate_request(self.path, backend='pyopenssl')
|
||||
csr = crypto_utils.load_certificate_request(self.path, backend='pyopenssl')
|
||||
except Exception as dummy:
|
||||
return False
|
||||
|
||||
@@ -882,27 +790,27 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
csr = cryptography.x509.CertificateSigningRequestBuilder()
|
||||
try:
|
||||
csr = csr.subject_name(cryptography.x509.Name([
|
||||
cryptography.x509.NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject
|
||||
cryptography.x509.NameAttribute(crypto_utils.cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject
|
||||
]))
|
||||
except ValueError as e:
|
||||
raise CertificateSigningRequestError(e)
|
||||
|
||||
if self.subjectAltName:
|
||||
csr = csr.add_extension(cryptography.x509.SubjectAlternativeName([
|
||||
cryptography_get_name(name) for name in self.subjectAltName
|
||||
crypto_utils.cryptography_get_name(name) for name in self.subjectAltName
|
||||
]), critical=self.subjectAltName_critical)
|
||||
|
||||
if self.keyUsage:
|
||||
params = cryptography_parse_key_usage_params(self.keyUsage)
|
||||
params = crypto_utils.cryptography_parse_key_usage_params(self.keyUsage)
|
||||
csr = csr.add_extension(cryptography.x509.KeyUsage(**params), critical=self.keyUsage_critical)
|
||||
|
||||
if self.extendedKeyUsage:
|
||||
usages = [cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage]
|
||||
usages = [crypto_utils.cryptography_name_to_oid(usage) for usage in self.extendedKeyUsage]
|
||||
csr = csr.add_extension(cryptography.x509.ExtendedKeyUsage(usages), critical=self.extendedKeyUsage_critical)
|
||||
|
||||
if self.basicConstraints:
|
||||
params = {}
|
||||
ca, path_length = cryptography_get_basic_constraints(self.basicConstraints)
|
||||
ca, path_length = crypto_utils.cryptography_get_basic_constraints(self.basicConstraints)
|
||||
csr = csr.add_extension(cryptography.x509.BasicConstraints(ca, path_length), critical=self.basicConstraints_critical)
|
||||
|
||||
if self.ocspMustStaple:
|
||||
@@ -915,15 +823,6 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
critical=self.ocspMustStaple_critical
|
||||
)
|
||||
|
||||
if self.name_constraints_permitted or self.name_constraints_excluded:
|
||||
try:
|
||||
csr = csr.add_extension(cryptography.x509.NameConstraints(
|
||||
[cryptography_get_name(name) for name in self.name_constraints_permitted],
|
||||
[cryptography_get_name(name) for name in self.name_constraints_excluded],
|
||||
), critical=self.name_constraints_critical)
|
||||
except TypeError as e:
|
||||
raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e))
|
||||
|
||||
if self.create_subject_key_identifier:
|
||||
csr = csr.add_extension(
|
||||
cryptography.x509.SubjectKeyIdentifier.from_public_key(self.privatekey.public_key()),
|
||||
@@ -935,14 +834,14 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
if self.authority_key_identifier is not None or self.authority_cert_issuer is not None or self.authority_cert_serial_number is not None:
|
||||
issuers = None
|
||||
if self.authority_cert_issuer is not None:
|
||||
issuers = [cryptography_get_name(n) for n in self.authority_cert_issuer]
|
||||
issuers = [crypto_utils.cryptography_get_name(n) for n in self.authority_cert_issuer]
|
||||
csr = csr.add_extension(
|
||||
cryptography.x509.AuthorityKeyIdentifier(self.authority_key_identifier, issuers, self.authority_cert_serial_number),
|
||||
critical=False
|
||||
)
|
||||
|
||||
digest = None
|
||||
if cryptography_key_needs_digest_for_signing(self.privatekey):
|
||||
if crypto_utils.cryptography_key_needs_digest_for_signing(self.privatekey):
|
||||
if self.digest == 'sha256':
|
||||
digest = cryptography.hazmat.primitives.hashes.SHA256()
|
||||
elif self.digest == 'sha384':
|
||||
@@ -982,7 +881,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
|
||||
def _check_csr(self):
|
||||
def _check_subject(csr):
|
||||
subject = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.subject]
|
||||
subject = [(crypto_utils.cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.subject]
|
||||
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
|
||||
return set(subject) == set(current_subject)
|
||||
|
||||
@@ -995,7 +894,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
def _check_subjectAltName(extensions):
|
||||
current_altnames_ext = _find_extension(extensions, cryptography.x509.SubjectAlternativeName)
|
||||
current_altnames = [str(altname) for altname in current_altnames_ext.value] if current_altnames_ext else []
|
||||
altnames = [str(cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
|
||||
altnames = [str(crypto_utils.cryptography_get_name(altname)) for altname in self.subjectAltName] if self.subjectAltName else []
|
||||
if set(altnames) != set(current_altnames):
|
||||
return False
|
||||
if altnames:
|
||||
@@ -1009,7 +908,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
return current_keyusage_ext is None
|
||||
elif current_keyusage_ext is None:
|
||||
return False
|
||||
params = cryptography_parse_key_usage_params(self.keyUsage)
|
||||
params = crypto_utils.cryptography_parse_key_usage_params(self.keyUsage)
|
||||
for param in params:
|
||||
if getattr(current_keyusage_ext.value, '_' + param) != params[param]:
|
||||
return False
|
||||
@@ -1020,7 +919,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
def _check_extenededKeyUsage(extensions):
|
||||
current_usages_ext = _find_extension(extensions, cryptography.x509.ExtendedKeyUsage)
|
||||
current_usages = [str(usage) for usage in current_usages_ext.value] if current_usages_ext else []
|
||||
usages = [str(cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else []
|
||||
usages = [str(crypto_utils.cryptography_name_to_oid(usage)) for usage in self.extendedKeyUsage] if self.extendedKeyUsage else []
|
||||
if set(current_usages) != set(usages):
|
||||
return False
|
||||
if usages:
|
||||
@@ -1032,7 +931,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
bc_ext = _find_extension(extensions, cryptography.x509.BasicConstraints)
|
||||
current_ca = bc_ext.value.ca if bc_ext else False
|
||||
current_path_length = bc_ext.value.path_length if bc_ext else None
|
||||
ca, path_length = cryptography_get_basic_constraints(self.basicConstraints)
|
||||
ca, path_length = crypto_utils.cryptography_get_basic_constraints(self.basicConstraints)
|
||||
# Check CA flag
|
||||
if ca != current_ca:
|
||||
return False
|
||||
@@ -1066,19 +965,6 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
else:
|
||||
return tlsfeature_ext is None
|
||||
|
||||
def _check_nameConstraints(extensions):
|
||||
current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints)
|
||||
current_nc_perm = [str(altname) for altname in current_nc_ext.value.permitted_subtrees] if current_nc_ext else []
|
||||
current_nc_excl = [str(altname) for altname in current_nc_ext.value.excluded_subtrees] if current_nc_ext else []
|
||||
nc_perm = [str(cryptography_get_name(altname)) for altname in self.name_constraints_permitted]
|
||||
nc_excl = [str(cryptography_get_name(altname)) for altname in self.name_constraints_excluded]
|
||||
if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl):
|
||||
return False
|
||||
if nc_perm or nc_excl:
|
||||
if current_nc_ext.critical != self.name_constraints_critical:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _check_subject_key_identifier(extensions):
|
||||
ext = _find_extension(extensions, cryptography.x509.SubjectKeyIdentifier)
|
||||
if self.create_subject_key_identifier or self.subject_key_identifier is not None:
|
||||
@@ -1100,7 +986,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
aci = None
|
||||
csr_aci = None
|
||||
if self.authority_cert_issuer is not None:
|
||||
aci = [str(cryptography_get_name(n)) for n in self.authority_cert_issuer]
|
||||
aci = [str(crypto_utils.cryptography_get_name(n)) for n in self.authority_cert_issuer]
|
||||
if ext.value.authority_cert_issuer is not None:
|
||||
csr_aci = [str(n) for n in ext.value.authority_cert_issuer]
|
||||
return (ext.value.key_identifier == self.authority_key_identifier
|
||||
@@ -1114,7 +1000,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
|
||||
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
|
||||
_check_ocspMustStaple(extensions) and _check_subject_key_identifier(extensions) and
|
||||
_check_authority_key_identifier(extensions) and _check_nameConstraints(extensions))
|
||||
_check_authority_key_identifier(extensions))
|
||||
|
||||
def _check_signature(csr):
|
||||
if not csr.is_signature_valid:
|
||||
@@ -1132,7 +1018,7 @@ class CertificateSigningRequestCryptography(CertificateSigningRequestBase):
|
||||
return key_a == key_b
|
||||
|
||||
try:
|
||||
csr = load_certificate_request(self.path, backend='cryptography')
|
||||
csr = crypto_utils.load_certificate_request(self.path, backend='cryptography')
|
||||
except Exception as dummy:
|
||||
return False
|
||||
|
||||
@@ -1169,9 +1055,6 @@ def main():
|
||||
basic_constraints_critical=dict(type='bool', default=False, aliases=['basicConstraints_critical']),
|
||||
ocsp_must_staple=dict(type='bool', default=False, aliases=['ocspMustStaple']),
|
||||
ocsp_must_staple_critical=dict(type='bool', default=False, aliases=['ocspMustStaple_critical']),
|
||||
name_constraints_permitted=dict(type='list', elements='str'),
|
||||
name_constraints_excluded=dict(type='list', elements='str'),
|
||||
name_constraints_critical=dict(type='bool', default=False),
|
||||
backup=dict(type='bool', default=False),
|
||||
create_subject_key_identifier=dict(type='bool', default=False),
|
||||
subject_key_identifier=dict(type='str'),
|
||||
@@ -1191,9 +1074,8 @@ def main():
|
||||
)
|
||||
|
||||
if module.params['version'] != 1:
|
||||
module.deprecate('The version option will only support allowed values from community.crypto 2.0.0 on. '
|
||||
'Currently, only the value 1 is allowed by RFC 2986',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The version option will only support allowed values from Ansible 2.14 on. '
|
||||
'Currently, only the value 1 is allowed by RFC 2986', version='2.14')
|
||||
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
if not os.path.isdir(base_dir):
|
||||
@@ -1227,8 +1109,7 @@ def main():
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
csr = CertificateSigningRequestPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
@@ -1254,7 +1135,7 @@ def main():
|
||||
|
||||
result = csr.dump()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -21,7 +24,7 @@ description:
|
||||
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
|
||||
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
|
||||
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
|
||||
and will be removed in community.crypto 2.0.0.
|
||||
and will be removed in Ansible 2.13.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.3
|
||||
author:
|
||||
@@ -38,32 +41,31 @@ options:
|
||||
- Content of the CSR file.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: str
|
||||
version_added: "1.0.0"
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
|
||||
seealso:
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: openssl_csr
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate an OpenSSL Certificate Signing Request
|
||||
community.crypto.openssl_csr:
|
||||
openssl_csr:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
common_name: www.ansible.com
|
||||
|
||||
- name: Get information on the CSR
|
||||
community.crypto.openssl_csr_info:
|
||||
openssl_csr_info:
|
||||
path: /etc/ssl/csr/www.ansible.com.csr
|
||||
register: result
|
||||
|
||||
@@ -141,29 +143,6 @@ ocsp_must_staple_critical:
|
||||
description: Whether the C(ocsp_must_staple) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
name_constraints_permitted:
|
||||
description: List of permitted subtrees to sign certificates for.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['email:.somedomain.com']
|
||||
version_added: 1.1.0
|
||||
name_constraints_excluded:
|
||||
description:
|
||||
- List of excluded subtrees the CA cannot sign certificates for.
|
||||
- Is C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: ['email:.com']
|
||||
version_added: 1.1.0
|
||||
name_constraints_critical:
|
||||
description:
|
||||
- Whether the C(name_constraints) extension is critical.
|
||||
- Is C(none) if extension is not present.
|
||||
returned: success
|
||||
type: bool
|
||||
version_added: 1.1.0
|
||||
subject:
|
||||
description:
|
||||
- The CSR's subject as a dictionary.
|
||||
@@ -177,6 +156,7 @@ subject_ordered:
|
||||
type: list
|
||||
elements: list
|
||||
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]'
|
||||
version_added: "2.9"
|
||||
public_key:
|
||||
description: CSR's public key in PEM format
|
||||
returned: success
|
||||
@@ -198,6 +178,7 @@ subject_key_identifier:
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
version_added: "2.9"
|
||||
authority_key_identifier:
|
||||
description:
|
||||
- The CSR's authority key identifier.
|
||||
@@ -206,6 +187,7 @@ authority_key_identifier:
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
version_added: "2.9"
|
||||
authority_cert_issuer:
|
||||
description:
|
||||
- The CSR's authority cert issuer as a list of general names.
|
||||
@@ -214,6 +196,7 @@ authority_cert_issuer:
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
version_added: "2.9"
|
||||
authority_cert_serial_number:
|
||||
description:
|
||||
- The CSR's authority cert serial number.
|
||||
@@ -221,6 +204,7 @@ authority_cert_serial_number:
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: int
|
||||
sample: '12345'
|
||||
version_added: "2.9"
|
||||
'''
|
||||
|
||||
|
||||
@@ -228,34 +212,12 @@ import abc
|
||||
import binascii
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_text, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_certificate_request,
|
||||
get_fingerprint_of_bytes,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_decode_name,
|
||||
cryptography_get_extensions_from_csr,
|
||||
cryptography_oid_to_name,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_get_extensions_from_csr,
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_normalize_name_attribute,
|
||||
pyopenssl_parse_name_constraints,
|
||||
)
|
||||
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
@@ -295,7 +257,7 @@ else:
|
||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CertificateSigningRequestInfo(OpenSSLObject):
|
||||
class CertificateSigningRequestInfo(crypto_utils.OpenSSLObject):
|
||||
def __init__(self, module, backend):
|
||||
super(CertificateSigningRequestInfo, self).__init__(
|
||||
module.params['path'] or '',
|
||||
@@ -310,11 +272,11 @@ class CertificateSigningRequestInfo(OpenSSLObject):
|
||||
self.content = self.content.encode('utf-8')
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -341,10 +303,6 @@ class CertificateSigningRequestInfo(OpenSSLObject):
|
||||
def _get_subject_alt_name(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_name_constraints(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_public_key(self, binary):
|
||||
pass
|
||||
@@ -367,7 +325,7 @@ class CertificateSigningRequestInfo(OpenSSLObject):
|
||||
|
||||
def get_info(self):
|
||||
result = dict()
|
||||
self.csr = load_certificate_request(self.path, content=self.content, backend=self.backend)
|
||||
self.csr = crypto_utils.load_certificate_request(self.path, content=self.content, backend=self.backend)
|
||||
|
||||
subject = self._get_subject_ordered()
|
||||
result['subject'] = dict()
|
||||
@@ -379,15 +337,10 @@ class CertificateSigningRequestInfo(OpenSSLObject):
|
||||
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
|
||||
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
|
||||
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
|
||||
(
|
||||
result['name_constraints_permitted'],
|
||||
result['name_constraints_excluded'],
|
||||
result['name_constraints_critical'],
|
||||
) = self._get_name_constraints()
|
||||
|
||||
result['public_key'] = self._get_public_key(binary=False)
|
||||
pk = self._get_public_key(binary=True)
|
||||
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict()
|
||||
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
|
||||
|
||||
if self.backend != 'pyopenssl':
|
||||
ski = self._get_subject_key_identifier()
|
||||
@@ -423,7 +376,7 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
|
||||
def _get_subject_ordered(self):
|
||||
result = []
|
||||
for attribute in self.csr.subject:
|
||||
result.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
result.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
return result
|
||||
|
||||
def _get_key_usage(self):
|
||||
@@ -468,7 +421,7 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
|
||||
try:
|
||||
ext_keyusage_ext = self.csr.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
|
||||
return sorted([
|
||||
cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
|
||||
crypto_utils.cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
|
||||
]), ext_keyusage_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
@@ -502,20 +455,11 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
|
||||
def _get_subject_alt_name(self):
|
||||
try:
|
||||
san_ext = self.csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
result = [cryptography_decode_name(san) for san in san_ext.value]
|
||||
result = [crypto_utils.cryptography_decode_name(san) for san in san_ext.value]
|
||||
return result, san_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_name_constraints(self):
|
||||
try:
|
||||
nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints)
|
||||
permitted = [cryptography_decode_name(san) for san in nc_ext.value.permitted_subtrees or []]
|
||||
excluded = [cryptography_decode_name(san) for san in nc_ext.value.excluded_subtrees or []]
|
||||
return permitted, excluded, nc_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, False
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
return self.csr.public_key().public_bytes(
|
||||
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
|
||||
@@ -534,13 +478,13 @@ class CertificateSigningRequestInfoCryptography(CertificateSigningRequestInfo):
|
||||
ext = self.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
||||
issuer = None
|
||||
if ext.value.authority_cert_issuer is not None:
|
||||
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
|
||||
issuer = [crypto_utils.cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
|
||||
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, None
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return cryptography_get_extensions_from_csr(self.csr)
|
||||
return crypto_utils.cryptography_get_extensions_from_csr(self.csr)
|
||||
|
||||
def _is_signature_valid(self):
|
||||
return self.csr.is_signature_valid
|
||||
@@ -555,7 +499,7 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
|
||||
def __get_name(self, name):
|
||||
result = []
|
||||
for sub in name.get_components():
|
||||
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
result.append([crypto_utils.pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
return result
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
@@ -565,7 +509,7 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
|
||||
for extension in self.csr.get_extensions():
|
||||
if extension.get_short_name() == short_name:
|
||||
result = [
|
||||
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
crypto_utils.pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
]
|
||||
return sorted(result), bool(extension.get_critical())
|
||||
return None, False
|
||||
@@ -593,21 +537,24 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
|
||||
else:
|
||||
return None, False
|
||||
|
||||
def _normalize_san(self, san):
|
||||
# apparently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
|
||||
# although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
|
||||
if san.startswith('IP Address:'):
|
||||
san = 'IP:' + san[len('IP Address:'):]
|
||||
if san.startswith('IP:'):
|
||||
ip = compat_ipaddress.ip_address(san[3:])
|
||||
san = 'IP:{0}'.format(ip.compressed)
|
||||
return san
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
for extension in self.csr.get_extensions():
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
result = [self._normalize_san(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
return result, bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_name_constraints(self):
|
||||
for extension in self.csr.get_extensions():
|
||||
if extension.get_short_name() == b'nameConstraints':
|
||||
permitted, excluded = pyopenssl_parse_name_constraints(extension)
|
||||
return permitted, excluded, bool(extension.get_critical())
|
||||
return None, None, False
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
@@ -637,7 +584,7 @@ class CertificateSigningRequestInfoPyOpenSSL(CertificateSigningRequestInfo):
|
||||
return None, None, None
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return pyopenssl_get_extensions_from_csr(self.csr)
|
||||
return crypto_utils.pyopenssl_get_extensions_from_csr(self.csr)
|
||||
|
||||
def _is_signature_valid(self):
|
||||
try:
|
||||
@@ -700,8 +647,7 @@ def main():
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
certificate = CertificateSigningRequestInfoPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
@@ -711,7 +657,7 @@ def main():
|
||||
|
||||
result = certificate.get_info()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -64,35 +67,33 @@ options:
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, openssl ]
|
||||
version_added: "1.0.0"
|
||||
return_content:
|
||||
description:
|
||||
- If set to C(yes), will return the (current or generated) DH params' content as I(dhparams).
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.0.0"
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: community.crypto.openssl_pkcs12
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: community.crypto.openssl_publickey
|
||||
- module: openssl_certificate
|
||||
- module: openssl_csr
|
||||
- module: openssl_pkcs12
|
||||
- module: openssl_privatekey
|
||||
- module: openssl_publickey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate Diffie-Hellman parameters with the default size (4096 bits)
|
||||
community.crypto.openssl_dhparam:
|
||||
openssl_dhparam:
|
||||
path: /etc/ssl/dhparams.pem
|
||||
|
||||
- name: Generate DH Parameters with a different size (2048 bits)
|
||||
community.crypto.openssl_dhparam:
|
||||
openssl_dhparam:
|
||||
path: /etc/ssl/dhparams.pem
|
||||
size: 2048
|
||||
|
||||
- name: Force regenerate an DH parameters if they already exist
|
||||
community.crypto.openssl_dhparam:
|
||||
openssl_dhparam:
|
||||
path: /etc/ssl/dhparams.pem
|
||||
force: yes
|
||||
'''
|
||||
@@ -117,7 +118,7 @@ dhparams:
|
||||
description: The (current or generated) DH params' content.
|
||||
returned: if I(state) is C(present) and I(return_content) is C(yes)
|
||||
type: str
|
||||
version_added: "1.0.0"
|
||||
version_added: "2.10"
|
||||
'''
|
||||
|
||||
import abc
|
||||
@@ -125,20 +126,12 @@ import os
|
||||
import re
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
|
||||
count_bits,
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '2.0'
|
||||
|
||||
@@ -234,7 +227,7 @@ class DHParameterBase(object):
|
||||
if self.backup_file:
|
||||
result['backup_file'] = self.backup_file
|
||||
if self.return_content:
|
||||
content = load_file_if_exists(self.path, ignore_errors=True)
|
||||
content = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['dhparams'] = content.decode('utf-8') if content else None
|
||||
|
||||
return result
|
||||
@@ -323,7 +316,7 @@ class DHParameterCryptography(DHParameterBase):
|
||||
# Write result
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
write_file(module, result)
|
||||
crypto_utils.write_file(module, result)
|
||||
|
||||
def _check_params_valid(self, module):
|
||||
"""Check if the params are in the correct state"""
|
||||
@@ -335,7 +328,7 @@ class DHParameterCryptography(DHParameterBase):
|
||||
except Exception as dummy:
|
||||
return False
|
||||
# Check parameters
|
||||
bits = count_bits(params.parameter_numbers().p)
|
||||
bits = crypto_utils.count_bits(params.parameter_numbers().p)
|
||||
return bits == self.size
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -95,20 +98,19 @@ options:
|
||||
- If set to C(yes), will return the (current or generated) PKCS#12's content as I(pkcs12).
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.0.0"
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: community.crypto.openssl_dhparam
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: community.crypto.openssl_publickey
|
||||
- module: openssl_certificate
|
||||
- module: openssl_csr
|
||||
- module: openssl_dhparam
|
||||
- module: openssl_privatekey
|
||||
- module: openssl_publickey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate PKCS#12 file
|
||||
community.crypto.openssl_pkcs12:
|
||||
openssl_pkcs12:
|
||||
action: export
|
||||
path: /opt/certs/ansible.p12
|
||||
friendly_name: raclette
|
||||
@@ -118,7 +120,7 @@ EXAMPLES = r'''
|
||||
state: present
|
||||
|
||||
- name: Change PKCS#12 file permission
|
||||
community.crypto.openssl_pkcs12:
|
||||
openssl_pkcs12:
|
||||
action: export
|
||||
path: /opt/certs/ansible.p12
|
||||
friendly_name: raclette
|
||||
@@ -129,7 +131,7 @@ EXAMPLES = r'''
|
||||
mode: '0600'
|
||||
|
||||
- name: Regen PKCS#12 file
|
||||
community.crypto.openssl_pkcs12:
|
||||
openssl_pkcs12:
|
||||
action: export
|
||||
src: /opt/certs/ansible.p12
|
||||
path: /opt/certs/ansible.p12
|
||||
@@ -142,14 +144,14 @@ EXAMPLES = r'''
|
||||
force: yes
|
||||
|
||||
- name: Dump/Parse PKCS#12 file
|
||||
community.crypto.openssl_pkcs12:
|
||||
openssl_pkcs12:
|
||||
action: parse
|
||||
src: /opt/certs/ansible.p12
|
||||
path: /opt/certs/ansible.pem
|
||||
state: present
|
||||
|
||||
- name: Remove PKCS#12 file
|
||||
community.crypto.openssl_pkcs12:
|
||||
openssl_pkcs12:
|
||||
path: /opt/certs/ansible.p12
|
||||
state: absent
|
||||
'''
|
||||
@@ -174,33 +176,14 @@ pkcs12:
|
||||
description: The (current or generated) PKCS#12's content Base64 encoded.
|
||||
returned: if I(state) is C(present) and I(return_content) is C(yes)
|
||||
type: str
|
||||
version_added: "1.0.0"
|
||||
version_added: "2.10"
|
||||
'''
|
||||
|
||||
import base64
|
||||
import os
|
||||
import stat
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
load_certificate,
|
||||
)
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
from OpenSSL import crypto
|
||||
@@ -210,12 +193,16 @@ except ImportError:
|
||||
else:
|
||||
pyopenssl_found = True
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
|
||||
class PkcsError(OpenSSLObjectError):
|
||||
|
||||
class PkcsError(crypto_utils.OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
class Pkcs(OpenSSLObject):
|
||||
class Pkcs(crypto_utils.OpenSSLObject):
|
||||
|
||||
def __init__(self, module):
|
||||
super(Pkcs, self).__init__(
|
||||
@@ -252,10 +239,11 @@ class Pkcs(OpenSSLObject):
|
||||
def _check_pkey_passphrase():
|
||||
if self.privatekey_passphrase:
|
||||
try:
|
||||
load_privatekey(self.privatekey_path, self.privatekey_passphrase)
|
||||
crypto_utils.load_privatekey(self.privatekey_path,
|
||||
self.privatekey_passphrase)
|
||||
except crypto.Error:
|
||||
return False
|
||||
except OpenSSLBadPassphraseError:
|
||||
except crypto_utils.OpenSSLBadPassphraseError:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -319,7 +307,7 @@ class Pkcs(OpenSSLObject):
|
||||
result['backup_file'] = self.backup_file
|
||||
if self.return_content:
|
||||
if self.pkcs12_bytes is None:
|
||||
self.pkcs12_bytes = load_file_if_exists(self.path, ignore_errors=True)
|
||||
self.pkcs12_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['pkcs12'] = base64.b64encode(self.pkcs12_bytes) if self.pkcs12_bytes else None
|
||||
|
||||
return result
|
||||
@@ -329,20 +317,24 @@ class Pkcs(OpenSSLObject):
|
||||
self.pkcs12 = crypto.PKCS12()
|
||||
|
||||
if self.other_certificates:
|
||||
other_certs = [load_certificate(other_cert) for other_cert
|
||||
other_certs = [crypto_utils.load_certificate(other_cert) for other_cert
|
||||
in self.other_certificates]
|
||||
self.pkcs12.set_ca_certificates(other_certs)
|
||||
|
||||
if self.certificate_path:
|
||||
self.pkcs12.set_certificate(load_certificate(self.certificate_path))
|
||||
self.pkcs12.set_certificate(crypto_utils.load_certificate(
|
||||
self.certificate_path))
|
||||
|
||||
if self.friendly_name:
|
||||
self.pkcs12.set_friendlyname(to_bytes(self.friendly_name))
|
||||
|
||||
if self.privatekey_path:
|
||||
try:
|
||||
self.pkcs12.set_privatekey(load_privatekey(self.privatekey_path, self.privatekey_passphrase))
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
self.pkcs12.set_privatekey(crypto_utils.load_privatekey(
|
||||
self.privatekey_path,
|
||||
self.privatekey_passphrase)
|
||||
)
|
||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||
raise PkcsError(exc)
|
||||
|
||||
return self.pkcs12.export(self.passphrase, self.iter_size, self.maciter_size)
|
||||
@@ -380,7 +372,7 @@ class Pkcs(OpenSSLObject):
|
||||
"""Write the PKCS#12 file."""
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
write_file(module, content, mode)
|
||||
crypto_utils.write_file(module, content, mode)
|
||||
if self.return_content:
|
||||
self.pkcs12_bytes = content
|
||||
|
||||
@@ -467,7 +459,7 @@ def main():
|
||||
result['mode'] = file_mode
|
||||
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -24,7 +27,7 @@ description:
|
||||
(or specify none), change the keysize, etc., the private key will be
|
||||
regenerated. If you are concerned that this could **overwrite your private key**,
|
||||
consider using the I(backup) option."
|
||||
- "The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the
|
||||
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
|
||||
@@ -110,7 +113,7 @@ options:
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
@@ -129,7 +132,6 @@ options:
|
||||
type: str
|
||||
default: auto_ignore
|
||||
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
|
||||
version_added: '1.0.0'
|
||||
format_mismatch:
|
||||
description:
|
||||
- Determines behavior of the module if the format of a private key does not match the expected format, but all
|
||||
@@ -140,7 +142,6 @@ options:
|
||||
type: str
|
||||
default: regenerate
|
||||
choices: [ regenerate, convert ]
|
||||
version_added: '1.0.0'
|
||||
backup:
|
||||
description:
|
||||
- Create a backup file including a timestamp so you can get
|
||||
@@ -154,7 +155,6 @@ options:
|
||||
value is treated appropriately and not accidentally written to logs etc.! Use with care!
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '1.0.0'
|
||||
regenerate:
|
||||
description:
|
||||
- Allows to configure in which situations the module is allowed to regenerate private keys.
|
||||
@@ -187,40 +187,39 @@ options:
|
||||
- full_idempotence
|
||||
- always
|
||||
default: full_idempotence
|
||||
version_added: '1.0.0'
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: community.crypto.openssl_dhparam
|
||||
- module: community.crypto.openssl_pkcs12
|
||||
- module: community.crypto.openssl_publickey
|
||||
- module: openssl_certificate
|
||||
- module: openssl_csr
|
||||
- module: openssl_dhparam
|
||||
- module: openssl_pkcs12
|
||||
- module: openssl_publickey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA)
|
||||
community.crypto.openssl_privatekey:
|
||||
openssl_privatekey:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
|
||||
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA) and a passphrase
|
||||
community.crypto.openssl_privatekey:
|
||||
openssl_privatekey:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
passphrase: ansible
|
||||
cipher: aes256
|
||||
|
||||
- name: Generate an OpenSSL private key with a different size (2048 bits)
|
||||
community.crypto.openssl_privatekey:
|
||||
openssl_privatekey:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
size: 2048
|
||||
|
||||
- name: Force regenerate an OpenSSL private key if it already exists
|
||||
community.crypto.openssl_privatekey:
|
||||
openssl_privatekey:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
force: yes
|
||||
|
||||
- name: Generate an OpenSSL private key with a different algorithm (DSA)
|
||||
community.crypto.openssl_privatekey:
|
||||
openssl_privatekey:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
type: DSA
|
||||
'''
|
||||
@@ -270,45 +269,15 @@ privatekey:
|
||||
- Will be Base64-encoded if the key is in raw format.
|
||||
returned: if I(state) is C(present) and I(return_content) is C(yes)
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
version_added: "2.10"
|
||||
'''
|
||||
|
||||
import abc
|
||||
import base64
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
get_fingerprint,
|
||||
get_fingerprint_of_bytes,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
|
||||
identify_private_key_format,
|
||||
)
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.6'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
|
||||
@@ -340,12 +309,24 @@ except ImportError:
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto import (
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
)
|
||||
|
||||
class PrivateKeyError(OpenSSLObjectError):
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class PrivateKeyError(crypto_utils.OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
class PrivateKeyBase(OpenSSLObject):
|
||||
class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
||||
|
||||
def __init__(self, module):
|
||||
super(PrivateKeyBase, self).__init__(
|
||||
@@ -403,7 +384,7 @@ class PrivateKeyBase(OpenSSLObject):
|
||||
privatekey_data = self._get_private_key_data()
|
||||
if self.return_content:
|
||||
self.privatekey_bytes = privatekey_data
|
||||
write_file(module, privatekey_data, 0o600)
|
||||
crypto_utils.write_file(module, privatekey_data, 0o600)
|
||||
self.changed = True
|
||||
elif not self.check(module, perms_required=False, ignore_conversion=False):
|
||||
# Convert
|
||||
@@ -413,7 +394,7 @@ class PrivateKeyBase(OpenSSLObject):
|
||||
privatekey_data = self._get_private_key_data()
|
||||
if self.return_content:
|
||||
self.privatekey_bytes = privatekey_data
|
||||
write_file(module, privatekey_data, 0o600)
|
||||
crypto_utils.write_file(module, privatekey_data, 0o600)
|
||||
self.changed = True
|
||||
|
||||
self.fingerprint = self._get_fingerprint()
|
||||
@@ -491,9 +472,9 @@ class PrivateKeyBase(OpenSSLObject):
|
||||
result['backup_file'] = self.backup_file
|
||||
if self.return_content:
|
||||
if self.privatekey_bytes is None:
|
||||
self.privatekey_bytes = load_file_if_exists(self.path, ignore_errors=True)
|
||||
self.privatekey_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
|
||||
if self.privatekey_bytes:
|
||||
if identify_private_key_format(self.privatekey_bytes) == 'raw':
|
||||
if crypto_utils.identify_private_key_format(self.privatekey_bytes) == 'raw':
|
||||
result['privatekey'] = base64.b64encode(self.privatekey_bytes)
|
||||
else:
|
||||
result['privatekey'] = self.privatekey_bytes.decode('utf-8')
|
||||
@@ -531,8 +512,8 @@ class PrivateKeyPyOpenSSL(PrivateKeyBase):
|
||||
"""Make sure that the private key has been loaded."""
|
||||
if self.privatekey is None:
|
||||
try:
|
||||
self.privatekey = privatekey = load_privatekey(self.path, self.passphrase)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
self.privatekey = privatekey = crypto_utils.load_privatekey(self.path, self.passphrase)
|
||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||
raise PrivateKeyError(exc)
|
||||
|
||||
def _get_private_key_data(self):
|
||||
@@ -544,11 +525,11 @@ class PrivateKeyPyOpenSSL(PrivateKeyBase):
|
||||
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.privatekey)
|
||||
|
||||
def _get_fingerprint(self):
|
||||
return get_fingerprint(self.path, self.passphrase)
|
||||
return crypto_utils.get_fingerprint(self.path, self.passphrase)
|
||||
|
||||
def _check_passphrase(self):
|
||||
try:
|
||||
load_privatekey(self.path, self.passphrase)
|
||||
crypto_utils.load_privatekey(self.path, self.passphrase)
|
||||
return True
|
||||
except Exception as dummy:
|
||||
return False
|
||||
@@ -737,7 +718,7 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||
with open(self.path, 'rb') as f:
|
||||
data = f.read()
|
||||
# Interpret bytes depending on format.
|
||||
format = identify_private_key_format(data)
|
||||
format = crypto_utils.identify_private_key_format(data)
|
||||
if format == 'raw':
|
||||
if len(data) == 56 and CRYPTOGRAPHY_HAS_X448:
|
||||
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data)
|
||||
@@ -772,13 +753,13 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||
cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
# Get fingerprints of public_key_bytes
|
||||
return get_fingerprint_of_bytes(public_key_bytes)
|
||||
return crypto_utils.get_fingerprint_of_bytes(public_key_bytes)
|
||||
|
||||
def _check_passphrase(self):
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
data = f.read()
|
||||
format = identify_private_key_format(data)
|
||||
format = crypto_utils.identify_private_key_format(data)
|
||||
if format == 'raw':
|
||||
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
|
||||
# actually load the key (and return False when this fails).
|
||||
@@ -825,7 +806,7 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
content = f.read()
|
||||
format = identify_private_key_format(content)
|
||||
format = crypto_utils.identify_private_key_format(content)
|
||||
return format == self._get_wanted_format()
|
||||
except Exception as dummy:
|
||||
return False
|
||||
@@ -917,8 +898,7 @@ def main():
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
private_key = PrivateKeyPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
@@ -945,7 +925,7 @@ def main():
|
||||
|
||||
result = private_key.dump()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -23,7 +26,7 @@ description:
|
||||
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
|
||||
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
|
||||
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
|
||||
and will be removed in community.crypto 2.0.0.
|
||||
and will be removed in Ansible 2.13.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.2.3
|
||||
author:
|
||||
@@ -39,7 +42,6 @@ options:
|
||||
- Content of the private key file.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
passphrase:
|
||||
description:
|
||||
- The passphrase for the private key.
|
||||
@@ -59,23 +61,23 @@ options:
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in Ansible 2.13.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
|
||||
seealso:
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: openssl_privatekey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate an OpenSSL private key with the default values (4096 bits, RSA)
|
||||
community.crypto.openssl_privatekey:
|
||||
openssl_privatekey:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
|
||||
- name: Get information on generated key
|
||||
community.crypto.openssl_privatekey_info:
|
||||
openssl_privatekey_info:
|
||||
path: /etc/ssl/private/ansible.com.pem
|
||||
register: result
|
||||
|
||||
@@ -137,32 +139,12 @@ private_data:
|
||||
import abc
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
CRYPTOGRAPHY_HAS_X25519,
|
||||
CRYPTOGRAPHY_HAS_X448,
|
||||
CRYPTOGRAPHY_HAS_ED25519,
|
||||
CRYPTOGRAPHY_HAS_ED448,
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
get_fingerprint_of_bytes,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
|
||||
binary_exp_mod,
|
||||
quick_is_not_prime,
|
||||
)
|
||||
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
@@ -182,6 +164,26 @@ try:
|
||||
import cryptography
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
try:
|
||||
import cryptography.hazmat.primitives.asymmetric.x25519
|
||||
CRYPTOGRAPHY_HAS_X25519 = True
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_X25519 = False
|
||||
try:
|
||||
import cryptography.hazmat.primitives.asymmetric.x448
|
||||
CRYPTOGRAPHY_HAS_X448 = True
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_X448 = False
|
||||
try:
|
||||
import cryptography.hazmat.primitives.asymmetric.ed25519
|
||||
CRYPTOGRAPHY_HAS_ED25519 = True
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_ED25519 = False
|
||||
try:
|
||||
import cryptography.hazmat.primitives.asymmetric.ed448
|
||||
CRYPTOGRAPHY_HAS_ED448 = True
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_HAS_ED448 = False
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_FOUND = False
|
||||
@@ -250,13 +252,13 @@ def _check_dsa_consistency(key_public_data, key_private_data):
|
||||
if (p - 1) % q != 0:
|
||||
return False
|
||||
# Check that g**q mod p == 1
|
||||
if binary_exp_mod(g, q, p) != 1:
|
||||
if crypto_utils.binary_exp_mod(g, q, p) != 1:
|
||||
return False
|
||||
# Check whether g**x mod p == y
|
||||
if binary_exp_mod(g, x, p) != y:
|
||||
if crypto_utils.binary_exp_mod(g, x, p) != y:
|
||||
return False
|
||||
# Check (quickly) whether p or q are not primes
|
||||
if quick_is_not_prime(q) or quick_is_not_prime(p):
|
||||
if crypto_utils.quick_is_not_prime(q) or crypto_utils.quick_is_not_prime(p):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -316,7 +318,7 @@ def _is_cryptography_key_consistent(key, key_public_data, key_private_data):
|
||||
return None
|
||||
|
||||
|
||||
class PrivateKeyInfo(OpenSSLObject):
|
||||
class PrivateKeyInfo(crypto_utils.OpenSSLObject):
|
||||
def __init__(self, module, backend):
|
||||
super(PrivateKeyInfo, self).__init__(
|
||||
module.params['path'] or '',
|
||||
@@ -332,11 +334,11 @@ class PrivateKeyInfo(OpenSSLObject):
|
||||
self.return_private_key_data = module.params['return_private_key_data']
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
@@ -368,19 +370,19 @@ class PrivateKeyInfo(OpenSSLObject):
|
||||
except (IOError, OSError) as exc:
|
||||
self.module.fail_json(msg=to_native(exc), **result)
|
||||
try:
|
||||
self.key = load_privatekey(
|
||||
self.key = crypto_utils.load_privatekey(
|
||||
path=None,
|
||||
content=priv_key_detail,
|
||||
passphrase=to_bytes(self.passphrase) if self.passphrase is not None else self.passphrase,
|
||||
backend=self.backend
|
||||
)
|
||||
result['can_parse_key'] = True
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
self.module.fail_json(msg=to_native(exc), **result)
|
||||
|
||||
result['public_key'] = self._get_public_key(binary=False)
|
||||
pk = self._get_public_key(binary=True)
|
||||
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict()
|
||||
result['public_key_fingerprints'] = crypto_utils.get_fingerprint_of_bytes(pk) if pk is not None else dict()
|
||||
|
||||
key_type, key_public_data, key_private_data = self._get_key_info()
|
||||
result['type'] = key_type
|
||||
@@ -629,8 +631,7 @@ def main():
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
privatekey = PrivateKeyInfoPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
@@ -640,7 +641,7 @@ def main():
|
||||
|
||||
result = privatekey.get_info()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
@@ -15,11 +18,11 @@ short_description: Generate an OpenSSL public key from its private key.
|
||||
description:
|
||||
- This module allows one to (re)generate OpenSSL public keys from their private keys.
|
||||
- Keys are generated in PEM or OpenSSH format.
|
||||
- "The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. When I(format) is C(OpenSSH),
|
||||
the C(cryptography) backend has to be used. Please note that the PyOpenSSL backend
|
||||
was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
|
||||
was deprecated in Ansible 2.9 and will be removed in Ansible 2.13."
|
||||
requirements:
|
||||
- Either cryptography >= 1.2.3 (older versions might work as well)
|
||||
- Or pyOpenSSL >= 16.0.0
|
||||
@@ -62,7 +65,6 @@ options:
|
||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
||||
If I(state) is C(present), one of them is required.
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
privatekey_passphrase:
|
||||
description:
|
||||
- The passphrase for the private key.
|
||||
@@ -87,48 +89,47 @@ options:
|
||||
- If set to C(yes), will return the (current or generated) public key's content as I(publickey).
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '1.0.0'
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
- module: community.crypto.openssl_csr
|
||||
- module: community.crypto.openssl_dhparam
|
||||
- module: community.crypto.openssl_pkcs12
|
||||
- module: community.crypto.openssl_privatekey
|
||||
- module: openssl_certificate
|
||||
- module: openssl_csr
|
||||
- module: openssl_dhparam
|
||||
- module: openssl_pkcs12
|
||||
- module: openssl_privatekey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate an OpenSSL public key in PEM format
|
||||
community.crypto.openssl_publickey:
|
||||
openssl_publickey:
|
||||
path: /etc/ssl/public/ansible.com.pem
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
|
||||
- name: Generate an OpenSSL public key in PEM format from an inline key
|
||||
community.crypto.openssl_publickey:
|
||||
openssl_publickey:
|
||||
path: /etc/ssl/public/ansible.com.pem
|
||||
privatekey_content: "{{ private_key_content }}"
|
||||
|
||||
- name: Generate an OpenSSL public key in OpenSSH v2 format
|
||||
community.crypto.openssl_publickey:
|
||||
openssl_publickey:
|
||||
path: /etc/ssl/public/ansible.com.pem
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
format: OpenSSH
|
||||
|
||||
- name: Generate an OpenSSL public key with a passphrase protected private key
|
||||
community.crypto.openssl_publickey:
|
||||
openssl_publickey:
|
||||
path: /etc/ssl/public/ansible.com.pem
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
privatekey_passphrase: ansible
|
||||
|
||||
- name: Force regenerate an OpenSSL public key if it already exists
|
||||
community.crypto.openssl_publickey:
|
||||
openssl_publickey:
|
||||
path: /etc/ssl/public/ansible.com.pem
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
force: yes
|
||||
|
||||
- name: Remove an OpenSSL public key
|
||||
community.crypto.openssl_publickey:
|
||||
openssl_publickey:
|
||||
path: /etc/ssl/public/ansible.com.pem
|
||||
state: absent
|
||||
'''
|
||||
@@ -173,33 +174,13 @@ publickey:
|
||||
description: The (current or generated) public key's content.
|
||||
returned: if I(state) is C(present) and I(return_content) is C(yes)
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
version_added: "2.10"
|
||||
'''
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
load_file_if_exists,
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
get_fingerprint,
|
||||
)
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '16.0.0'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION_OPENSSH = '1.4'
|
||||
@@ -227,12 +208,16 @@ except ImportError:
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
class PublicKeyError(OpenSSLObjectError):
|
||||
|
||||
class PublicKeyError(crypto_utils.OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
class PublicKey(OpenSSLObject):
|
||||
class PublicKey(crypto_utils.OpenSSLObject):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(PublicKey, self).__init__(
|
||||
@@ -257,7 +242,7 @@ class PublicKey(OpenSSLObject):
|
||||
self.backup_file = None
|
||||
|
||||
def _create_publickey(self, module):
|
||||
self.privatekey = load_privatekey(
|
||||
self.privatekey = crypto_utils.load_privatekey(
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
@@ -296,15 +281,15 @@ class PublicKey(OpenSSLObject):
|
||||
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
write_file(module, publickey_content)
|
||||
crypto_utils.write_file(module, publickey_content)
|
||||
|
||||
self.changed = True
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||
raise PublicKeyError(exc)
|
||||
except (IOError, OSError) as exc:
|
||||
raise PublicKeyError(exc)
|
||||
|
||||
self.fingerprint = get_fingerprint(
|
||||
self.fingerprint = crypto_utils.get_fingerprint(
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
@@ -352,7 +337,7 @@ class PublicKey(OpenSSLObject):
|
||||
|
||||
try:
|
||||
desired_publickey = self._create_publickey(module)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||
raise PublicKeyError(exc)
|
||||
|
||||
return publickey_content == desired_publickey
|
||||
@@ -381,7 +366,7 @@ class PublicKey(OpenSSLObject):
|
||||
result['backup_file'] = self.backup_file
|
||||
if self.return_content:
|
||||
if self.publickey_bytes is None:
|
||||
self.publickey_bytes = load_file_if_exists(self.path, ignore_errors=True)
|
||||
self.publickey_bytes = crypto_utils.load_file_if_exists(self.path, ignore_errors=True)
|
||||
result['publickey'] = self.publickey_bytes.decode('utf-8') if self.publickey_bytes else None
|
||||
|
||||
return result
|
||||
@@ -445,8 +430,7 @@ def main():
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated', version='2.13')
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(minimal_cryptography_version)),
|
||||
@@ -479,7 +463,7 @@ def main():
|
||||
|
||||
result = public_key.dump()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -1,322 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Patrick Pichler <ppichler+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: openssl_signature
|
||||
version_added: 1.1.0
|
||||
short_description: Sign data with openssl
|
||||
description:
|
||||
- This module allows one to sign data using a private key.
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL backend
|
||||
was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
|
||||
requirements:
|
||||
- Either cryptography >= 1.4 (some key types require newer versions)
|
||||
- Or pyOpenSSL >= 0.11 (Ed25519 and Ed448 keys are not supported with this backend)
|
||||
author:
|
||||
- Patrick Pichler (@aveexy)
|
||||
- Markus Teufelberger (@MarkusTeufelberger)
|
||||
options:
|
||||
privatekey_path:
|
||||
description:
|
||||
- The path to the private key to use when signing.
|
||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
||||
type: path
|
||||
privatekey_content:
|
||||
description:
|
||||
- The content of the private key to use when signing the certificate signing request.
|
||||
- Either I(privatekey_path) or I(privatekey_content) must be specified, but not both.
|
||||
type: str
|
||||
privatekey_passphrase:
|
||||
description:
|
||||
- The passphrase for the private key.
|
||||
- This is required if the private key is password protected.
|
||||
type: str
|
||||
path:
|
||||
description:
|
||||
- The file to sign.
|
||||
- This file will only be read and not modified.
|
||||
type: path
|
||||
required: true
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
notes:
|
||||
- |
|
||||
When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version:
|
||||
RSA keys: C(cryptography) >= 1.4
|
||||
DSA and ECDSA keys: C(cryptography) >= 1.5
|
||||
ed448 and ed25519 keys: C(cryptography) >= 2.6
|
||||
seealso:
|
||||
- module: community.crypto.openssl_signature_info
|
||||
- module: community.crypto.openssl_privatekey
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Sign example file
|
||||
community.crypto.openssl_signature:
|
||||
privatekey_path: private.key
|
||||
path: /tmp/example_file
|
||||
register: sig
|
||||
|
||||
- name: Verify signature of example file
|
||||
community.crypto.openssl_signature_info:
|
||||
certificate_path: cert.pem
|
||||
path: /tmp/example_file
|
||||
signature: "{{ sig.signature }}"
|
||||
register: verify
|
||||
|
||||
- name: Make sure the signature is valid
|
||||
assert:
|
||||
that:
|
||||
- verify.valid
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
signature:
|
||||
description: Base64 encoded signature.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
import os
|
||||
import traceback
|
||||
from distutils.version import LooseVersion
|
||||
import base64
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.11'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except ImportError:
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.hazmat.primitives.asymmetric.padding
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_FOUND = False
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN,
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN,
|
||||
CRYPTOGRAPHY_HAS_ED25519_SIGN,
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN,
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
)
|
||||
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class SignatureBase(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureBase, self).__init__(
|
||||
path=module.params['path'],
|
||||
state='present',
|
||||
force=False,
|
||||
check_mode=module.check_mode
|
||||
)
|
||||
|
||||
self.backend = backend
|
||||
|
||||
self.privatekey_path = module.params['privatekey_path']
|
||||
self.privatekey_content = module.params['privatekey_content']
|
||||
if self.privatekey_content is not None:
|
||||
self.privatekey_content = self.privatekey_content.encode('utf-8')
|
||||
self.privatekey_passphrase = module.params['privatekey_passphrase']
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
|
||||
# Implementation with using pyOpenSSL
|
||||
class SignaturePyOpenSSL(SignatureBase):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignaturePyOpenSSL, self).__init__(module, backend)
|
||||
|
||||
def run(self):
|
||||
|
||||
result = dict()
|
||||
|
||||
try:
|
||||
with open(self.path, "rb") as f:
|
||||
_in = f.read()
|
||||
|
||||
private_key = load_privatekey(
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend=self.backend,
|
||||
)
|
||||
|
||||
signature = OpenSSL.crypto.sign(private_key, _in, "sha256")
|
||||
result['signature'] = base64.b64encode(signature)
|
||||
return result
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(e)
|
||||
|
||||
|
||||
# Implementation with using cryptography
|
||||
class SignatureCryptography(SignatureBase):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureCryptography, self).__init__(module, backend)
|
||||
|
||||
def run(self):
|
||||
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
||||
_hash = cryptography.hazmat.primitives.hashes.SHA256()
|
||||
|
||||
result = dict()
|
||||
|
||||
try:
|
||||
with open(self.path, "rb") as f:
|
||||
_in = f.read()
|
||||
|
||||
private_key = load_privatekey(
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend=self.backend,
|
||||
)
|
||||
|
||||
signature = None
|
||||
|
||||
if CRYPTOGRAPHY_HAS_DSA_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||
signature = private_key.sign(_in, _hash)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_EC_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||
signature = private_key.sign(_in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash))
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED25519_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
|
||||
signature = private_key.sign(_in)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED448_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
|
||||
signature = private_key.sign(_in)
|
||||
|
||||
if CRYPTOGRAPHY_HAS_RSA_SIGN:
|
||||
if isinstance(private_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||
signature = private_key.sign(_in, _padding, _hash)
|
||||
|
||||
if signature is None:
|
||||
self.module.fail_json(
|
||||
msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
|
||||
result['signature'] = base64.b64encode(signature)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(e)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str'),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
path=dict(type='path', required=True),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
),
|
||||
required_one_of=(
|
||||
['privatekey_path', 'privatekey_content'],
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not os.path.isfile(module.params['path']):
|
||||
module.fail_json(
|
||||
name=module.params['path'],
|
||||
msg='The file {0} does not exist'.format(module.params['path'])
|
||||
)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# Decision
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
try:
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
_sign = SignaturePyOpenSSL(module, backend)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
_sign = SignatureCryptography(module, backend)
|
||||
|
||||
result = _sign.run()
|
||||
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,354 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2019, Patrick Pichler <ppichler+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: openssl_signature_info
|
||||
version_added: 1.1.0
|
||||
short_description: Verify signatures with openssl
|
||||
description:
|
||||
- This module allows one to verify a signature for a file via a certificate.
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option. Please note that the PyOpenSSL backend
|
||||
was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
|
||||
requirements:
|
||||
- Either cryptography >= 1.4 (some key types require newer versions)
|
||||
- Or pyOpenSSL >= 0.11 (Ed25519 and Ed448 keys are not supported with this backend)
|
||||
author:
|
||||
- Patrick Pichler (@aveexy)
|
||||
- Markus Teufelberger (@MarkusTeufelberger)
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- The signed file to verify.
|
||||
- This file will only be read and not modified.
|
||||
type: path
|
||||
required: true
|
||||
certificate_path:
|
||||
description:
|
||||
- The path to the certificate used to verify the signature.
|
||||
- Either I(certificate_path) or I(certificate_content) must be specified, but not both.
|
||||
type: path
|
||||
certificate_content:
|
||||
description:
|
||||
- The content of the certificate used to verify the signature.
|
||||
- Either I(certificate_path) or I(certificate_content) must be specified, but not both.
|
||||
type: str
|
||||
signature:
|
||||
description: Base64 encoded signature.
|
||||
type: str
|
||||
required: true
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
notes:
|
||||
- |
|
||||
When using the C(cryptography) backend, the following key types require at least the following C(cryptography) version:
|
||||
RSA keys: C(cryptography) >= 1.4
|
||||
DSA and ECDSA keys: C(cryptography) >= 1.5
|
||||
ed448 and ed25519 keys: C(cryptography) >= 2.6
|
||||
seealso:
|
||||
- module: community.crypto.openssl_signature
|
||||
- module: community.crypto.x509_certificate
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Sign example file
|
||||
community.crypto.openssl_signature:
|
||||
privatekey_path: private.key
|
||||
path: /tmp/example_file
|
||||
register: sig
|
||||
|
||||
- name: Verify signature of example file
|
||||
community.crypto.openssl_signature_info:
|
||||
certificate_path: cert.pem
|
||||
path: /tmp/example_file
|
||||
signature: "{{ sig.signature }}"
|
||||
register: verify
|
||||
|
||||
- name: Make sure the signature is valid
|
||||
assert:
|
||||
that:
|
||||
- verify.valid
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
valid:
|
||||
description: C(true) means the signature was valid for the given file, C(false) means it wasn't.
|
||||
returned: success
|
||||
type: bool
|
||||
'''
|
||||
|
||||
import os
|
||||
import traceback
|
||||
from distutils.version import LooseVersion
|
||||
import base64
|
||||
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.11'
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
except ImportError:
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
import cryptography.hazmat.primitives.asymmetric.padding
|
||||
import cryptography.hazmat.primitives.hashes
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_FOUND = False
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
CRYPTOGRAPHY_HAS_DSA_SIGN,
|
||||
CRYPTOGRAPHY_HAS_EC_SIGN,
|
||||
CRYPTOGRAPHY_HAS_ED25519_SIGN,
|
||||
CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||
CRYPTOGRAPHY_HAS_RSA_SIGN,
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_certificate,
|
||||
)
|
||||
|
||||
from ansible.module_utils._text import to_native, to_bytes
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
|
||||
class SignatureInfoBase(OpenSSLObject):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureInfoBase, self).__init__(
|
||||
path=module.params['path'],
|
||||
state='present',
|
||||
force=False,
|
||||
check_mode=module.check_mode
|
||||
)
|
||||
|
||||
self.backend = backend
|
||||
|
||||
self.signature = module.params['signature']
|
||||
self.certificate_path = module.params['certificate_path']
|
||||
self.certificate_content = module.params['certificate_content']
|
||||
if self.certificate_content is not None:
|
||||
self.certificate_content = self.certificate_content.encode('utf-8')
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
|
||||
# Implementation with using pyOpenSSL
|
||||
class SignatureInfoPyOpenSSL(SignatureInfoBase):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureInfoPyOpenSSL, self).__init__(module, backend)
|
||||
|
||||
def run(self):
|
||||
|
||||
result = dict()
|
||||
|
||||
try:
|
||||
with open(self.path, "rb") as f:
|
||||
_in = f.read()
|
||||
|
||||
_signature = base64.b64decode(self.signature)
|
||||
certificate = load_certificate(
|
||||
path=self.certificate_path,
|
||||
content=self.certificate_content,
|
||||
backend=self.backend,
|
||||
)
|
||||
|
||||
try:
|
||||
OpenSSL.crypto.verify(certificate, _signature, _in, 'sha256')
|
||||
result['valid'] = True
|
||||
except Exception:
|
||||
result['valid'] = False
|
||||
return result
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(e)
|
||||
|
||||
|
||||
# Implementation with using cryptography
|
||||
class SignatureInfoCryptography(SignatureInfoBase):
|
||||
|
||||
def __init__(self, module, backend):
|
||||
super(SignatureInfoCryptography, self).__init__(module, backend)
|
||||
|
||||
def run(self):
|
||||
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
||||
_hash = cryptography.hazmat.primitives.hashes.SHA256()
|
||||
|
||||
result = dict()
|
||||
|
||||
try:
|
||||
with open(self.path, "rb") as f:
|
||||
_in = f.read()
|
||||
|
||||
_signature = base64.b64decode(self.signature)
|
||||
certificate = load_certificate(
|
||||
path=self.certificate_path,
|
||||
content=self.certificate_content,
|
||||
backend=self.backend,
|
||||
)
|
||||
public_key = certificate.public_key()
|
||||
verified = False
|
||||
valid = False
|
||||
|
||||
if CRYPTOGRAPHY_HAS_DSA_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey):
|
||||
public_key.verify(_signature, _in, _hash)
|
||||
verified = True
|
||||
valid = True
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
verified = True
|
||||
valid = False
|
||||
|
||||
if CRYPTOGRAPHY_HAS_EC_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
|
||||
public_key.verify(_signature, _in, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(_hash))
|
||||
verified = True
|
||||
valid = True
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
verified = True
|
||||
valid = False
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED25519_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
|
||||
public_key.verify(_signature, _in)
|
||||
verified = True
|
||||
valid = True
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
verified = True
|
||||
valid = False
|
||||
|
||||
if CRYPTOGRAPHY_HAS_ED448_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
|
||||
public_key.verify(_signature, _in)
|
||||
verified = True
|
||||
valid = True
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
verified = True
|
||||
valid = False
|
||||
|
||||
if CRYPTOGRAPHY_HAS_RSA_SIGN:
|
||||
try:
|
||||
if isinstance(public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
|
||||
public_key.verify(_signature, _in, _padding, _hash)
|
||||
verified = True
|
||||
valid = True
|
||||
except cryptography.exceptions.InvalidSignature:
|
||||
verified = True
|
||||
valid = False
|
||||
|
||||
if not verified:
|
||||
self.module.fail_json(
|
||||
msg="Unsupported key type. Your cryptography version is {0}".format(CRYPTOGRAPHY_VERSION)
|
||||
)
|
||||
result['valid'] = valid
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
raise OpenSSLObjectError(e)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
certificate_path=dict(type='path'),
|
||||
certificate_content=dict(type='str'),
|
||||
path=dict(type='path', required=True),
|
||||
signature=dict(type='str', required=True),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
),
|
||||
required_one_of=(
|
||||
['certificate_path', 'certificate_content'],
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not os.path.isfile(module.params['path']):
|
||||
module.fail_json(
|
||||
name=module.params['path'],
|
||||
msg='The file {0} does not exist'.format(module.params['path'])
|
||||
)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# Decision
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
try:
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
_sign = SignatureInfoPyOpenSSL(module, backend)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
_sign = SignatureInfoCryptography(module, backend)
|
||||
|
||||
result = _sign.run()
|
||||
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,881 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
|
||||
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: x509_certificate_info
|
||||
short_description: Provide information of OpenSSL X.509 certificates
|
||||
description:
|
||||
- This module allows one to query information on OpenSSL certificates.
|
||||
- It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. If both the
|
||||
cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
|
||||
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with
|
||||
C(select_crypto_backend)). Please note that the PyOpenSSL backend was deprecated in Ansible 2.9
|
||||
and will be removed in community.crypto 2.0.0.
|
||||
- Note that this module was called C(openssl_certificate_info) when included directly in Ansible
|
||||
up to version 2.9. When moved to the collection C(community.crypto), it was renamed to
|
||||
M(community.crypto.x509_certificate_info). From Ansible 2.10 on, it can still be used by the
|
||||
old short name (or by C(ansible.builtin.openssl_certificate_info)), which redirects to
|
||||
C(community.crypto.x509_certificate_info). When using FQCNs or when using the
|
||||
L(collections,https://docs.ansible.com/ansible/latest/user_guide/collections_using.html#using-collections-in-a-playbook)
|
||||
keyword, the new name M(community.crypto.x509_certificate_info) should be used to avoid
|
||||
a deprecation warning.
|
||||
requirements:
|
||||
- PyOpenSSL >= 0.15 or cryptography >= 1.6
|
||||
author:
|
||||
- Felix Fontein (@felixfontein)
|
||||
- Yanis Guenane (@Spredzy)
|
||||
- Markus Teufelberger (@MarkusTeufelberger)
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Remote absolute path where the certificate file is loaded from.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: path
|
||||
content:
|
||||
description:
|
||||
- Content of the X.509 certificate in PEM format.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: str
|
||||
version_added: '1.0.0'
|
||||
valid_at:
|
||||
description:
|
||||
- A dict of names mapping to time specifications. Every time specified here
|
||||
will be checked whether the certificate is valid at this point. See the
|
||||
C(valid_at) return value for informations on the result.
|
||||
- Time can be specified either as relative time or as absolute timestamp.
|
||||
- Time will always be interpreted as UTC.
|
||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h), and ASN.1 TIME (i.e. pattern C(YYYYMMDDHHMMSSZ)).
|
||||
Note that all timestamps will be treated as being in UTC.
|
||||
type: dict
|
||||
select_crypto_backend:
|
||||
description:
|
||||
- Determines which crypto backend to use.
|
||||
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
|
||||
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
|
||||
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
|
||||
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
|
||||
From that point on, only the C(cryptography) backend will be available.
|
||||
type: str
|
||||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
|
||||
notes:
|
||||
- All timestamp values are provided in ASN.1 TIME format, i.e. following the C(YYYYMMDDHHMMSSZ) pattern.
|
||||
They are all in UTC.
|
||||
seealso:
|
||||
- module: community.crypto.x509_certificate
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate a Self Signed OpenSSL certificate
|
||||
community.crypto.x509_certificate:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||
csr_path: /etc/ssl/csr/ansible.com.csr
|
||||
provider: selfsigned
|
||||
|
||||
|
||||
# Get information on the certificate
|
||||
|
||||
- name: Get information on generated certificate
|
||||
community.crypto.x509_certificate_info:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
register: result
|
||||
|
||||
- name: Dump information
|
||||
debug:
|
||||
var: result
|
||||
|
||||
|
||||
# Check whether the certificate is valid or not valid at certain times, fail
|
||||
# if this is not the case. The first task (x509_certificate_info) collects
|
||||
# the information, and the second task (assert) validates the result and
|
||||
# makes the playbook fail in case something is not as expected.
|
||||
|
||||
- name: Test whether that certificate is valid tomorrow and/or in three weeks
|
||||
community.crypto.x509_certificate_info:
|
||||
path: /etc/ssl/crt/ansible.com.crt
|
||||
valid_at:
|
||||
point_1: "+1d"
|
||||
point_2: "+3w"
|
||||
register: result
|
||||
|
||||
- name: Validate that certificate is valid tomorrow, but not in three weeks
|
||||
assert:
|
||||
that:
|
||||
- result.valid_at.point_1 # valid in one day
|
||||
- not result.valid_at.point_2 # not valid in three weeks
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
expired:
|
||||
description: Whether the certificate is expired (i.e. C(notAfter) is in the past)
|
||||
returned: success
|
||||
type: bool
|
||||
basic_constraints:
|
||||
description: Entries in the C(basic_constraints) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[CA:TRUE, pathlen:1]"
|
||||
basic_constraints_critical:
|
||||
description: Whether the C(basic_constraints) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extended_key_usage:
|
||||
description: Entries in the C(extended_key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[Biometric Info, DVCS, Time Stamping]"
|
||||
extended_key_usage_critical:
|
||||
description: Whether the C(extended_key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
extensions_by_oid:
|
||||
description: Returns a dictionary for every extension OID
|
||||
returned: success
|
||||
type: dict
|
||||
contains:
|
||||
critical:
|
||||
description: Whether the extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
value:
|
||||
description: The Base64 encoded value (in DER format) of the extension
|
||||
returned: success
|
||||
type: str
|
||||
sample: "MAMCAQU="
|
||||
sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}'
|
||||
key_usage:
|
||||
description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "[Key Agreement, Data Encipherment]"
|
||||
key_usage_critical:
|
||||
description: Whether the C(key_usage) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
subject_alt_name:
|
||||
description: Entries in the C(subject_alt_name) extension, or C(none) if extension is not present.
|
||||
returned: success
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
subject_alt_name_critical:
|
||||
description: Whether the C(subject_alt_name) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple:
|
||||
description: C(yes) if the OCSP Must Staple extension is present, C(none) otherwise.
|
||||
returned: success
|
||||
type: bool
|
||||
ocsp_must_staple_critical:
|
||||
description: Whether the C(ocsp_must_staple) extension is critical.
|
||||
returned: success
|
||||
type: bool
|
||||
issuer:
|
||||
description:
|
||||
- The certificate's issuer.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}'
|
||||
issuer_ordered:
|
||||
description: The certificate's issuer as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]'
|
||||
subject:
|
||||
description:
|
||||
- The certificate's subject as a dictionary.
|
||||
- Note that for repeated values, only the last one will be returned.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}'
|
||||
subject_ordered:
|
||||
description: The certificate's subject as an ordered list of tuples.
|
||||
returned: success
|
||||
type: list
|
||||
elements: list
|
||||
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]'
|
||||
not_after:
|
||||
description: C(notAfter) date as ASN.1 TIME
|
||||
returned: success
|
||||
type: str
|
||||
sample: 20190413202428Z
|
||||
not_before:
|
||||
description: C(notBefore) date as ASN.1 TIME
|
||||
returned: success
|
||||
type: str
|
||||
sample: 20190331202428Z
|
||||
public_key:
|
||||
description: Certificate's public key in PEM format
|
||||
returned: success
|
||||
type: str
|
||||
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
|
||||
public_key_fingerprints:
|
||||
description:
|
||||
- Fingerprints of certificate's public key.
|
||||
- For every hash algorithm available, the fingerprint is computed.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
|
||||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
|
||||
signature_algorithm:
|
||||
description: The signature algorithm used to sign the certificate.
|
||||
returned: success
|
||||
type: str
|
||||
sample: sha256WithRSAEncryption
|
||||
serial_number:
|
||||
description: The certificate's serial number.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 1234
|
||||
version:
|
||||
description: The certificate version.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 3
|
||||
valid_at:
|
||||
description: For every time stamp provided in the I(valid_at) option, a
|
||||
boolean whether the certificate is valid at that point in time
|
||||
or not.
|
||||
returned: success
|
||||
type: dict
|
||||
subject_key_identifier:
|
||||
description:
|
||||
- The certificate's subject key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(SubjectKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
authority_key_identifier:
|
||||
description:
|
||||
- The certificate's authority key identifier.
|
||||
- The identifier is returned in hexadecimal, with C(:) used to separate bytes.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: str
|
||||
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
|
||||
authority_cert_issuer:
|
||||
description:
|
||||
- The certificate's authority cert issuer as a list of general names.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: list
|
||||
elements: str
|
||||
sample: "[DNS:www.ansible.com, IP:1.2.3.4]"
|
||||
authority_cert_serial_number:
|
||||
description:
|
||||
- The certificate's authority cert serial number.
|
||||
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
|
||||
returned: success and if the pyOpenSSL backend is I(not) used
|
||||
type: int
|
||||
sample: '12345'
|
||||
ocsp_uri:
|
||||
description: The OCSP responder URI, if included in the certificate. Will be
|
||||
C(none) if no OCSP responder URI is included.
|
||||
returned: success
|
||||
type: str
|
||||
'''
|
||||
|
||||
|
||||
import abc
|
||||
import binascii
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_native, to_text, to_bytes
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
get_relative_time_option,
|
||||
load_certificate,
|
||||
get_fingerprint_of_bytes,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_decode_name,
|
||||
cryptography_get_extensions_from_cert,
|
||||
cryptography_oid_to_name,
|
||||
cryptography_serial_number_of_cert,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_get_extensions_from_cert,
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_normalize_name_attribute,
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# OpenSSL 1.1.0 or newer
|
||||
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
|
||||
else:
|
||||
# OpenSSL 1.0.x or older
|
||||
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
|
||||
except ImportError:
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
import cryptography
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
|
||||
except ImportError:
|
||||
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
|
||||
CRYPTOGRAPHY_FOUND = False
|
||||
else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
|
||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CertificateInfo(OpenSSLObject):
|
||||
def __init__(self, module, backend):
|
||||
super(CertificateInfo, self).__init__(
|
||||
module.params['path'] or '',
|
||||
'present',
|
||||
False,
|
||||
module.check_mode,
|
||||
)
|
||||
self.backend = backend
|
||||
self.module = module
|
||||
self.content = module.params['content']
|
||||
if self.content is not None:
|
||||
self.content = self.content.encode('utf-8')
|
||||
|
||||
self.valid_at = module.params['valid_at']
|
||||
if self.valid_at:
|
||||
for k, v in self.valid_at.items():
|
||||
if not isinstance(v, string_types):
|
||||
self.module.fail_json(
|
||||
msg='The value for valid_at.{0} must be of type string (got {1})'.format(k, type(v))
|
||||
)
|
||||
self.valid_at[k] = get_relative_time_option(v, 'valid_at.{0}'.format(k))
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_signature_algorithm(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_subject_ordered(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_issuer_ordered(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_version(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_extended_key_usage(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_basic_constraints(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_ocsp_must_staple(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_subject_alt_name(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_not_before(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_not_after(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_public_key(self, binary):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_subject_key_identifier(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_authority_key_identifier(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_serial_number(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_all_extensions(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def _get_ocsp_uri(self):
|
||||
pass
|
||||
|
||||
def get_info(self):
|
||||
result = dict()
|
||||
self.cert = load_certificate(self.path, content=self.content, backend=self.backend)
|
||||
|
||||
result['signature_algorithm'] = self._get_signature_algorithm()
|
||||
subject = self._get_subject_ordered()
|
||||
issuer = self._get_issuer_ordered()
|
||||
result['subject'] = dict()
|
||||
for k, v in subject:
|
||||
result['subject'][k] = v
|
||||
result['subject_ordered'] = subject
|
||||
result['issuer'] = dict()
|
||||
for k, v in issuer:
|
||||
result['issuer'][k] = v
|
||||
result['issuer_ordered'] = issuer
|
||||
result['version'] = self._get_version()
|
||||
result['key_usage'], result['key_usage_critical'] = self._get_key_usage()
|
||||
result['extended_key_usage'], result['extended_key_usage_critical'] = self._get_extended_key_usage()
|
||||
result['basic_constraints'], result['basic_constraints_critical'] = self._get_basic_constraints()
|
||||
result['ocsp_must_staple'], result['ocsp_must_staple_critical'] = self._get_ocsp_must_staple()
|
||||
result['subject_alt_name'], result['subject_alt_name_critical'] = self._get_subject_alt_name()
|
||||
|
||||
not_before = self._get_not_before()
|
||||
not_after = self._get_not_after()
|
||||
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
|
||||
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
|
||||
result['expired'] = not_after < datetime.datetime.utcnow()
|
||||
|
||||
result['valid_at'] = dict()
|
||||
if self.valid_at:
|
||||
for k, v in self.valid_at.items():
|
||||
result['valid_at'][k] = not_before <= v <= not_after
|
||||
|
||||
result['public_key'] = self._get_public_key(binary=False)
|
||||
pk = self._get_public_key(binary=True)
|
||||
result['public_key_fingerprints'] = get_fingerprint_of_bytes(pk) if pk is not None else dict()
|
||||
|
||||
if self.backend != 'pyopenssl':
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
|
||||
result['serial_number'] = self._get_serial_number()
|
||||
result['extensions_by_oid'] = self._get_all_extensions()
|
||||
result['ocsp_uri'] = self._get_ocsp_uri()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class CertificateInfoCryptography(CertificateInfo):
|
||||
"""Validate the supplied cert, using the cryptography backend"""
|
||||
def __init__(self, module):
|
||||
super(CertificateInfoCryptography, self).__init__(module, 'cryptography')
|
||||
|
||||
def _get_signature_algorithm(self):
|
||||
return cryptography_oid_to_name(self.cert.signature_algorithm_oid)
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
result = []
|
||||
for attribute in self.cert.subject:
|
||||
result.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
return result
|
||||
|
||||
def _get_issuer_ordered(self):
|
||||
result = []
|
||||
for attribute in self.cert.issuer:
|
||||
result.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
return result
|
||||
|
||||
def _get_version(self):
|
||||
if self.cert.version == x509.Version.v1:
|
||||
return 1
|
||||
if self.cert.version == x509.Version.v3:
|
||||
return 3
|
||||
return "unknown"
|
||||
|
||||
def _get_key_usage(self):
|
||||
try:
|
||||
current_key_ext = self.cert.extensions.get_extension_for_class(x509.KeyUsage)
|
||||
current_key_usage = current_key_ext.value
|
||||
key_usage = dict(
|
||||
digital_signature=current_key_usage.digital_signature,
|
||||
content_commitment=current_key_usage.content_commitment,
|
||||
key_encipherment=current_key_usage.key_encipherment,
|
||||
data_encipherment=current_key_usage.data_encipherment,
|
||||
key_agreement=current_key_usage.key_agreement,
|
||||
key_cert_sign=current_key_usage.key_cert_sign,
|
||||
crl_sign=current_key_usage.crl_sign,
|
||||
encipher_only=False,
|
||||
decipher_only=False,
|
||||
)
|
||||
if key_usage['key_agreement']:
|
||||
key_usage.update(dict(
|
||||
encipher_only=current_key_usage.encipher_only,
|
||||
decipher_only=current_key_usage.decipher_only
|
||||
))
|
||||
|
||||
key_usage_names = dict(
|
||||
digital_signature='Digital Signature',
|
||||
content_commitment='Non Repudiation',
|
||||
key_encipherment='Key Encipherment',
|
||||
data_encipherment='Data Encipherment',
|
||||
key_agreement='Key Agreement',
|
||||
key_cert_sign='Certificate Sign',
|
||||
crl_sign='CRL Sign',
|
||||
encipher_only='Encipher Only',
|
||||
decipher_only='Decipher Only',
|
||||
)
|
||||
return sorted([
|
||||
key_usage_names[name] for name, value in key_usage.items() if value
|
||||
]), current_key_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
try:
|
||||
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage)
|
||||
return sorted([
|
||||
cryptography_oid_to_name(eku) for eku in ext_keyusage_ext.value
|
||||
]), ext_keyusage_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
try:
|
||||
ext_keyusage_ext = self.cert.extensions.get_extension_for_class(x509.BasicConstraints)
|
||||
result = []
|
||||
result.append('CA:{0}'.format('TRUE' if ext_keyusage_ext.value.ca else 'FALSE'))
|
||||
if ext_keyusage_ext.value.path_length is not None:
|
||||
result.append('pathlen:{0}'.format(ext_keyusage_ext.value.path_length))
|
||||
return sorted(result), ext_keyusage_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
try:
|
||||
try:
|
||||
# This only works with cryptography >= 2.1
|
||||
tlsfeature_ext = self.cert.extensions.get_extension_for_class(x509.TLSFeature)
|
||||
value = cryptography.x509.TLSFeatureType.status_request in tlsfeature_ext.value
|
||||
except AttributeError as dummy:
|
||||
# Fallback for cryptography < 2.1
|
||||
oid = x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.24")
|
||||
tlsfeature_ext = self.cert.extensions.get_extension_for_oid(oid)
|
||||
value = tlsfeature_ext.value.value == b"\x30\x03\x02\x01\x05"
|
||||
return value, tlsfeature_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
try:
|
||||
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||
result = [cryptography_decode_name(san) for san in san_ext.value]
|
||||
return result, san_ext.critical
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, False
|
||||
|
||||
def _get_not_before(self):
|
||||
return self.cert.not_valid_before
|
||||
|
||||
def _get_not_after(self):
|
||||
return self.cert.not_valid_after
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
return self.cert.public_key().public_bytes(
|
||||
serialization.Encoding.DER if binary else serialization.Encoding.PEM,
|
||||
serialization.PublicFormat.SubjectPublicKeyInfo
|
||||
)
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
try:
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
|
||||
return ext.value.digest
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
try:
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
||||
issuer = None
|
||||
if ext.value.authority_cert_issuer is not None:
|
||||
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
|
||||
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
|
||||
except cryptography.x509.ExtensionNotFound:
|
||||
return None, None, None
|
||||
|
||||
def _get_serial_number(self):
|
||||
return cryptography_serial_number_of_cert(self.cert)
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return cryptography_get_extensions_from_cert(self.cert)
|
||||
|
||||
def _get_ocsp_uri(self):
|
||||
try:
|
||||
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
|
||||
for desc in ext.value:
|
||||
if desc.access_method == x509.oid.AuthorityInformationAccessOID.OCSP:
|
||||
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
|
||||
return desc.access_location.value
|
||||
except x509.ExtensionNotFound as dummy:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
class CertificateInfoPyOpenSSL(CertificateInfo):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module):
|
||||
super(CertificateInfoPyOpenSSL, self).__init__(module, 'pyopenssl')
|
||||
|
||||
def _get_signature_algorithm(self):
|
||||
return to_text(self.cert.get_signature_algorithm())
|
||||
|
||||
def __get_name(self, name):
|
||||
result = []
|
||||
for sub in name.get_components():
|
||||
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
return result
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
return self.__get_name(self.cert.get_subject())
|
||||
|
||||
def _get_issuer_ordered(self):
|
||||
return self.__get_name(self.cert.get_issuer())
|
||||
|
||||
def _get_version(self):
|
||||
# Version numbers in certs are off by one:
|
||||
# v1: 0, v2: 1, v3: 2 ...
|
||||
return self.cert.get_version() + 1
|
||||
|
||||
def _get_extension(self, short_name):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == short_name:
|
||||
result = [
|
||||
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
]
|
||||
return sorted(result), bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_key_usage(self):
|
||||
return self._get_extension(b'keyUsage')
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
return self._get_extension(b'extendedKeyUsage')
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
return self._get_extension(b'basicConstraints')
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
|
||||
oms_ext = [
|
||||
ext for ext in extensions
|
||||
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
|
||||
]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
# Older versions of libssl don't know about OCSP Must Staple
|
||||
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
|
||||
if oms_ext:
|
||||
return True, bool(oms_ext[0].get_critical())
|
||||
else:
|
||||
return None, False
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
return result, bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_not_before(self):
|
||||
time_string = to_native(self.cert.get_notBefore())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def _get_not_after(self):
|
||||
time_string = to_native(self.cert.get_notAfter())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def _get_public_key(self, binary):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_ASN1 if binary else crypto.FILETYPE_PEM,
|
||||
self.cert.get_pubkey()
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
if binary:
|
||||
rc = crypto._lib.i2d_PUBKEY_bio(bio, self.cert.get_pubkey()._pkey)
|
||||
else:
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None, None, None
|
||||
|
||||
def _get_serial_number(self):
|
||||
return self.cert.get_serial_number()
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return pyopenssl_get_extensions_from_cert(self.cert)
|
||||
|
||||
def _get_ocsp_uri(self):
|
||||
for i in range(self.cert.get_extension_count()):
|
||||
ext = self.cert.get_extension(i)
|
||||
if ext.get_short_name() == b'authorityInfoAccess':
|
||||
v = str(ext)
|
||||
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path'),
|
||||
content=dict(type='str'),
|
||||
valid_at=dict(type='dict'),
|
||||
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
|
||||
),
|
||||
required_one_of=(
|
||||
['path', 'content'],
|
||||
),
|
||||
mutually_exclusive=(
|
||||
['path', 'content'],
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
if module._name == 'community.crypto.openssl_certificate_info':
|
||||
module.deprecate("The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'",
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
|
||||
try:
|
||||
if module.params['path'] is not None:
|
||||
base_dir = os.path.dirname(module.params['path']) or '.'
|
||||
if not os.path.isdir(base_dir):
|
||||
module.fail_json(
|
||||
name=base_dir,
|
||||
msg='The directory %s does not exist or the file is not a directory' % base_dir
|
||||
)
|
||||
|
||||
backend = module.params['select_crypto_backend']
|
||||
if backend == 'auto':
|
||||
# Detect what backend we can use
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# If cryptography is available we'll use it
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Fail if no backend has been found
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
certificate = CertificateInfoPyOpenSSL(module)
|
||||
elif backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
certificate = CertificateInfoCryptography(module)
|
||||
|
||||
result = certificate.get_info()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -7,11 +7,13 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: x509_crl
|
||||
version_added: '1.0.0'
|
||||
short_description: Generate Certificate Revocation Lists (CRLs)
|
||||
description:
|
||||
- This module allows one to (re)generate or update Certificate Revocation Lists (CRLs).
|
||||
@@ -61,15 +63,6 @@ options:
|
||||
type: path
|
||||
required: yes
|
||||
|
||||
format:
|
||||
description:
|
||||
- Whether the CRL file should be in PEM or DER format.
|
||||
- If an existing CRL file does match everything but I(format), it will be converted to the correct format
|
||||
instead of regenerated.
|
||||
type: str
|
||||
choices: [pem, der]
|
||||
default: pem
|
||||
|
||||
privatekey_path:
|
||||
description:
|
||||
- Path to the CA's private key to use when signing the CRL.
|
||||
@@ -237,7 +230,7 @@ notes:
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Generate a CRL
|
||||
community.crypto.x509_crl:
|
||||
x509_crl:
|
||||
path: /etc/ssl/my-ca.crl
|
||||
privatekey_path: /etc/ssl/private/my-ca.pem
|
||||
issuer:
|
||||
@@ -273,12 +266,6 @@ privatekey:
|
||||
returned: changed or success
|
||||
type: str
|
||||
sample: /path/to/my-ca.pem
|
||||
format:
|
||||
description:
|
||||
- Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)).
|
||||
returned: success
|
||||
type: str
|
||||
sample: pem
|
||||
issuer:
|
||||
description:
|
||||
- The CRL's issuer.
|
||||
@@ -353,60 +340,19 @@ revoked_certificates:
|
||||
type: bool
|
||||
sample: no
|
||||
crl:
|
||||
description:
|
||||
- The (current or generated) CRL's content.
|
||||
- Will be the CRL itself if I(format) is C(pem), and Base64 of the
|
||||
CRL if I(format) is C(der).
|
||||
description: The (current or generated) CRL's content.
|
||||
returned: if I(state) is C(present) and I(return_content) is C(yes)
|
||||
type: str
|
||||
'''
|
||||
|
||||
|
||||
import base64
|
||||
import os
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils._text import to_native, to_text
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||
write_file,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
OpenSSLBadPassphraseError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
load_privatekey,
|
||||
load_certificate,
|
||||
parse_name_field,
|
||||
get_relative_time_option,
|
||||
select_message_digest,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_get_name,
|
||||
cryptography_name_to_oid,
|
||||
cryptography_oid_to_name,
|
||||
cryptography_serial_number_of_cert,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import (
|
||||
REVOCATION_REASON_MAP,
|
||||
TIMESTAMP_FORMAT,
|
||||
cryptography_decode_revoked_certificate,
|
||||
cryptography_dump_revoked,
|
||||
cryptography_get_signature_algorithm_oid_from_crl,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
|
||||
identify_pem_format,
|
||||
)
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
|
||||
|
||||
@@ -430,11 +376,14 @@ else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
|
||||
class CRLError(OpenSSLObjectError):
|
||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CRLError(crypto_utils.OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
class CRL(OpenSSLObject):
|
||||
class CRL(crypto_utils.OpenSSLObject):
|
||||
|
||||
def __init__(self, module):
|
||||
super(CRL, self).__init__(
|
||||
@@ -444,8 +393,6 @@ class CRL(OpenSSLObject):
|
||||
module.check_mode
|
||||
)
|
||||
|
||||
self.format = module.params['format']
|
||||
|
||||
self.update = module.params['mode'] == 'update'
|
||||
self.ignore_timestamps = module.params['ignore_timestamps']
|
||||
self.return_content = module.params['return_content']
|
||||
@@ -457,13 +404,13 @@ class CRL(OpenSSLObject):
|
||||
self.privatekey_content = self.privatekey_content.encode('utf-8')
|
||||
self.privatekey_passphrase = module.params['privatekey_passphrase']
|
||||
|
||||
self.issuer = parse_name_field(module.params['issuer'])
|
||||
self.issuer = crypto_utils.parse_name_field(module.params['issuer'])
|
||||
self.issuer = [(entry[0], entry[1]) for entry in self.issuer if entry[1]]
|
||||
|
||||
self.last_update = get_relative_time_option(module.params['last_update'], 'last_update')
|
||||
self.next_update = get_relative_time_option(module.params['next_update'], 'next_update')
|
||||
self.last_update = crypto_utils.get_relative_time_option(module.params['last_update'], 'last_update')
|
||||
self.next_update = crypto_utils.get_relative_time_option(module.params['next_update'], 'next_update')
|
||||
|
||||
self.digest = select_message_digest(module.params['digest'])
|
||||
self.digest = crypto_utils.select_message_digest(module.params['digest'])
|
||||
if self.digest is None:
|
||||
raise CRLError('The digest "{0}" is not supported'.format(module.params['digest']))
|
||||
|
||||
@@ -485,9 +432,13 @@ class CRL(OpenSSLObject):
|
||||
try:
|
||||
if rc['content'] is not None:
|
||||
rc['content'] = rc['content'].encode('utf-8')
|
||||
cert = load_certificate(rc['path'], content=rc['content'], backend='cryptography')
|
||||
result['serial_number'] = cryptography_serial_number_of_cert(cert)
|
||||
except OpenSSLObjectError as e:
|
||||
cert = crypto_utils.load_certificate(rc['path'], content=rc['content'], backend='cryptography')
|
||||
try:
|
||||
result['serial_number'] = cert.serial_number
|
||||
except AttributeError:
|
||||
# The property was called "serial" before cryptography 1.4
|
||||
result['serial_number'] = cert.serial
|
||||
except crypto_utils.OpenSSLObjectError as e:
|
||||
if rc['content'] is not None:
|
||||
module.fail_json(
|
||||
msg='Cannot parse certificate from {0}content: {1}'.format(path_prefix, to_native(e))
|
||||
@@ -501,17 +452,17 @@ class CRL(OpenSSLObject):
|
||||
result['serial_number'] = rc['serial_number']
|
||||
# All other options
|
||||
if rc['issuer']:
|
||||
result['issuer'] = [cryptography_get_name(issuer) for issuer in rc['issuer']]
|
||||
result['issuer'] = [crypto_utils.cryptography_get_name(issuer) for issuer in rc['issuer']]
|
||||
result['issuer_critical'] = rc['issuer_critical']
|
||||
result['revocation_date'] = get_relative_time_option(
|
||||
result['revocation_date'] = crypto_utils.get_relative_time_option(
|
||||
rc['revocation_date'],
|
||||
path_prefix + 'revocation_date'
|
||||
)
|
||||
if rc['reason']:
|
||||
result['reason'] = REVOCATION_REASON_MAP[rc['reason']]
|
||||
result['reason'] = crypto_utils.REVOCATION_REASON_MAP[rc['reason']]
|
||||
result['reason_critical'] = rc['reason_critical']
|
||||
if rc['invalidity_date']:
|
||||
result['invalidity_date'] = get_relative_time_option(
|
||||
result['invalidity_date'] = crypto_utils.get_relative_time_option(
|
||||
rc['invalidity_date'],
|
||||
path_prefix + 'invalidity_date'
|
||||
)
|
||||
@@ -524,31 +475,24 @@ class CRL(OpenSSLObject):
|
||||
self.backup_file = None
|
||||
|
||||
try:
|
||||
self.privatekey = load_privatekey(
|
||||
self.privatekey = crypto_utils.load_privatekey(
|
||||
path=self.privatekey_path,
|
||||
content=self.privatekey_content,
|
||||
passphrase=self.privatekey_passphrase,
|
||||
backend='cryptography'
|
||||
)
|
||||
except OpenSSLBadPassphraseError as exc:
|
||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||
raise CRLError(exc)
|
||||
|
||||
self.crl = None
|
||||
try:
|
||||
with open(self.path, 'rb') as f:
|
||||
data = f.read()
|
||||
self.actual_format = 'pem' if identify_pem_format(data) else 'der'
|
||||
if self.actual_format == 'pem':
|
||||
self.crl = x509.load_pem_x509_crl(data, default_backend())
|
||||
if self.return_content:
|
||||
self.crl_content = data
|
||||
else:
|
||||
self.crl = x509.load_der_x509_crl(data, default_backend())
|
||||
if self.return_content:
|
||||
self.crl_content = base64.b64encode(data)
|
||||
self.crl = x509.load_pem_x509_crl(data, default_backend())
|
||||
if self.return_content:
|
||||
self.crl_content = data
|
||||
except Exception as dummy:
|
||||
self.crl_content = None
|
||||
self.actual_format = self.format
|
||||
|
||||
def remove(self):
|
||||
if self.backup:
|
||||
@@ -579,7 +523,7 @@ class CRL(OpenSSLObject):
|
||||
entry['invalidity_date_critical'],
|
||||
)
|
||||
|
||||
def check(self, perms_required=True, ignore_conversion=True):
|
||||
def check(self, perms_required=True):
|
||||
"""Ensure the resource is in its desired state."""
|
||||
|
||||
state_and_perms = super(CRL, self).check(self.module, perms_required)
|
||||
@@ -597,11 +541,11 @@ class CRL(OpenSSLObject):
|
||||
if self.digest.name != self.crl.signature_hash_algorithm.name:
|
||||
return False
|
||||
|
||||
want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer]
|
||||
want_issuer = [(crypto_utils.cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer]
|
||||
if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]:
|
||||
return False
|
||||
|
||||
old_entries = [self._compress_entry(cryptography_decode_revoked_certificate(cert)) for cert in self.crl]
|
||||
old_entries = [self._compress_entry(crypto_utils.cryptography_decode_revoked_certificate(cert)) for cert in self.crl]
|
||||
new_entries = [self._compress_entry(cert) for cert in self.revoked_certificates]
|
||||
if self.update:
|
||||
# We don't simply use a set so that duplicate entries are treated correctly
|
||||
@@ -614,9 +558,6 @@ class CRL(OpenSSLObject):
|
||||
if old_entries != new_entries:
|
||||
return False
|
||||
|
||||
if self.format != self.actual_format and not ignore_conversion:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _generate_crl(self):
|
||||
@@ -625,7 +566,7 @@ class CRL(OpenSSLObject):
|
||||
|
||||
try:
|
||||
crl = crl.issuer_name(Name([
|
||||
NameAttribute(cryptography_name_to_oid(entry[0]), to_text(entry[1]))
|
||||
NameAttribute(crypto_utils.cryptography_name_to_oid(entry[0]), to_text(entry[1]))
|
||||
for entry in self.issuer
|
||||
]))
|
||||
except ValueError as e:
|
||||
@@ -637,7 +578,7 @@ class CRL(OpenSSLObject):
|
||||
if self.update and self.crl:
|
||||
new_entries = set([self._compress_entry(entry) for entry in self.revoked_certificates])
|
||||
for entry in self.crl:
|
||||
decoded_entry = self._compress_entry(cryptography_decode_revoked_certificate(entry))
|
||||
decoded_entry = self._compress_entry(crypto_utils.cryptography_decode_revoked_certificate(entry))
|
||||
if decoded_entry not in new_entries:
|
||||
crl = crl.add_revoked_certificate(entry)
|
||||
for entry in self.revoked_certificates:
|
||||
@@ -647,7 +588,7 @@ class CRL(OpenSSLObject):
|
||||
if entry['issuer'] is not None:
|
||||
revoked_cert = revoked_cert.add_extension(
|
||||
x509.CertificateIssuer([
|
||||
cryptography_get_name(name) for name in entry['issuer']
|
||||
crypto_utils.cryptography_get_name(name) for name in self.entry['issuer']
|
||||
]),
|
||||
entry['issuer_critical']
|
||||
)
|
||||
@@ -664,42 +605,43 @@ class CRL(OpenSSLObject):
|
||||
crl = crl.add_revoked_certificate(revoked_cert.build(backend))
|
||||
|
||||
self.crl = crl.sign(self.privatekey, self.digest, backend=backend)
|
||||
if self.format == 'pem':
|
||||
return self.crl.public_bytes(Encoding.PEM)
|
||||
else:
|
||||
return self.crl.public_bytes(Encoding.DER)
|
||||
return self.crl.public_bytes(Encoding.PEM)
|
||||
|
||||
def generate(self):
|
||||
result = None
|
||||
if not self.check(perms_required=False, ignore_conversion=True) or self.force:
|
||||
if not self.check(perms_required=False) or self.force:
|
||||
result = self._generate_crl()
|
||||
elif not self.check(perms_required=False, ignore_conversion=False) and self.crl:
|
||||
if self.format == 'pem':
|
||||
result = self.crl.public_bytes(Encoding.PEM)
|
||||
else:
|
||||
result = self.crl.public_bytes(Encoding.DER)
|
||||
|
||||
if result is not None:
|
||||
if self.return_content:
|
||||
if self.format == 'pem':
|
||||
self.crl_content = result
|
||||
else:
|
||||
self.crl_content = base64.b64encode(result)
|
||||
self.crl_content = result
|
||||
if self.backup:
|
||||
self.backup_file = self.module.backup_local(self.path)
|
||||
write_file(self.module, result)
|
||||
crypto_utils.write_file(self.module, result)
|
||||
self.changed = True
|
||||
|
||||
file_args = self.module.load_file_common_arguments(self.module.params)
|
||||
if self.module.set_fs_attributes_if_different(file_args, False):
|
||||
self.changed = True
|
||||
|
||||
def _dump_revoked(self, entry):
|
||||
return {
|
||||
'serial_number': entry['serial_number'],
|
||||
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
|
||||
'issuer':
|
||||
[crypto_utils.cryptography_decode_name(issuer) for issuer in entry['issuer']]
|
||||
if entry['issuer'] is not None else None,
|
||||
'issuer_critical': entry['issuer_critical'],
|
||||
'reason': crypto_utils.REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
|
||||
'reason_critical': entry['reason_critical'],
|
||||
'invalidity_date':
|
||||
entry['invalidity_date'].strftime(TIMESTAMP_FORMAT)
|
||||
if entry['invalidity_date'] is not None else None,
|
||||
'invalidity_date_critical': entry['invalidity_date_critical'],
|
||||
}
|
||||
|
||||
def dump(self, check_mode=False):
|
||||
result = {
|
||||
'changed': self.changed,
|
||||
'filename': self.path,
|
||||
'privatekey': self.privatekey_path,
|
||||
'format': self.format,
|
||||
'last_update': None,
|
||||
'next_update': None,
|
||||
'digest': None,
|
||||
@@ -713,7 +655,7 @@ class CRL(OpenSSLObject):
|
||||
if check_mode:
|
||||
result['last_update'] = self.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result['next_update'] = self.next_update.strftime(TIMESTAMP_FORMAT)
|
||||
# result['digest'] = cryptography_oid_to_name(self.crl.signature_algorithm_oid)
|
||||
# result['digest'] = crypto_utils.cryptography_oid_to_name(self.crl.signature_algorithm_oid)
|
||||
result['digest'] = self.module.params['digest']
|
||||
result['issuer_ordered'] = self.issuer
|
||||
result['issuer'] = {}
|
||||
@@ -721,22 +663,32 @@ class CRL(OpenSSLObject):
|
||||
result['issuer'][k] = v
|
||||
result['revoked_certificates'] = []
|
||||
for entry in self.revoked_certificates:
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
|
||||
result['revoked_certificates'].append(self._dump_revoked(entry))
|
||||
elif self.crl:
|
||||
result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT)
|
||||
result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl))
|
||||
try:
|
||||
result['digest'] = crypto_utils.cryptography_oid_to_name(self.crl.signature_algorithm_oid)
|
||||
except AttributeError:
|
||||
# Older cryptography versions don't have signature_algorithm_oid yet
|
||||
dotted = crypto_utils._obj2txt(
|
||||
self.crl._backend._lib,
|
||||
self.crl._backend._ffi,
|
||||
self.crl._x509_crl.sig_alg.algorithm
|
||||
)
|
||||
oid = x509.oid.ObjectIdentifier(dotted)
|
||||
result['digest'] = crypto_utils.cryptography_oid_to_name(oid)
|
||||
issuer = []
|
||||
for attribute in self.crl.issuer:
|
||||
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
issuer.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
result['issuer_ordered'] = issuer
|
||||
result['issuer'] = {}
|
||||
for k, v in issuer:
|
||||
result['issuer'][k] = v
|
||||
result['revoked_certificates'] = []
|
||||
for cert in self.crl:
|
||||
entry = cryptography_decode_revoked_certificate(cert)
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
|
||||
entry = crypto_utils.cryptography_decode_revoked_certificate(cert)
|
||||
result['revoked_certificates'].append(self._dump_revoked(entry))
|
||||
|
||||
if self.return_content:
|
||||
result['crl'] = self.crl_content
|
||||
@@ -752,7 +704,6 @@ def main():
|
||||
force=dict(type='bool', default=False),
|
||||
backup=dict(type='bool', default=False),
|
||||
path=dict(type='path', required=True),
|
||||
format=dict(type='str', default='pem', choices=['pem', 'der']),
|
||||
privatekey_path=dict(type='path'),
|
||||
privatekey_content=dict(type='str'),
|
||||
privatekey_passphrase=dict(type='str', no_log=True),
|
||||
@@ -809,7 +760,7 @@ def main():
|
||||
if module.params['state'] == 'present':
|
||||
if module.check_mode:
|
||||
result = crl.dump(check_mode=True)
|
||||
result['changed'] = module.params['force'] or not crl.check() or not crl.check(ignore_conversion=False)
|
||||
result['changed'] = module.params['force'] or not crl.check()
|
||||
module.exit_json(**result)
|
||||
|
||||
crl.generate()
|
||||
@@ -823,7 +774,7 @@ def main():
|
||||
|
||||
result = crl.dump()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as exc:
|
||||
except crypto_utils.OpenSSLObjectError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: x509_crl_info
|
||||
version_added: '1.0.0'
|
||||
short_description: Retrieve information on Certificate Revocation Lists (CRLs)
|
||||
description:
|
||||
- This module allows one to retrieve information on Certificate Revocation Lists (CRLs).
|
||||
@@ -27,7 +29,7 @@ options:
|
||||
type: path
|
||||
content:
|
||||
description:
|
||||
- Content of the X.509 CRL in PEM format, or Base64-encoded X.509 CRL.
|
||||
- Content of the X.509 certificate in PEM format.
|
||||
- Either I(path) or I(content) must be specified, but not both.
|
||||
type: str
|
||||
|
||||
@@ -35,12 +37,12 @@ notes:
|
||||
- All timestamp values are provided in ASN.1 TIME format, i.e. following the C(YYYYMMDDHHMMSSZ) pattern.
|
||||
They are all in UTC.
|
||||
seealso:
|
||||
- module: community.crypto.x509_crl
|
||||
- module: x509_crl
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Get information on CRL
|
||||
community.crypto.x509_crl_info:
|
||||
x509_crl_info:
|
||||
path: /etc/ssl/my-ca.crl
|
||||
register: result
|
||||
|
||||
@@ -49,12 +51,6 @@ EXAMPLES = r'''
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
format:
|
||||
description:
|
||||
- Whether the CRL is in PEM format (C(pem)) or in DER format (C(der)).
|
||||
returned: success
|
||||
type: str
|
||||
sample: pem
|
||||
issuer:
|
||||
description:
|
||||
- The CRL's issuer.
|
||||
@@ -131,38 +127,12 @@ revoked_certificates:
|
||||
'''
|
||||
|
||||
|
||||
import base64
|
||||
import traceback
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible_collections.community.crypto.plugins.module_utils import crypto as crypto_utils
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||
OpenSSLObject,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||
cryptography_oid_to_name,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_crl import (
|
||||
TIMESTAMP_FORMAT,
|
||||
cryptography_decode_revoked_certificate,
|
||||
cryptography_dump_revoked,
|
||||
cryptography_get_signature_algorithm_oid_from_crl,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
|
||||
identify_pem_format,
|
||||
)
|
||||
|
||||
# crypto_utils
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
|
||||
|
||||
@@ -179,11 +149,14 @@ else:
|
||||
CRYPTOGRAPHY_FOUND = True
|
||||
|
||||
|
||||
class CRLError(OpenSSLObjectError):
|
||||
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
||||
|
||||
|
||||
class CRLError(crypto_utils.OpenSSLObjectError):
|
||||
pass
|
||||
|
||||
|
||||
class CRLInfo(OpenSSLObject):
|
||||
class CRLInfo(crypto_utils.OpenSSLObject):
|
||||
"""The main module implementation."""
|
||||
|
||||
def __init__(self, module):
|
||||
@@ -207,22 +180,31 @@ class CRLInfo(OpenSSLObject):
|
||||
self.module.fail_json(msg='Error while reading CRL file from disk: {0}'.format(e))
|
||||
else:
|
||||
data = self.content.encode('utf-8')
|
||||
if not identify_pem_format(data):
|
||||
data = base64.b64decode(self.content)
|
||||
|
||||
self.crl_pem = identify_pem_format(data)
|
||||
try:
|
||||
if self.crl_pem:
|
||||
self.crl = x509.load_pem_x509_crl(data, default_backend())
|
||||
else:
|
||||
self.crl = x509.load_der_x509_crl(data, default_backend())
|
||||
self.crl = x509.load_pem_x509_crl(data, default_backend())
|
||||
except Exception as e:
|
||||
self.module.fail_json(msg='Error while decoding CRL: {0}'.format(e))
|
||||
|
||||
def _dump_revoked(self, entry):
|
||||
return {
|
||||
'serial_number': entry['serial_number'],
|
||||
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
|
||||
'issuer':
|
||||
[crypto_utils.cryptography_decode_name(issuer) for issuer in entry['issuer']]
|
||||
if entry['issuer'] is not None else None,
|
||||
'issuer_critical': entry['issuer_critical'],
|
||||
'reason': crypto_utils.REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
|
||||
'reason_critical': entry['reason_critical'],
|
||||
'invalidity_date':
|
||||
entry['invalidity_date'].strftime(TIMESTAMP_FORMAT)
|
||||
if entry['invalidity_date'] is not None else None,
|
||||
'invalidity_date_critical': entry['invalidity_date_critical'],
|
||||
}
|
||||
|
||||
def get_info(self):
|
||||
result = {
|
||||
'changed': False,
|
||||
'format': 'pem' if self.crl_pem else 'der',
|
||||
'last_update': None,
|
||||
'next_update': None,
|
||||
'digest': None,
|
||||
@@ -233,27 +215,37 @@ class CRLInfo(OpenSSLObject):
|
||||
|
||||
result['last_update'] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
|
||||
result['next_update'] = self.crl.next_update.strftime(TIMESTAMP_FORMAT)
|
||||
result['digest'] = cryptography_oid_to_name(cryptography_get_signature_algorithm_oid_from_crl(self.crl))
|
||||
try:
|
||||
result['digest'] = crypto_utils.cryptography_oid_to_name(self.crl.signature_algorithm_oid)
|
||||
except AttributeError:
|
||||
# Older cryptography versions don't have signature_algorithm_oid yet
|
||||
dotted = crypto_utils._obj2txt(
|
||||
self.crl._backend._lib,
|
||||
self.crl._backend._ffi,
|
||||
self.crl._x509_crl.sig_alg.algorithm
|
||||
)
|
||||
oid = x509.oid.ObjectIdentifier(dotted)
|
||||
result['digest'] = crypto_utils.cryptography_oid_to_name(oid)
|
||||
issuer = []
|
||||
for attribute in self.crl.issuer:
|
||||
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
issuer.append([crypto_utils.cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||
result['issuer_ordered'] = issuer
|
||||
result['issuer'] = {}
|
||||
for k, v in issuer:
|
||||
result['issuer'][k] = v
|
||||
result['revoked_certificates'] = []
|
||||
for cert in self.crl:
|
||||
entry = cryptography_decode_revoked_certificate(cert)
|
||||
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
|
||||
entry = crypto_utils.cryptography_decode_revoked_certificate(cert)
|
||||
result['revoked_certificates'].append(self._dump_revoked(entry))
|
||||
|
||||
return result
|
||||
|
||||
def generate(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
def dump(self):
|
||||
# Empty method because OpenSSLObject wants this
|
||||
# Empty method because crypto_utils.OpenSSLObject wants this
|
||||
pass
|
||||
|
||||
|
||||
@@ -280,7 +272,7 @@ def main():
|
||||
crl = CRLInfo(module)
|
||||
result = crl.get_info()
|
||||
module.exit_json(**result)
|
||||
except OpenSSLObjectError as e:
|
||||
except crypto_utils.OpenSSLObjectError as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
|
||||
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
language: python
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- T=none
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- env: T=none
|
||||
include:
|
||||
- env: T=devel/sanity/1
|
||||
- env: T=devel/sanity/extra
|
||||
|
||||
- env: T=devel/units/2.6/1
|
||||
- env: T=devel/units/2.7/1
|
||||
- env: T=devel/units/3.5/1
|
||||
- env: T=devel/units/3.6/1
|
||||
- env: T=devel/units/3.7/1
|
||||
- env: T=devel/units/3.8/1
|
||||
- env: T=devel/units/3.9/1
|
||||
|
||||
- env: T=devel/osx/10.11/1
|
||||
- env: T=devel/rhel/7.8/1
|
||||
- env: T=devel/rhel/8.2/1
|
||||
- env: T=devel/freebsd/11.1/1
|
||||
- env: T=devel/freebsd/12.1/1
|
||||
- env: T=devel/linux/centos6/1
|
||||
- env: T=devel/linux/centos7/1
|
||||
- env: T=devel/linux/centos8/1
|
||||
- env: T=devel/linux/fedora30/1
|
||||
- env: T=devel/linux/fedora31/1
|
||||
- env: T=devel/linux/fedora32/1
|
||||
- env: T=devel/linux/opensuse15py2/1
|
||||
- env: T=devel/linux/opensuse15/1
|
||||
- env: T=devel/linux/ubuntu1604/1
|
||||
- env: T=devel/linux/ubuntu1804/1
|
||||
|
||||
- env: T=devel/cloud/2.6/1
|
||||
- env: T=devel/cloud/2.7/1
|
||||
- env: T=devel/cloud/3.5/1
|
||||
- env: T=devel/cloud/3.6/1
|
||||
- env: T=devel/cloud/3.7/1
|
||||
- env: T=devel/cloud/3.8/1
|
||||
- env: T=devel/cloud/3.9/1
|
||||
|
||||
# For Ansible 2.10, use a combination of different targets
|
||||
- env: T=2.10/sanity/1
|
||||
- env: T=2.10/units/2.7/1
|
||||
- env: T=2.10/units/3.8/1
|
||||
- env: T=2.10/rhel/7.8/1
|
||||
- env: T=2.10/linux/ubuntu1804/1
|
||||
- env: T=2.10/cloud/3.6/1
|
||||
|
||||
# For Ansible 2.9, use a combination of different targets
|
||||
- env: T=2.9/sanity/1
|
||||
- env: T=2.9/units/2.7/1
|
||||
- env: T=2.9/units/3.8/1
|
||||
- env: T=2.9/rhel/7.8/1
|
||||
- env: T=2.9/linux/ubuntu1804/1
|
||||
- env: T=2.9/cloud/3.5/1
|
||||
|
||||
branches:
|
||||
except:
|
||||
- "*-patch-*"
|
||||
- "revert-*-*"
|
||||
|
||||
build:
|
||||
ci:
|
||||
- tests/utils/shippable/timing.sh tests/utils/shippable/shippable.sh $T
|
||||
|
||||
integrations:
|
||||
notifications:
|
||||
- integrationName: email
|
||||
type: email
|
||||
on_success: never
|
||||
on_failure: never
|
||||
on_start: never
|
||||
on_pull_request: never
|
||||
@@ -1,20 +1,8 @@
|
||||
- name: Generate account keys
|
||||
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/{{ item }}.pem
|
||||
loop:
|
||||
- accountkey
|
||||
- accountkey2
|
||||
- accountkey3
|
||||
- accountkey4
|
||||
- accountkey5
|
||||
- name: Generate account key
|
||||
command: openssl ecparam -name prime256v1 -genkey -out {{ output_dir }}/accountkey.pem
|
||||
|
||||
- name: Parse account keys (to ease debugging some test failures)
|
||||
command: openssl ec -in {{ output_dir }}/{{ item }}.pem -noout -text
|
||||
loop:
|
||||
- accountkey
|
||||
- accountkey2
|
||||
- accountkey3
|
||||
- accountkey4
|
||||
- accountkey5
|
||||
- name: Parse account key (to ease debugging some test failures)
|
||||
command: openssl ec -in {{ output_dir }}/accountkey.pem -noout -text
|
||||
|
||||
- name: Do not try to create account
|
||||
acme_account:
|
||||
@@ -165,6 +153,12 @@
|
||||
contact: []
|
||||
register: account_modified_2_idempotent
|
||||
|
||||
- name: Generate new account key
|
||||
command: openssl ecparam -name secp384r1 -genkey -out {{ output_dir }}/accountkey2.pem
|
||||
|
||||
- name: Parse account key (to ease debugging some test failures)
|
||||
command: openssl ec -in {{ output_dir }}/accountkey2.pem -noout -text
|
||||
|
||||
- name: Change account key (check mode, diff)
|
||||
acme_account:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
@@ -248,36 +242,3 @@
|
||||
allow_creation: no
|
||||
ignore_errors: yes
|
||||
register: account_not_created_3
|
||||
|
||||
- name: Create account with External Account Binding
|
||||
acme_account:
|
||||
select_crypto_backend: "{{ select_crypto_backend }}"
|
||||
account_key_src: "{{ output_dir }}/{{ item.account }}.pem"
|
||||
acme_version: 2
|
||||
acme_directory: https://{{ acme_host }}:14000/dir
|
||||
validate_certs: no
|
||||
state: present
|
||||
allow_creation: yes
|
||||
terms_agreed: yes
|
||||
contact:
|
||||
- mailto:example@example.org
|
||||
external_account_binding:
|
||||
kid: "{{ item.kid }}"
|
||||
alg: "{{ item.alg }}"
|
||||
key: "{{ item.key }}"
|
||||
register: account_created_eab
|
||||
ignore_errors: yes
|
||||
loop:
|
||||
- account: accountkey3
|
||||
kid: kid-1
|
||||
alg: HS256
|
||||
key: zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W
|
||||
- account: accountkey4
|
||||
kid: kid-2
|
||||
alg: HS384
|
||||
key: b10lLJs8l1GPIzsLP0s6pMt8O0XVGnfTaCeROxQM0BIt2XrJMDHJZBM5NuQmQJQH
|
||||
- account: accountkey5
|
||||
kid: kid-3
|
||||
alg: HS512
|
||||
key: zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W
|
||||
- debug: var=account_created_eab
|
||||
|
||||
@@ -127,11 +127,3 @@
|
||||
that:
|
||||
- account_not_created_3 is failed
|
||||
- account_not_created_3.msg == 'Account does not exist or is deactivated.'
|
||||
|
||||
- name: Validate that the account with External Account Binding has been created
|
||||
assert:
|
||||
that:
|
||||
- account_created_eab.results[0] is changed
|
||||
- account_created_eab.results[1] is changed
|
||||
- account_created_eab.results[2] is failed
|
||||
- "'HS512 key must be at least 64 bytes long' in account_created_eab.results[2].msg"
|
||||
|
||||
@@ -251,7 +251,7 @@
|
||||
# the first chain will be found, and we need a second condition to
|
||||
# make sure that the first condition actually works. (The second
|
||||
# condition has been tested above.)
|
||||
- test_certificates: first
|
||||
- test_certificates: last
|
||||
subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}"
|
||||
- test_certificates: last
|
||||
issuer: "{{ acme_roots[1].subject }}"
|
||||
@@ -372,35 +372,35 @@
|
||||
register: cert_8_text
|
||||
# Dump certificate info
|
||||
- name: Dumping cert 1
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-1.pem"
|
||||
register: cert_1_info
|
||||
- name: Dumping cert 2
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-2.pem"
|
||||
register: cert_2_info
|
||||
- name: Dumping cert 3
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-3.pem"
|
||||
register: cert_3_info
|
||||
- name: Dumping cert 4
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-4.pem"
|
||||
register: cert_4_info
|
||||
- name: Dumping cert 5
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-5.pem"
|
||||
register: cert_5_info
|
||||
- name: Dumping cert 6
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-6.pem"
|
||||
register: cert_6_info
|
||||
- name: Dumping cert 7
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-7.pem"
|
||||
register: cert_7_info
|
||||
- name: Dumping cert 8
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/cert-8.pem"
|
||||
register: cert_8_info
|
||||
## GET ACCOUNT ORDERS #########################################################################
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
loop: "{{ query('nested', types, root_numbers) }}"
|
||||
|
||||
- name: Analyze root certificates
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/acme-root-{{ item }}.pem"
|
||||
loop: "{{ root_numbers }}"
|
||||
register: acme_roots
|
||||
|
||||
- name: Analyze intermediate certificates
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: "{{ output_dir }}/acme-intermediate-{{ item }}.pem"
|
||||
loop: "{{ root_numbers }}"
|
||||
register: acme_intermediates
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
shippable/posix/group1
|
||||
skip/aix
|
||||
|
||||
@@ -2,16 +2,18 @@
|
||||
command: '{{ ansible_python.executable }} -c ''import cryptography; print(cryptography.__version__)'''
|
||||
register: cryptography_version
|
||||
- block:
|
||||
- name: Make sure testhost directory exists
|
||||
- name: Archive test files
|
||||
community.general.archive:
|
||||
path: '{{ role_path }}/files/'
|
||||
dest: '{{ output_dir }}/files.tgz'
|
||||
- name: Create temporary directory to store files
|
||||
file:
|
||||
path: '{{ remote_tmp_dir }}/files/'
|
||||
state: directory
|
||||
when: ansible_version.string is version('2.10', '<')
|
||||
- name: Copy test files to testhost
|
||||
copy:
|
||||
src: '{{ role_path }}/files/'
|
||||
path: '{{ remote_tmp_dir }}/files/'
|
||||
- name: Unarchive test files on testhost
|
||||
unarchive:
|
||||
src: '{{ output_dir }}/files.tgz'
|
||||
dest: '{{ remote_tmp_dir }}/files/'
|
||||
remote_src: yes
|
||||
- name: Find root for cert 1
|
||||
certificate_complete_chain:
|
||||
input_chain: '{{ lookup(''file'', ''cert1-fullchain.pem'', rstrip=False) }}'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
shippable/posix/group1
|
||||
destructive
|
||||
needs/httptester
|
||||
skip/aix
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
hidden
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
- name: Include OS-specific variables
|
||||
include_vars: '{{ ansible_os_family }}.yml'
|
||||
when: not ansible_os_family == "Darwin"
|
||||
|
||||
- name: Install OpenSSL
|
||||
become: True
|
||||
package:
|
||||
name: '{{ openssl_package_name }}'
|
||||
when: not ansible_os_family == 'Darwin'
|
||||
|
||||
- name: Install pyOpenSSL (Python 3)
|
||||
become: True
|
||||
package:
|
||||
name: '{{ pyopenssl_package_name_python3 }}'
|
||||
when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '>=')
|
||||
|
||||
- name: Install pyOpenSSL (Python 2)
|
||||
become: True
|
||||
package:
|
||||
name: '{{ pyopenssl_package_name }}'
|
||||
when: not ansible_os_family == 'Darwin' and ansible_python_version is version('3.0', '<')
|
||||
|
||||
- name: Install pyOpenSSL (Darwin)
|
||||
become: True
|
||||
pip:
|
||||
name: pyOpenSSL
|
||||
when: ansible_os_family == 'Darwin'
|
||||
|
||||
- name: register pyOpenSSL version
|
||||
command: "{{ ansible_python.executable }} -c 'import OpenSSL; print(OpenSSL.__version__)'"
|
||||
register: pyopenssl_version
|
||||
|
||||
- name: register openssl version
|
||||
shell: "openssl version | cut -d' ' -f2"
|
||||
register: openssl_version
|
||||
|
||||
- name: register cryptography version
|
||||
command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'"
|
||||
register: cryptography_version
|
||||
@@ -0,0 +1,3 @@
|
||||
pyopenssl_package_name: python-openssl
|
||||
pyopenssl_package_name_python3: python3-openssl
|
||||
openssl_package_name: openssl
|
||||
@@ -0,0 +1,3 @@
|
||||
pyopenssl_package_name: py27-openssl
|
||||
pyopenssl_package_name_python3: py36-openssl
|
||||
openssl_package_name: openssl
|
||||
@@ -0,0 +1,3 @@
|
||||
pyopenssl_package_name: pyOpenSSL
|
||||
pyopenssl_package_name_python3: python3-pyOpenSSL
|
||||
openssl_package_name: openssl
|
||||
@@ -0,0 +1,3 @@
|
||||
pyopenssl_package_name: python-pyOpenSSL
|
||||
pyopenssl_package_name_python3: python3-pyOpenSSL
|
||||
openssl_package_name: openssl
|
||||
4
tests/integration/targets/incidental_x509_crl/aliases
Normal file
4
tests/integration/targets/incidental_x509_crl/aliases
Normal file
@@ -0,0 +1,4 @@
|
||||
x509_crl_info
|
||||
shippable/posix/incidental
|
||||
destructive
|
||||
skip/aix
|
||||
@@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
- incidental_setup_openssl
|
||||
289
tests/integration/targets/incidental_x509_crl/tasks/impl.yml
Normal file
289
tests/integration/targets/incidental_x509_crl/tasks/impl.yml
Normal file
@@ -0,0 +1,289 @@
|
||||
---
|
||||
- name: Create CRL 1 (check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
revocation_date: 20191001000000Z
|
||||
check_mode: yes
|
||||
register: crl_1_check
|
||||
- name: Create CRL 1
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
revocation_date: 20191001000000Z
|
||||
register: crl_1
|
||||
- name: Retrieve CRL 1 infos
|
||||
x509_crl_info:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
register: crl_1_info_1
|
||||
- name: Retrieve CRL 1 infos via file content
|
||||
x509_crl_info:
|
||||
content: '{{ lookup("file", output_dir ~ "/ca-crl1.crl") }}'
|
||||
register: crl_1_info_2
|
||||
- name: Create CRL 1 (idempotent, check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
revocation_date: 20191001000000Z
|
||||
check_mode: yes
|
||||
register: crl_1_idem_check
|
||||
- name: Create CRL 1 (idempotent)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
revocation_date: 20191013000000Z
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
revocation_date: 20191001000000Z
|
||||
register: crl_1_idem
|
||||
- name: Create CRL 1 (idempotent with content, check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
privatekey_content: "{{ lookup('file', output_dir ~ '/ca.key') }}"
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- content: "{{ lookup('file', output_dir ~ '/cert-1.pem') }}"
|
||||
revocation_date: 20191013000000Z
|
||||
- content: "{{ lookup('file', output_dir ~ '/cert-2.pem') }}"
|
||||
revocation_date: 20191013000000Z
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
revocation_date: 20191001000000Z
|
||||
check_mode: yes
|
||||
register: crl_1_idem_content_check
|
||||
- name: Create CRL 1 (idempotent with content)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl1.crl'
|
||||
privatekey_content: "{{ lookup('file', output_dir ~ '/ca.key') }}"
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: 20191013000000Z
|
||||
next_update: 20191113000000Z
|
||||
revoked_certificates:
|
||||
- content: "{{ lookup('file', output_dir ~ '/cert-1.pem') }}"
|
||||
revocation_date: 20191013000000Z
|
||||
- content: "{{ lookup('file', output_dir ~ '/cert-2.pem') }}"
|
||||
revocation_date: 20191013000000Z
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
revocation_date: 20191001000000Z
|
||||
register: crl_1_idem_content
|
||||
|
||||
- name: Create CRL 2 (check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
check_mode: yes
|
||||
register: crl_2_check
|
||||
- name: Create CRL 2
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
register: crl_2
|
||||
- name: Create CRL 2 (idempotent, check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
ignore_timestamps: yes
|
||||
check_mode: yes
|
||||
register: crl_2_idem_check
|
||||
- name: Create CRL 2 (idempotent)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-1.pem'
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
- serial_number: 1234
|
||||
ignore_timestamps: yes
|
||||
register: crl_2_idem
|
||||
- name: Create CRL 2 (idempotent update, check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- serial_number: 1235
|
||||
ignore_timestamps: yes
|
||||
mode: update
|
||||
check_mode: yes
|
||||
register: crl_2_idem_update_change_check
|
||||
- name: Create CRL 2 (idempotent update)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- serial_number: 1235
|
||||
ignore_timestamps: yes
|
||||
mode: update
|
||||
register: crl_2_idem_update_change
|
||||
- name: Create CRL 2 (idempotent update, check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
ignore_timestamps: yes
|
||||
mode: update
|
||||
check_mode: yes
|
||||
register: crl_2_idem_update_check
|
||||
- name: Create CRL 2 (idempotent update)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
ignore_timestamps: yes
|
||||
mode: update
|
||||
register: crl_2_idem_update
|
||||
- name: Create CRL 2 (changed timestamps, check mode)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
ignore_timestamps: no
|
||||
mode: update
|
||||
check_mode: yes
|
||||
register: crl_2_change_check
|
||||
- name: Create CRL 2 (changed timestamps)
|
||||
x509_crl:
|
||||
path: '{{ output_dir }}/ca-crl2.crl'
|
||||
privatekey_path: '{{ output_dir }}/ca.key'
|
||||
issuer:
|
||||
CN: Ansible
|
||||
last_update: +0d
|
||||
next_update: +0d
|
||||
revoked_certificates:
|
||||
- path: '{{ output_dir }}/cert-2.pem'
|
||||
reason: key_compromise
|
||||
reason_critical: yes
|
||||
invalidity_date: 20191012000000Z
|
||||
ignore_timestamps: no
|
||||
mode: update
|
||||
return_content: yes
|
||||
register: crl_2_change
|
||||
83
tests/integration/targets/incidental_x509_crl/tasks/main.yml
Normal file
83
tests/integration/targets/incidental_x509_crl/tasks/main.yml
Normal file
@@ -0,0 +1,83 @@
|
||||
---
|
||||
- set_fact:
|
||||
certificates:
|
||||
- name: ca
|
||||
subject:
|
||||
commonName: Ansible
|
||||
is_ca: yes
|
||||
- name: ca-2
|
||||
subject:
|
||||
commonName: Ansible Other CA
|
||||
is_ca: yes
|
||||
- name: cert-1
|
||||
subject_alt_name:
|
||||
- DNS:ansible.com
|
||||
- name: cert-2
|
||||
subject_alt_name:
|
||||
- DNS:example.com
|
||||
- name: cert-3
|
||||
subject_alt_name:
|
||||
- DNS:example.org
|
||||
- IP:1.2.3.4
|
||||
- name: cert-4
|
||||
subject_alt_name:
|
||||
- DNS:test.ansible.com
|
||||
- DNS:b64.ansible.com
|
||||
|
||||
- name: Generate private keys
|
||||
openssl_privatekey:
|
||||
path: '{{ output_dir }}/{{ item.name }}.key'
|
||||
type: ECC
|
||||
curve: secp256r1
|
||||
loop: "{{ certificates }}"
|
||||
|
||||
- name: Generate CSRs
|
||||
openssl_csr:
|
||||
path: '{{ output_dir }}/{{ item.name }}.csr'
|
||||
privatekey_path: '{{ output_dir }}/{{ item.name }}.key'
|
||||
subject: "{{ item.subject | default(omit) }}"
|
||||
subject_alt_name: "{{ item.subject_alt_name | default(omit) }}"
|
||||
basic_constraints: "{{ 'CA:TRUE' if item.is_ca | default(false) else omit }}"
|
||||
use_common_name_for_san: no
|
||||
loop: "{{ certificates }}"
|
||||
|
||||
- name: Generate CA certificates
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/{{ item.name }}.pem'
|
||||
csr_path: '{{ output_dir }}/{{ item.name }}.csr'
|
||||
privatekey_path: '{{ output_dir }}/{{ item.name }}.key'
|
||||
provider: selfsigned
|
||||
loop: "{{ certificates }}"
|
||||
when: item.is_ca | default(false)
|
||||
|
||||
- name: Generate other certificates
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/{{ item.name }}.pem'
|
||||
csr_path: '{{ output_dir }}/{{ item.name }}.csr'
|
||||
provider: ownca
|
||||
ownca_path: '{{ output_dir }}/ca.pem'
|
||||
ownca_privatekey_path: '{{ output_dir }}/ca.key'
|
||||
loop: "{{ certificates }}"
|
||||
when: not (item.is_ca | default(false))
|
||||
|
||||
- name: Get certificate infos
|
||||
openssl_certificate_info:
|
||||
path: '{{ output_dir }}/{{ item }}.pem'
|
||||
loop:
|
||||
- cert-1
|
||||
- cert-2
|
||||
- cert-3
|
||||
- cert-4
|
||||
register: certificate_infos
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
|
||||
- import_tasks: ../tests/validate.yml
|
||||
vars:
|
||||
select_crypto_backend: cryptography
|
||||
|
||||
when: cryptography_version.stdout is version('1.2', '>=')
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
- name: Validate CRL 1
|
||||
assert:
|
||||
that:
|
||||
- crl_1_check is changed
|
||||
- crl_1 is changed
|
||||
- crl_1_idem_check is not changed
|
||||
- crl_1_idem is not changed
|
||||
- crl_1_idem_content_check is not changed
|
||||
- crl_1_idem_content is not changed
|
||||
|
||||
- name: Validate CRL 1 info
|
||||
assert:
|
||||
that:
|
||||
- crl_1_info_1 == crl_1_info_2
|
||||
- crl_1_info_1.digest == 'ecdsa-with-SHA256'
|
||||
- crl_1_info_1.issuer | length == 1
|
||||
- crl_1_info_1.issuer.commonName == 'Ansible'
|
||||
- crl_1_info_1.issuer_ordered | length == 1
|
||||
- crl_1_info_1.last_update == '20191013000000Z'
|
||||
- crl_1_info_1.next_update == '20191113000000Z'
|
||||
- crl_1_info_1.revoked_certificates | length == 3
|
||||
- crl_1_info_1.revoked_certificates[0].invalidity_date is none
|
||||
- crl_1_info_1.revoked_certificates[0].invalidity_date_critical == false
|
||||
- crl_1_info_1.revoked_certificates[0].issuer is none
|
||||
- crl_1_info_1.revoked_certificates[0].issuer_critical == false
|
||||
- crl_1_info_1.revoked_certificates[0].reason is none
|
||||
- crl_1_info_1.revoked_certificates[0].reason_critical == false
|
||||
- crl_1_info_1.revoked_certificates[0].revocation_date == '20191013000000Z'
|
||||
- crl_1_info_1.revoked_certificates[0].serial_number == certificate_infos.results[0].serial_number
|
||||
- crl_1_info_1.revoked_certificates[1].invalidity_date == '20191012000000Z'
|
||||
- crl_1_info_1.revoked_certificates[1].invalidity_date_critical == false
|
||||
- crl_1_info_1.revoked_certificates[1].issuer is none
|
||||
- crl_1_info_1.revoked_certificates[1].issuer_critical == false
|
||||
- crl_1_info_1.revoked_certificates[1].reason == 'key_compromise'
|
||||
- crl_1_info_1.revoked_certificates[1].reason_critical == true
|
||||
- crl_1_info_1.revoked_certificates[1].revocation_date == '20191013000000Z'
|
||||
- crl_1_info_1.revoked_certificates[1].serial_number == certificate_infos.results[1].serial_number
|
||||
- crl_1_info_1.revoked_certificates[2].invalidity_date is none
|
||||
- crl_1_info_1.revoked_certificates[2].invalidity_date_critical == false
|
||||
- crl_1_info_1.revoked_certificates[2].issuer is none
|
||||
- crl_1_info_1.revoked_certificates[2].issuer_critical == false
|
||||
- crl_1_info_1.revoked_certificates[2].reason is none
|
||||
- crl_1_info_1.revoked_certificates[2].reason_critical == false
|
||||
- crl_1_info_1.revoked_certificates[2].revocation_date == '20191001000000Z'
|
||||
- crl_1_info_1.revoked_certificates[2].serial_number == 1234
|
||||
|
||||
- name: Validate CRL 2
|
||||
assert:
|
||||
that:
|
||||
- crl_2_check is changed
|
||||
- crl_2 is changed
|
||||
- crl_2_idem_check is not changed
|
||||
- crl_2_idem is not changed
|
||||
- crl_2_idem_update_change_check is changed
|
||||
- crl_2_idem_update_change is changed
|
||||
- crl_2_idem_update_check is not changed
|
||||
- crl_2_idem_update is not changed
|
||||
- crl_2_change_check is changed
|
||||
- crl_2_change is changed
|
||||
- crl_2_change.crl == lookup('file', output_dir ~ '/ca-crl2.crl', rstrip=False)
|
||||
@@ -1,4 +1,5 @@
|
||||
shippable/posix/group1
|
||||
shippable/posix/group4
|
||||
skip/aix
|
||||
skip/osx
|
||||
skip/freebsd
|
||||
skip/docker
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
shippable/posix/group1
|
||||
destructive
|
||||
skip/aix
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
shippable/posix/group1
|
||||
destructive
|
||||
skip/aix
|
||||
|
||||
3
tests/integration/targets/openssl_certificate/aliases
Normal file
3
tests/integration/targets/openssl_certificate/aliases
Normal file
@@ -0,0 +1,3 @@
|
||||
shippable/posix/group5
|
||||
destructive
|
||||
skip/aix
|
||||
@@ -31,7 +31,7 @@
|
||||
useCommonNameForSAN: no
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (no extensions)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
csr_path: '{{ output_dir }}/csr_noext.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -40,7 +40,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (with SANs)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_sans.pem'
|
||||
csr_path: '{{ output_dir }}/csr_sans.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -49,7 +49,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (should fail)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
@@ -59,7 +59,7 @@
|
||||
register: extension_missing_san
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_sans.pem'
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
@@ -70,7 +70,7 @@
|
||||
register: extension_san
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (strict)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_sans.pem'
|
||||
provider: assertonly
|
||||
subject_alt_name:
|
||||
@@ -82,7 +82,7 @@
|
||||
register: extension_san_strict
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that key_usage is there (should fail)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
provider: assertonly
|
||||
key_usage:
|
||||
@@ -92,7 +92,7 @@
|
||||
register: extension_missing_ku
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Assert that extended_key_usage is there (should fail)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
provider: assertonly
|
||||
extended_key_usage:
|
||||
@@ -113,7 +113,7 @@
|
||||
- "'Found no extendedKeyUsage extension' in extension_missing_eku.msg"
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 1
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
privatekey_passphrase: hunter2
|
||||
@@ -123,7 +123,7 @@
|
||||
register: passphrase_error_1
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 2
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
|
||||
privatekey_passphrase: wrong_password
|
||||
@@ -133,7 +133,7 @@
|
||||
register: passphrase_error_2
|
||||
|
||||
- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 3
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_noext.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
|
||||
provider: assertonly
|
||||
@@ -11,7 +11,7 @@
|
||||
commonName: www.example.com
|
||||
|
||||
- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/has_expired_cert.pem'
|
||||
csr_path: '{{ output_dir }}/has_expired_csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/has_expired_privatekey.pem'
|
||||
@@ -27,7 +27,7 @@
|
||||
when: select_crypto_backend == 'cryptography' # So we create it with 'command'
|
||||
|
||||
- name: "(Expired) Check task fails because cert is expired (has_expired: false)"
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
provider: assertonly
|
||||
path: "{{ output_dir }}/has_expired_cert.pem"
|
||||
has_expired: false
|
||||
@@ -40,7 +40,7 @@
|
||||
that: expired_cert_check is failed
|
||||
|
||||
- name: "(Expired) Check expired cert check is ignored (has_expired: true)"
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
provider: assertonly
|
||||
path: "{{ output_dir }}/has_expired_cert.pem"
|
||||
has_expired: true
|
||||
@@ -34,7 +34,7 @@
|
||||
basic_constraints_critical: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ca_cert.pem'
|
||||
csr_path: '{{ output_dir }}/ca_csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/ca_privatekey.pem'
|
||||
@@ -43,7 +43,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ca_cert_pw.pem'
|
||||
csr_path: '{{ output_dir }}/ca_csr_pw.csr'
|
||||
privatekey_path: '{{ output_dir }}/ca_privatekey_pw.pem'
|
||||
@@ -53,7 +53,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -66,7 +66,7 @@
|
||||
register: ownca_certificate
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -79,7 +79,7 @@
|
||||
register: ownca_certificate_idempotence
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (check mode)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -91,7 +91,7 @@
|
||||
check_mode: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
provider: assertonly
|
||||
@@ -107,7 +107,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca v2 certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_v2.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -121,7 +121,7 @@
|
||||
ignore_errors: true
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate2
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert2.pem'
|
||||
csr_path: '{{ output_dir }}/csr2.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey2.pem'
|
||||
@@ -132,7 +132,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate2
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert2.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekey2.pem'
|
||||
provider: assertonly
|
||||
@@ -160,7 +160,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
provider: ownca
|
||||
ownca_not_before: 20181023133742Z
|
||||
ownca_not_after: 20191023133742Z
|
||||
@@ -172,7 +172,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with relative notBefore and notAfter
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
provider: ownca
|
||||
ownca_not_before: +1s
|
||||
ownca_not_after: +52w
|
||||
@@ -184,7 +184,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca ECC certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ecc.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -196,7 +196,7 @@
|
||||
register: ownca_certificate_ecc
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned certificate (privatekey passphrase)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ecc_2.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert_pw.pem'
|
||||
@@ -208,7 +208,7 @@
|
||||
register: selfsigned_certificate_passphrase
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 1)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_pw1.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -221,7 +221,7 @@
|
||||
register: passphrase_error_1
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 2)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_pw2.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -234,7 +234,7 @@
|
||||
register: passphrase_error_2
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 3)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_pw3.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -250,7 +250,7 @@
|
||||
dest: "{{ output_dir }}/ownca_broken.pem"
|
||||
content: "broken"
|
||||
- name: Regenerate broken cert
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_broken.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -261,7 +261,7 @@
|
||||
register: ownca_broken
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Backup test
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_backup.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -272,7 +272,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: ownca_backup_1
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Backup test (idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_backup.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -283,7 +283,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: ownca_backup_2
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Backup test (change)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_backup.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -294,7 +294,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: ownca_backup_3
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Backup test (remove)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_backup.pem'
|
||||
state: absent
|
||||
provider: ownca
|
||||
@@ -302,7 +302,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: ownca_backup_4
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Backup test (remove, idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_backup.pem'
|
||||
state: absent
|
||||
provider: ownca
|
||||
@@ -311,7 +311,7 @@
|
||||
register: ownca_backup_5
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -324,7 +324,7 @@
|
||||
register: ownca_subject_key_identifier_1
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (idempotency)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -337,7 +337,7 @@
|
||||
register: ownca_subject_key_identifier_2
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -350,7 +350,7 @@
|
||||
register: ownca_subject_key_identifier_3
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (remove idempotency)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -363,7 +363,7 @@
|
||||
register: ownca_subject_key_identifier_4
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create subject key identifier (re-enable)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -376,7 +376,7 @@
|
||||
register: ownca_subject_key_identifier_5
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_aki.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -389,7 +389,7 @@
|
||||
register: ownca_authority_key_identifier_1
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (idempotency)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_aki.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -402,7 +402,7 @@
|
||||
register: ownca_authority_key_identifier_2
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_aki.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -415,7 +415,7 @@
|
||||
register: ownca_authority_key_identifier_3
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (remove idempotency)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_aki.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -428,7 +428,7 @@
|
||||
register: ownca_authority_key_identifier_4
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Create authority key identifier (re-add)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_aki.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -469,7 +469,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_{{ item }}.pem'
|
||||
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -484,7 +484,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_{{ item }}.pem'
|
||||
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert.pem'
|
||||
@@ -529,7 +529,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ca_cert_{{ item }}.pem'
|
||||
csr_path: '{{ output_dir }}/ca_csr_{{ item }}.csr'
|
||||
privatekey_path: '{{ output_dir }}/ca_privatekey_{{ item }}.pem'
|
||||
@@ -542,7 +542,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_{{ item }}_2.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert_{{ item }}.pem'
|
||||
@@ -558,7 +558,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/ownca_cert_{{ item }}_2.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
ownca_path: '{{ output_dir }}/ca_cert_{{ item }}.pem'
|
||||
@@ -9,7 +9,7 @@
|
||||
privatekey_path: '{{ output_dir }}/removal_privatekey.pem'
|
||||
|
||||
- name: (Removal, {{select_crypto_backend}}) Generate selfsigned certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/removal_cert.pem'
|
||||
csr_path: '{{ output_dir }}/removal_csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/removal_privatekey.pem'
|
||||
@@ -23,7 +23,7 @@
|
||||
register: removal_1_prestat
|
||||
|
||||
- name: "(Removal, {{select_crypto_backend}}) Remove certificate"
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: "{{ output_dir }}/removal_cert.pem"
|
||||
state: absent
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
@@ -36,7 +36,7 @@
|
||||
register: removal_1_poststat
|
||||
|
||||
- name: "(Removal, {{select_crypto_backend}}) Remove certificate (idempotent)"
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: "{{ output_dir }}/removal_cert.pem"
|
||||
state: absent
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
@@ -25,7 +25,7 @@
|
||||
commonName: www.example.org
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -36,7 +36,7 @@
|
||||
register: selfsigned_certificate
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -47,7 +47,7 @@
|
||||
register: selfsigned_certificate_idempotence
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -57,7 +57,7 @@
|
||||
check_mode: yes
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode, other CSR)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert.pem'
|
||||
csr_path: '{{ output_dir }}/csr_minimal_change.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -68,7 +68,7 @@
|
||||
register: selfsigned_certificate_csr_minimal_change
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
provider: assertonly
|
||||
@@ -82,7 +82,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned v2 certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_v2.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -117,7 +117,7 @@
|
||||
- biometricInfo
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate2
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert2.pem'
|
||||
csr_path: '{{ output_dir }}/csr2.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey2.pem'
|
||||
@@ -126,7 +126,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate2
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert2.pem'
|
||||
privatekey_path: '{{ output_dir }}/privatekey2.pem'
|
||||
provider: assertonly
|
||||
@@ -163,7 +163,7 @@
|
||||
path: "{{ output_dir }}/csr3.pem"
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create certificate3 with notBefore and notAfter
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
provider: selfsigned
|
||||
selfsigned_not_before: 20181023133742Z
|
||||
selfsigned_not_after: 20191023133742Z
|
||||
@@ -187,7 +187,7 @@
|
||||
commonName: www.example.com
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_ecc.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -205,7 +205,7 @@
|
||||
commonName: www.example.com
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (privatekey passphrase)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_pass.pem'
|
||||
csr_path: '{{ output_dir }}/csr_pass.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
|
||||
@@ -216,7 +216,7 @@
|
||||
register: selfsigned_certificate_passphrase
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 1)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_pw1.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -228,7 +228,7 @@
|
||||
register: passphrase_error_1
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 2)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_pw2.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
|
||||
@@ -240,7 +240,7 @@
|
||||
register: passphrase_error_2
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 3)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_pw3.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekeypw.pem'
|
||||
@@ -255,7 +255,7 @@
|
||||
dest: "{{ output_dir }}/cert_broken.pem"
|
||||
content: "broken"
|
||||
- name: Regenerate broken cert
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_broken.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -264,7 +264,7 @@
|
||||
register: selfsigned_broken
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Backup test
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -274,7 +274,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: selfsigned_backup_1
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -284,7 +284,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: selfsigned_backup_2
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (change)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
|
||||
csr_path: '{{ output_dir }}/csr.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -294,7 +294,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: selfsigned_backup_3
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (remove)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
|
||||
state: absent
|
||||
provider: selfsigned
|
||||
@@ -302,7 +302,7 @@
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: selfsigned_backup_4
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Backup test (remove, idempotent)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_backup.pem'
|
||||
state: absent
|
||||
provider: selfsigned
|
||||
@@ -311,7 +311,7 @@
|
||||
register: selfsigned_backup_5
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -323,7 +323,7 @@
|
||||
register: selfsigned_subject_key_identifier_1
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (idempotency)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -335,7 +335,7 @@
|
||||
register: selfsigned_subject_key_identifier_2
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -347,7 +347,7 @@
|
||||
register: selfsigned_subject_key_identifier_3
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (remove idempotency)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -359,7 +359,7 @@
|
||||
register: selfsigned_subject_key_identifier_4
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Create subject key identifier test (re-enable)
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/selfsigned_cert_ski.pem'
|
||||
csr_path: '{{ output_dir }}/csr_ecc.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_ecc.pem'
|
||||
@@ -399,7 +399,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_{{ item }}.pem'
|
||||
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem'
|
||||
@@ -413,7 +413,7 @@
|
||||
ignore_errors: yes
|
||||
|
||||
- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_{{ item }}.pem'
|
||||
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey_{{ item }}.pem'
|
||||
@@ -1,2 +1,3 @@
|
||||
shippable/posix/group1
|
||||
destructive
|
||||
skip/aix
|
||||
@@ -3,7 +3,7 @@
|
||||
msg: "Executing tests with backend {{ select_crypto_backend }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: '{{ output_dir }}/cert_1.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
@@ -36,7 +36,7 @@
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info directly
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
content: '{{ lookup("file", output_dir ~ "/cert_1.pem") }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result_direct
|
||||
@@ -47,7 +47,7 @@
|
||||
- result == result_direct
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: '{{ output_dir }}/cert_2.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
valid_at:
|
||||
@@ -66,7 +66,7 @@
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: '{{ output_dir }}/cert_3.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
@@ -88,7 +88,7 @@
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: '{{ output_dir }}/cert_4.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
@@ -106,7 +106,7 @@
|
||||
info_results: "{{ info_results + [result] }}"
|
||||
|
||||
- name: ({{select_crypto_backend}}) Get certificate info for packaged cert 1
|
||||
x509_certificate_info:
|
||||
openssl_certificate_info:
|
||||
path: '{{ role_path }}/files/cert1.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: result
|
||||
@@ -113,7 +113,7 @@
|
||||
authority_key_identifier: '{{ "44:55:66:77" if cryptography_version.stdout is version("1.3", ">=") else omit }}'
|
||||
|
||||
- name: Generate selfsigned certificates
|
||||
x509_certificate:
|
||||
openssl_certificate:
|
||||
path: '{{ output_dir }}/cert_{{ item }}.pem'
|
||||
csr_path: '{{ output_dir }}/csr_{{ item }}.csr'
|
||||
privatekey_path: '{{ output_dir }}/privatekey.pem'
|
||||
@@ -1,2 +1,3 @@
|
||||
shippable/posix/group1
|
||||
destructive
|
||||
skip/aix
|
||||
|
||||
@@ -550,17 +550,29 @@
|
||||
- Encipher Only
|
||||
- decipherOnly
|
||||
key_usage_critical: yes
|
||||
extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}'
|
||||
subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}'
|
||||
extended_key_usage:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
subject_alt_name:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
basic_constraints:
|
||||
- "CA:TRUE"
|
||||
- "pathlen:23"
|
||||
basic_constraints_critical: yes
|
||||
name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}'
|
||||
name_constraints_excluded:
|
||||
- "DNS:.example.com"
|
||||
- "DNS:.org"
|
||||
name_constraints_critical: yes
|
||||
ocsp_must_staple: yes
|
||||
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
@@ -568,61 +580,9 @@
|
||||
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
vars:
|
||||
value_for_extended_key_usage_pyopenssl:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
value_for_extended_key_usage:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
- 1.2.3.4.5.6
|
||||
value_for_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
value_for_san_pyopenssl:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
- "RID:1.2.3.4"
|
||||
value_for_san:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
- "RID:1.2.3.4"
|
||||
- "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71"
|
||||
- "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost"
|
||||
- "dirName:O = Example Net, CN = example.net"
|
||||
- "dirName:/O=Example Com/CN=example.com"
|
||||
value_for_name_constraints_permitted:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/24"
|
||||
- "IP:::1:0:0/112"
|
||||
value_for_name_constraints_permitted_pyopenssl:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/255.255.255.0"
|
||||
register: everything_1
|
||||
|
||||
- name: Generate CSR with everything (idempotent, check mode)
|
||||
@@ -658,17 +618,29 @@
|
||||
- Encipher Only
|
||||
- decipherOnly
|
||||
key_usage_critical: yes
|
||||
extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}'
|
||||
subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}'
|
||||
extended_key_usage:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
subject_alt_name:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
basic_constraints:
|
||||
- "CA:TRUE"
|
||||
- "pathlen:23"
|
||||
basic_constraints_critical: yes
|
||||
name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}'
|
||||
name_constraints_excluded:
|
||||
- "DNS:.org"
|
||||
- "DNS:.example.com"
|
||||
name_constraints_critical: yes
|
||||
ocsp_must_staple: yes
|
||||
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
@@ -676,61 +648,9 @@
|
||||
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
vars:
|
||||
value_for_extended_key_usage_pyopenssl:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
value_for_extended_key_usage:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
- 1.2.3.4.5.6
|
||||
value_for_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
value_for_san_pyopenssl:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
- "RID:1.2.3.4"
|
||||
value_for_san:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
- "RID:1.2.3.4"
|
||||
- "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71"
|
||||
- "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost"
|
||||
- "dirName:O=Example Net,CN=example.net"
|
||||
- "dirName:/O = Example Com/CN = example.com"
|
||||
value_for_name_constraints_permitted:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/255.255.255.0"
|
||||
- "IP:0::0:1:0:0/112"
|
||||
value_for_name_constraints_permitted_pyopenssl:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/255.255.255.0"
|
||||
check_mode: yes
|
||||
register: everything_2
|
||||
|
||||
@@ -767,17 +687,29 @@
|
||||
- Encipher Only
|
||||
- decipherOnly
|
||||
key_usage_critical: yes
|
||||
extended_key_usage: '{{ value_for_extended_key_usage if select_crypto_backend != "pyopenssl" else value_for_extended_key_usage_pyopenssl }}'
|
||||
subject_alt_name: '{{ value_for_san if select_crypto_backend != "pyopenssl" else value_for_san_pyopenssl }}'
|
||||
extended_key_usage:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
subject_alt_name:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
basic_constraints:
|
||||
- "CA:TRUE"
|
||||
- "pathlen:23"
|
||||
basic_constraints_critical: yes
|
||||
name_constraints_permitted: '{{ value_for_name_constraints_permitted if select_crypto_backend != "pyopenssl" else value_for_name_constraints_permitted_pyopenssl }}'
|
||||
name_constraints_excluded:
|
||||
- "DNS:.org"
|
||||
- "DNS:.example.com"
|
||||
name_constraints_critical: yes
|
||||
ocsp_must_staple: yes
|
||||
subject_key_identifier: '{{ "00:11:22:33" if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
authority_key_identifier: '{{ "44:55:66:77" if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
@@ -785,69 +717,11 @@
|
||||
authority_cert_serial_number: '{{ 12345 if select_crypto_backend != "pyopenssl" else omit }}'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
vars:
|
||||
value_for_extended_key_usage_pyopenssl:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
value_for_extended_key_usage:
|
||||
- serverAuth # the same as "TLS Web Server Authentication"
|
||||
- TLS Web Server Authentication
|
||||
- TLS Web Client Authentication
|
||||
- Code Signing
|
||||
- E-mail Protection
|
||||
- timeStamping
|
||||
- OCSPSigning
|
||||
- Any Extended Key Usage
|
||||
- qcStatements
|
||||
- DVCS
|
||||
- IPSec User
|
||||
- biometricInfo
|
||||
- 1.2.3.4.5.6
|
||||
value_for_authority_cert_issuer:
|
||||
- "DNS:ca.example.org"
|
||||
- "IP:1.2.3.4"
|
||||
value_for_san_pyopenssl:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
- "RID:1.2.3.4"
|
||||
value_for_san:
|
||||
- "DNS:www.ansible.com"
|
||||
- "IP:1.2.3.4"
|
||||
- "IP:::1"
|
||||
- "email:test@example.org"
|
||||
- "URI:https://example.org/test/index.html"
|
||||
- "RID:1.2.3.4"
|
||||
- "otherName:1.2.3.4;0c:07:63:65:72:74:72:65:71"
|
||||
- "otherName:1.3.6.1.4.1.311.20.2.3;UTF8:bob@localhost"
|
||||
- "dirName:O =Example Net, CN= example.net"
|
||||
- "dirName:/O =Example Com/CN= example.com"
|
||||
value_for_name_constraints_permitted:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/255.255.255.0"
|
||||
- "IP:0::0:1:0:0/112"
|
||||
value_for_name_constraints_permitted_pyopenssl:
|
||||
- "DNS:www.example.com"
|
||||
- "IP:1.2.3.0/255.255.255.0"
|
||||
register: everything_3
|
||||
|
||||
- name: Get info from CSR with everything
|
||||
community.crypto.openssl_csr_info:
|
||||
path: '{{ output_dir }}/csr_everything.csr'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
register: everything_info
|
||||
|
||||
- name: Ed25519 and Ed448 tests (for cryptography >= 2.6)
|
||||
block:
|
||||
- name: Generate privatekeys
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user