mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-07 05:43:06 +00:00
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35ef2edb3f | ||
|
|
ebcf866891 | ||
|
|
60c6d87b05 | ||
|
|
2aa38fe247 | ||
|
|
d19faa1627 | ||
|
|
e910f299b9 | ||
|
|
2ebf26854e | ||
|
|
7ff067937a | ||
|
|
2727b74cc7 | ||
|
|
3bb9c5f9a7 | ||
|
|
5623590c77 | ||
|
|
29050913b3 | ||
|
|
aead2bf783 | ||
|
|
2aaae70372 | ||
|
|
de29a258c6 | ||
|
|
8ca5b480e4 | ||
|
|
f6ec785b0d | ||
|
|
4834c0cb4b | ||
|
|
1f0016c616 | ||
|
|
74e4be139f | ||
|
|
b584336b62 | ||
|
|
20b0d7a298 | ||
|
|
5741f24aa9 | ||
|
|
b5dfc9fc75 | ||
|
|
4318e95618 | ||
|
|
113cbb6eb8 | ||
|
|
3ebed060b7 | ||
|
|
270ef0db47 | ||
|
|
0b306b436a | ||
|
|
c5519bc557 | ||
|
|
23d6f2ae75 | ||
|
|
9da7c54ae9 | ||
|
|
928cb3aa9b | ||
|
|
3e6815d73f | ||
|
|
cb08f56066 | ||
|
|
e3f486a063 | ||
|
|
0a1e25e16a | ||
|
|
ff4966ad3f | ||
|
|
0cb10be2d5 | ||
|
|
901863989b | ||
|
|
99377764c1 | ||
|
|
426d70fbcf | ||
|
|
f315722b31 | ||
|
|
73afe8e742 | ||
|
|
db67b8a857 | ||
|
|
e05475d58a | ||
|
|
dceee8f50e | ||
|
|
b893252ad1 | ||
|
|
0755a2b657 | ||
|
|
90bf8b0b2e | ||
|
|
5ff28c751d | ||
|
|
7b08edb5a4 | ||
|
|
fbadcbeb29 | ||
|
|
e991375f55 | ||
|
|
33c99014ae | ||
|
|
fbd6ff6ead | ||
|
|
c4ab2eb3b5 | ||
|
|
44b6df0ce5 | ||
|
|
14a42505a9 | ||
|
|
44cbd33cb7 | ||
|
|
4411a71d06 | ||
|
|
bfe37bc668 |
@@ -19,6 +19,11 @@ schedules:
|
|||||||
branches:
|
branches:
|
||||||
include:
|
include:
|
||||||
- main
|
- main
|
||||||
|
- cron: 0 12 * * 0
|
||||||
|
displayName: Weekly (old stable branches)
|
||||||
|
always: true
|
||||||
|
branches:
|
||||||
|
include:
|
||||||
- stable-*
|
- stable-*
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
@@ -108,16 +113,12 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: devel/linux/{0}/1
|
testFormat: devel/linux/{0}/1
|
||||||
targets:
|
targets:
|
||||||
- name: CentOS 6
|
|
||||||
test: centos6
|
|
||||||
- name: CentOS 7
|
- name: CentOS 7
|
||||||
test: centos7
|
test: centos7
|
||||||
- name: CentOS 8
|
|
||||||
test: centos8
|
|
||||||
- name: Fedora 33
|
|
||||||
test: fedora33
|
|
||||||
- name: Fedora 34
|
- name: Fedora 34
|
||||||
test: fedora34
|
test: fedora34
|
||||||
|
- name: Fedora 35
|
||||||
|
test: fedora35
|
||||||
- name: openSUSE 15 py2
|
- name: openSUSE 15 py2
|
||||||
test: opensuse15py2
|
test: opensuse15py2
|
||||||
- name: openSUSE 15 py3
|
- name: openSUSE 15 py3
|
||||||
@@ -134,8 +135,8 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: 2.12/linux/{0}/1
|
testFormat: 2.12/linux/{0}/1
|
||||||
targets:
|
targets:
|
||||||
- name: CentOS 8
|
- name: CentOS 6
|
||||||
test: centos8
|
test: centos6
|
||||||
- name: Fedora 33
|
- name: Fedora 33
|
||||||
test: fedora33
|
test: fedora33
|
||||||
- name: openSUSE 15 py3
|
- name: openSUSE 15 py3
|
||||||
@@ -152,8 +153,6 @@ stages:
|
|||||||
targets:
|
targets:
|
||||||
- name: CentOS 7
|
- name: CentOS 7
|
||||||
test: centos7
|
test: centos7
|
||||||
- name: CentOS 8
|
|
||||||
test: centos8
|
|
||||||
- name: Fedora 32
|
- name: Fedora 32
|
||||||
test: fedora32
|
test: fedora32
|
||||||
- name: openSUSE 15 py2
|
- name: openSUSE 15 py2
|
||||||
@@ -202,14 +201,14 @@ stages:
|
|||||||
parameters:
|
parameters:
|
||||||
testFormat: devel/{0}/1
|
testFormat: devel/{0}/1
|
||||||
targets:
|
targets:
|
||||||
- name: macOS 11.1
|
- name: macOS 12.0
|
||||||
test: macos/11.1
|
test: macos/12.0
|
||||||
- name: RHEL 7.9
|
- name: RHEL 7.9
|
||||||
test: rhel/7.9
|
test: rhel/7.9
|
||||||
- name: RHEL 8.4
|
- name: RHEL 8.5
|
||||||
test: rhel/8.4
|
test: rhel/8.5
|
||||||
- name: FreeBSD 12.2
|
- name: FreeBSD 12.3
|
||||||
test: freebsd/12.2
|
test: freebsd/12.3
|
||||||
- name: FreeBSD 13.0
|
- name: FreeBSD 13.0
|
||||||
test: freebsd/13.0
|
test: freebsd/13.0
|
||||||
- stage: Remote_2_12
|
- stage: Remote_2_12
|
||||||
@@ -274,7 +273,6 @@ stages:
|
|||||||
nameFormat: Python {0}
|
nameFormat: Python {0}
|
||||||
testFormat: devel/cloud/{0}/1
|
testFormat: devel/cloud/{0}/1
|
||||||
targets:
|
targets:
|
||||||
- test: 2.6
|
|
||||||
- test: 2.7
|
- test: 2.7
|
||||||
- test: 3.5
|
- test: 3.5
|
||||||
- test: 3.6
|
- test: 3.6
|
||||||
@@ -291,6 +289,7 @@ stages:
|
|||||||
nameFormat: Python {0}
|
nameFormat: Python {0}
|
||||||
testFormat: 2.12/cloud/{0}/1
|
testFormat: 2.12/cloud/{0}/1
|
||||||
targets:
|
targets:
|
||||||
|
- test: 2.6
|
||||||
- test: 3.9
|
- test: 3.9
|
||||||
- stage: Cloud_2_11
|
- stage: Cloud_2_11
|
||||||
displayName: Cloud 2.11
|
displayName: Cloud 2.11
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ mkdir "${agent_temp_directory}/coverage/"
|
|||||||
|
|
||||||
options=(--venv --venv-system-site-packages --color -v)
|
options=(--venv --venv-system-site-packages --color -v)
|
||||||
|
|
||||||
ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}"
|
ansible-test coverage combine --group-by command --export "${agent_temp_directory}/coverage/" "${options[@]}"
|
||||||
|
|
||||||
if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then
|
if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then
|
||||||
# Only analyze coverage if the installed version of ansible-test supports it.
|
# Only analyze coverage if the installed version of ansible-test supports it.
|
||||||
|
|||||||
101
.azure-pipelines/scripts/publish-codecov.py
Executable file
101
.azure-pipelines/scripts/publish-codecov.py
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
Upload code coverage reports to codecov.io.
|
||||||
|
Multiple coverage files from multiple languages are accepted and aggregated after upload.
|
||||||
|
Python coverage, as well as PowerShell and Python stubs can all be uploaded.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import dataclasses
|
||||||
|
import pathlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import typing as t
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class CoverageFile:
|
||||||
|
name: str
|
||||||
|
path: pathlib.Path
|
||||||
|
flags: t.List[str]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass(frozen=True)
|
||||||
|
class Args:
|
||||||
|
dry_run: bool
|
||||||
|
path: pathlib.Path
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args() -> Args:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('-n', '--dry-run', action='store_true')
|
||||||
|
parser.add_argument('path', type=pathlib.Path)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Store arguments in a typed dataclass
|
||||||
|
fields = dataclasses.fields(Args)
|
||||||
|
kwargs = {field.name: getattr(args, field.name) for field in fields}
|
||||||
|
|
||||||
|
return Args(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def process_files(directory: pathlib.Path) -> t.Tuple[CoverageFile, ...]:
|
||||||
|
processed = []
|
||||||
|
for file in directory.joinpath('reports').glob('coverage*.xml'):
|
||||||
|
name = file.stem.replace('coverage=', '')
|
||||||
|
|
||||||
|
# Get flags from name
|
||||||
|
flags = name.replace('-powershell', '').split('=') # Drop '-powershell' suffix
|
||||||
|
flags = [flag if not flag.startswith('stub') else flag.split('-')[0] for flag in flags] # Remove "-01" from stub files
|
||||||
|
|
||||||
|
processed.append(CoverageFile(name, file, flags))
|
||||||
|
|
||||||
|
return tuple(processed)
|
||||||
|
|
||||||
|
|
||||||
|
def upload_files(codecov_bin: pathlib.Path, files: t.Tuple[CoverageFile, ...], dry_run: bool = False) -> None:
|
||||||
|
for file in files:
|
||||||
|
cmd = [
|
||||||
|
str(codecov_bin),
|
||||||
|
'--name', file.name,
|
||||||
|
'--file', str(file.path),
|
||||||
|
]
|
||||||
|
for flag in file.flags:
|
||||||
|
cmd.extend(['--flags', flag])
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
print(f'DRY-RUN: Would run command: {cmd}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def download_file(url: str, dest: pathlib.Path, flags: int, dry_run: bool = False) -> None:
|
||||||
|
if dry_run:
|
||||||
|
print(f'DRY-RUN: Would download {url} to {dest} and set mode to {flags:o}')
|
||||||
|
return
|
||||||
|
|
||||||
|
with urllib.request.urlopen(url) as resp:
|
||||||
|
with dest.open('w+b') as f:
|
||||||
|
# Read data in chunks rather than all at once
|
||||||
|
shutil.copyfileobj(resp, f, 64 * 1024)
|
||||||
|
|
||||||
|
dest.chmod(flags)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args()
|
||||||
|
url = 'https://ansible-ci-files.s3.amazonaws.com/codecov/linux/codecov'
|
||||||
|
with tempfile.TemporaryDirectory(prefix='codecov-') as tmpdir:
|
||||||
|
codecov_bin = pathlib.Path(tmpdir) / 'codecov'
|
||||||
|
download_file(url, codecov_bin, 0o755, args.dry_run)
|
||||||
|
|
||||||
|
files = process_files(args.path)
|
||||||
|
upload_files(codecov_bin, files, args.dry_run)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Upload code coverage reports to codecov.io.
|
|
||||||
# Multiple coverage files from multiple languages are accepted and aggregated after upload.
|
|
||||||
# Python coverage, as well as PowerShell and Python stubs can all be uploaded.
|
|
||||||
|
|
||||||
set -o pipefail -eu
|
|
||||||
|
|
||||||
output_path="$1"
|
|
||||||
|
|
||||||
curl --silent --show-error https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh > codecov.sh
|
|
||||||
|
|
||||||
for file in "${output_path}"/reports/coverage*.xml; do
|
|
||||||
name="${file}"
|
|
||||||
name="${name##*/}" # remove path
|
|
||||||
name="${name##coverage=}" # remove 'coverage=' prefix if present
|
|
||||||
name="${name%.xml}" # remove '.xml' suffix
|
|
||||||
|
|
||||||
bash codecov.sh \
|
|
||||||
-f "${file}" \
|
|
||||||
-n "${name}" \
|
|
||||||
-X coveragepy \
|
|
||||||
-X gcov \
|
|
||||||
-X fix \
|
|
||||||
-X search \
|
|
||||||
-X xcode \
|
|
||||||
|| echo "Failed to upload code coverage report to codecov.io: ${file}"
|
|
||||||
done
|
|
||||||
@@ -12,4 +12,4 @@ if ! ansible-test --help >/dev/null 2>&1; then
|
|||||||
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ansible-test coverage xml --stub --venv --venv-system-site-packages --color -v
|
ansible-test coverage xml --group-by command --stub --venv --venv-system-site-packages --color -v
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
|
summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
|
||||||
displayName: Publish to Azure Pipelines
|
displayName: Publish to Azure Pipelines
|
||||||
condition: gt(variables.coverageFileCount, 0)
|
condition: gt(variables.coverageFileCount, 0)
|
||||||
- bash: .azure-pipelines/scripts/publish-codecov.sh "$(outputPath)"
|
- bash: .azure-pipelines/scripts/publish-codecov.py "$(outputPath)"
|
||||||
displayName: Publish to codecov.io
|
displayName: Publish to codecov.io
|
||||||
condition: gt(variables.coverageFileCount, 0)
|
condition: gt(variables.coverageFileCount, 0)
|
||||||
continueOnError: true
|
continueOnError: true
|
||||||
|
|||||||
120
CHANGELOG.rst
120
CHANGELOG.rst
@@ -5,6 +5,126 @@ Community Crypto Release Notes
|
|||||||
.. contents:: Topics
|
.. contents:: Topics
|
||||||
|
|
||||||
|
|
||||||
|
v1.9.12
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Regular bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- certificate_complete_chain - allow multiple potential intermediate certificates to have the same subject (https://github.com/ansible-collections/community.crypto/issues/399, https://github.com/ansible-collections/community.crypto/pull/403).
|
||||||
|
- x509_certificate - for the ``ownca`` provider, check whether the CA private key actually belongs to the CA certificate. This fix only covers the ``cryptography`` backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - regenerate certificate when the CA's public key changes for ``provider=ownca``. This fix only covers the ``cryptography`` backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - regenerate certificate when the CA's subject changes for ``provider=ownca`` (https://github.com/ansible-collections/community.crypto/issues/400, https://github.com/ansible-collections/community.crypto/pull/402).
|
||||||
|
- x509_certificate - regenerate certificate when the private key changes for ``provider=selfsigned``. This fix only covers the ``cryptography`` backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
|
||||||
|
Known Issues
|
||||||
|
------------
|
||||||
|
|
||||||
|
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl`` backend, changing the CA's public key does not cause regeneration of the certificate (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl`` backend, it is possible to specify a CA private key which is not related to the CA certificate (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - when using the ``selfsigned`` provider with the ``pyopenssl`` backend, changing the private key does not cause regeneration of the certificate (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
|
||||||
|
v1.9.11
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- openssh_cert - fixed false ``changed`` status for ``host`` certificates when using ``full_idempotence`` (https://github.com/ansible-collections/community.crypto/issues/395, https://github.com/ansible-collections/community.crypto/pull/396).
|
||||||
|
|
||||||
|
v1.9.10
|
||||||
|
=======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Regular bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- luks_devices - set ``LANG`` and similar environment variables to avoid translated output, which can break some of the module's functionality like key management (https://github.com/ansible-collections/community.crypto/pull/388, https://github.com/ansible-collections/community.crypto/issues/385).
|
||||||
|
|
||||||
|
v1.9.9
|
||||||
|
======
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.crypto/pull/353).
|
||||||
|
- certificate_complete_chain - do not append root twice if the chain already ends with a root certificate (https://github.com/ansible-collections/community.crypto/pull/360).
|
||||||
|
- certificate_complete_chain - do not hang when infinite loop is found (https://github.com/ansible-collections/community.crypto/issues/355, https://github.com/ansible-collections/community.crypto/pull/360).
|
||||||
|
|
||||||
|
v1.9.8
|
||||||
|
======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Documentation fix release. No actual code changes.
|
||||||
|
|
||||||
|
v1.9.7
|
||||||
|
======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release with extra forward compatibility for newer versions of cryptography.
|
||||||
|
|
||||||
|
Minor Changes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
- acme_* modules - fix usage of ``fetch_url`` with changes in latest ansible-core ``devel`` branch (https://github.com/ansible-collections/community.crypto/pull/339).
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- acme_certificate - avoid passing multiple certificates to ``cryptography``'s X.509 certificate loader when ``fullchain_dest`` is used (https://github.com/ansible-collections/community.crypto/pull/324).
|
||||||
|
- get_certificate, openssl_csr_info, x509_certificate_info - add fallback code for extension parsing that works with cryptography 36.0.0 and newer. This code re-serializes de-serialized extensions and thus can return slightly different values if the extension in the original CSR resp. certificate was not canonicalized correctly. This code is currently used as a fallback if the existing code stops working, but we will switch it to be the main code in a future release (https://github.com/ansible-collections/community.crypto/pull/331).
|
||||||
|
- luks_device - now also runs a built-in LUKS signature cleaner on ``state=absent`` to make sure that also the secondary LUKS2 header is wiped when older versions of wipefs are used (https://github.com/ansible-collections/community.crypto/issues/326, https://github.com/ansible-collections/community.crypto/pull/327).
|
||||||
|
- openssl_pkcs12 - use new PKCS#12 deserialization infrastructure from cryptography 36.0.0 if available (https://github.com/ansible-collections/community.crypto/pull/302).
|
||||||
|
|
||||||
|
v1.9.6
|
||||||
|
======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Regular bugfix release.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
|
||||||
|
|
||||||
|
v1.9.5
|
||||||
|
======
|
||||||
|
|
||||||
|
Release Summary
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Bugfix release to fully support cryptography 35.0.0.
|
||||||
|
|
||||||
|
Bugfixes
|
||||||
|
--------
|
||||||
|
|
||||||
|
- get_certificate - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||||
|
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||||
|
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
|
||||||
|
- openssl_pkcs12 - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/296).
|
||||||
|
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||||
|
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
|
||||||
|
|
||||||
v1.9.4
|
v1.9.4
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|||||||
@@ -528,6 +528,63 @@ releases:
|
|||||||
changes:
|
changes:
|
||||||
release_summary: Accidental 1.9.1 release. Identical to 1.9.0.
|
release_summary: Accidental 1.9.1 release. Identical to 1.9.0.
|
||||||
release_date: '2021-08-30'
|
release_date: '2021-08-30'
|
||||||
|
1.9.10:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- luks_devices - set ``LANG`` and similar environment variables to avoid translated
|
||||||
|
output, which can break some of the module's functionality like key management
|
||||||
|
(https://github.com/ansible-collections/community.crypto/pull/388, https://github.com/ansible-collections/community.crypto/issues/385).
|
||||||
|
release_summary: Regular bugfix release.
|
||||||
|
fragments:
|
||||||
|
- 1.9.10.yml
|
||||||
|
- 388-luks_device-i18n.yml
|
||||||
|
release_date: '2022-02-01'
|
||||||
|
1.9.11:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- openssh_cert - fixed false ``changed`` status for ``host`` certificates when
|
||||||
|
using ``full_idempotence`` (https://github.com/ansible-collections/community.crypto/issues/395,
|
||||||
|
https://github.com/ansible-collections/community.crypto/pull/396).
|
||||||
|
release_summary: Bugfix release.
|
||||||
|
fragments:
|
||||||
|
- 1.9.11.yml
|
||||||
|
- 396-openssh_cert-host-cert-idempotence-fix.yml
|
||||||
|
release_date: '2022-02-05'
|
||||||
|
1.9.12:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- certificate_complete_chain - allow multiple potential intermediate certificates
|
||||||
|
to have the same subject (https://github.com/ansible-collections/community.crypto/issues/399,
|
||||||
|
https://github.com/ansible-collections/community.crypto/pull/403).
|
||||||
|
- x509_certificate - for the ``ownca`` provider, check whether the CA private
|
||||||
|
key actually belongs to the CA certificate. This fix only covers the ``cryptography``
|
||||||
|
backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - regenerate certificate when the CA's public key changes
|
||||||
|
for ``provider=ownca``. This fix only covers the ``cryptography`` backend,
|
||||||
|
not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - regenerate certificate when the CA's subject changes for
|
||||||
|
``provider=ownca`` (https://github.com/ansible-collections/community.crypto/issues/400,
|
||||||
|
https://github.com/ansible-collections/community.crypto/pull/402).
|
||||||
|
- x509_certificate - regenerate certificate when the private key changes for
|
||||||
|
``provider=selfsigned``. This fix only covers the ``cryptography`` backend,
|
||||||
|
not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
known_issues:
|
||||||
|
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl``
|
||||||
|
backend, changing the CA's public key does not cause regeneration of the certificate
|
||||||
|
(https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl``
|
||||||
|
backend, it is possible to specify a CA private key which is not related to
|
||||||
|
the CA certificate (https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
- x509_certificate - when using the ``selfsigned`` provider with the ``pyopenssl``
|
||||||
|
backend, changing the private key does not cause regeneration of the certificate
|
||||||
|
(https://github.com/ansible-collections/community.crypto/pull/407).
|
||||||
|
release_summary: Regular bugfix release.
|
||||||
|
fragments:
|
||||||
|
- 1.9.12.yml
|
||||||
|
- 402-x509_certificate-ownca-subject.yml
|
||||||
|
- 403-certificate_complete_chain-same-subject.yml
|
||||||
|
- 407-x509_certificate-signature.yml
|
||||||
|
release_date: '2022-02-21'
|
||||||
1.9.2:
|
1.9.2:
|
||||||
changes:
|
changes:
|
||||||
release_summary: Bugfix release to fix the changelog. No other change compared
|
release_summary: Bugfix release to fix the changelog. No other change compared
|
||||||
@@ -562,3 +619,82 @@ releases:
|
|||||||
- 279-acme-openssl.yml
|
- 279-acme-openssl.yml
|
||||||
- 282-acme_challenge_cert_helper-error.yml
|
- 282-acme_challenge_cert_helper-error.yml
|
||||||
release_date: '2021-09-28'
|
release_date: '2021-09-28'
|
||||||
|
1.9.5:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- get_certificate - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
|
||||||
|
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release
|
||||||
|
(https://github.com/ansible-collections/community.crypto/pull/294).
|
||||||
|
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release
|
||||||
|
in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
|
||||||
|
- openssl_pkcs12 - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/296).
|
||||||
|
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release
|
||||||
|
(https://github.com/ansible-collections/community.crypto/pull/294).
|
||||||
|
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release
|
||||||
|
in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
|
||||||
|
release_summary: Bugfix release to fully support cryptography 35.0.0.
|
||||||
|
fragments:
|
||||||
|
- 1.9.5.yml
|
||||||
|
- 294-cryptography-35.0.0.yml
|
||||||
|
- 296-openssl_pkcs12-cryptography-35.yml
|
||||||
|
- 300-pyopenssl-cryptography-35.yml
|
||||||
|
release_date: '2021-10-06'
|
||||||
|
1.9.6:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
|
||||||
|
release_summary: Regular bugfix release.
|
||||||
|
fragments:
|
||||||
|
- 1.9.6.yml
|
||||||
|
- 313-unicode-names.yml
|
||||||
|
release_date: '2021-10-30'
|
||||||
|
1.9.7:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- acme_certificate - avoid passing multiple certificates to ``cryptography``'s
|
||||||
|
X.509 certificate loader when ``fullchain_dest`` is used (https://github.com/ansible-collections/community.crypto/pull/324).
|
||||||
|
- get_certificate, openssl_csr_info, x509_certificate_info - add fallback code
|
||||||
|
for extension parsing that works with cryptography 36.0.0 and newer. This
|
||||||
|
code re-serializes de-serialized extensions and thus can return slightly different
|
||||||
|
values if the extension in the original CSR resp. certificate was not canonicalized
|
||||||
|
correctly. This code is currently used as a fallback if the existing code
|
||||||
|
stops working, but we will switch it to be the main code in a future release
|
||||||
|
(https://github.com/ansible-collections/community.crypto/pull/331).
|
||||||
|
- luks_device - now also runs a built-in LUKS signature cleaner on ``state=absent``
|
||||||
|
to make sure that also the secondary LUKS2 header is wiped when older versions
|
||||||
|
of wipefs are used (https://github.com/ansible-collections/community.crypto/issues/326,
|
||||||
|
https://github.com/ansible-collections/community.crypto/pull/327).
|
||||||
|
- openssl_pkcs12 - use new PKCS#12 deserialization infrastructure from cryptography
|
||||||
|
36.0.0 if available (https://github.com/ansible-collections/community.crypto/pull/302).
|
||||||
|
minor_changes:
|
||||||
|
- acme_* modules - fix usage of ``fetch_url`` with changes in latest ansible-core
|
||||||
|
``devel`` branch (https://github.com/ansible-collections/community.crypto/pull/339).
|
||||||
|
release_summary: Bugfix release with extra forward compatibility for newer versions
|
||||||
|
of cryptography.
|
||||||
|
fragments:
|
||||||
|
- 1.9.7.yml
|
||||||
|
- 302-openssl_pkcs12-cryptography-36.0.0.yml
|
||||||
|
- 324-acme_certificate-fullchain.yml
|
||||||
|
- 327-luks_device-wipe.yml
|
||||||
|
- 331-cryptography-extensions.yml
|
||||||
|
- fetch_url-devel.yml
|
||||||
|
release_date: '2021-11-22'
|
||||||
|
1.9.8:
|
||||||
|
changes:
|
||||||
|
release_summary: Documentation fix release. No actual code changes.
|
||||||
|
fragments:
|
||||||
|
- 1.9.8.yml
|
||||||
|
release_date: '2021-12-13'
|
||||||
|
1.9.9:
|
||||||
|
changes:
|
||||||
|
bugfixes:
|
||||||
|
- Various modules and plugins - use vendored version of ``distutils.version``
|
||||||
|
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.crypto/pull/353).
|
||||||
|
- certificate_complete_chain - do not append root twice if the chain already
|
||||||
|
ends with a root certificate (https://github.com/ansible-collections/community.crypto/pull/360).
|
||||||
|
- certificate_complete_chain - do not hang when infinite loop is found (https://github.com/ansible-collections/community.crypto/issues/355,
|
||||||
|
https://github.com/ansible-collections/community.crypto/pull/360).
|
||||||
|
fragments:
|
||||||
|
- 353-distutils.version.yml
|
||||||
|
- 360-certificate_complete_chain-loop.yml
|
||||||
|
release_date: '2022-01-11'
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ In the following example, we assume that the certificate to sign (including its
|
|||||||
|
|
||||||
- name: Sign certificate with our CA
|
- name: Sign certificate with our CA
|
||||||
community.crypto.x509_certificate_pipe:
|
community.crypto.x509_certificate_pipe:
|
||||||
csr_content: "{{ ca_csr.csr }}"
|
csr_content: "{{ csr.csr }}"
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_path: /path/to/ca-certificate.pem
|
ownca_path: /path/to/ca-certificate.pem
|
||||||
ownca_privatekey_path: /path/to/ca-certificate.key
|
ownca_privatekey_path: /path/to/ca-certificate.key
|
||||||
@@ -128,7 +128,7 @@ Please note that the above procedure is **not idempotent**. The following extend
|
|||||||
- name: Sign certificate with our CA
|
- name: Sign certificate with our CA
|
||||||
community.crypto.x509_certificate_pipe:
|
community.crypto.x509_certificate_pipe:
|
||||||
content: "{{ (certificate.content | b64decode) if certificate_exists.stat.exists else omit }}"
|
content: "{{ (certificate.content | b64decode) if certificate_exists.stat.exists else omit }}"
|
||||||
csr_content: "{{ ca_csr.csr }}"
|
csr_content: "{{ csr.csr }}"
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_path: /path/to/ca-certificate.pem
|
ownca_path: /path/to/ca-certificate.pem
|
||||||
ownca_privatekey_path: /path/to/ca-certificate.key
|
ownca_privatekey_path: /path/to/ca-certificate.key
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace: community
|
namespace: community
|
||||||
name: crypto
|
name: crypto
|
||||||
version: 1.9.4
|
version: 1.9.12
|
||||||
readme: README.md
|
readme: README.md
|
||||||
authors:
|
authors:
|
||||||
- Ansible (github.com/ansible)
|
- Ansible (github.com/ansible)
|
||||||
|
|||||||
@@ -457,8 +457,8 @@ options:
|
|||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||||
- Note that if using relative time this module is NOT idempotent.
|
|
||||||
- If this value is not specified, the certificate will start being valid from now.
|
- If this value is not specified, the certificate will start being valid from now.
|
||||||
|
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the C(ownca) provider.
|
||||||
type: str
|
type: str
|
||||||
default: +0s
|
default: +0s
|
||||||
@@ -470,8 +470,8 @@ options:
|
|||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||||
- Note that if using relative time this module is NOT idempotent.
|
|
||||||
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
||||||
|
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||||
- This is only used by the C(ownca) provider.
|
- This is only used by the C(ownca) provider.
|
||||||
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
||||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||||
@@ -548,8 +548,8 @@ options:
|
|||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||||
- Note that if using relative time this module is NOT idempotent.
|
|
||||||
- If this value is not specified, the certificate will start being valid from now.
|
- If this value is not specified, the certificate will start being valid from now.
|
||||||
|
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the C(selfsigned) provider.
|
||||||
type: str
|
type: str
|
||||||
default: +0s
|
default: +0s
|
||||||
@@ -562,8 +562,8 @@ options:
|
|||||||
- Time will always be interpreted as UTC.
|
- Time will always be interpreted as UTC.
|
||||||
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
|
||||||
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
|
||||||
- Note that if using relative time this module is NOT idempotent.
|
|
||||||
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
- If this value is not specified, the certificate will stop being valid 10 years from now.
|
||||||
|
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
|
||||||
- This is only used by the C(selfsigned) provider.
|
- This is only used by the C(selfsigned) provider.
|
||||||
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
|
||||||
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
Please see U(https://support.apple.com/en-us/HT210176) for more details.
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
|
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
|
||||||
is used for all keys which support it. Please note that not every key can be exported in any format.
|
is used for all keys which support it. Please note that not every key can be exported in any format.
|
||||||
- The value C(auto) selects a fromat based on the key format. The value C(auto_ignore) does the same,
|
- The value C(auto) selects a format based on the key format. The value C(auto_ignore) does the same,
|
||||||
but for existing private key files, it will not force a regenerate when its format is not the automatically
|
but for existing private key files, it will not force a regenerate when its format is not the automatically
|
||||||
selected one for generation.
|
selected one for generation.
|
||||||
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.
|
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.
|
||||||
|
|||||||
343
plugins/module_utils/_version.py
Normal file
343
plugins/module_utils/_version.py
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
# Vendored copy of distutils/version.py from CPython 3.9.5
|
||||||
|
#
|
||||||
|
# Implements multiple version numbering conventions for the
|
||||||
|
# Python Module Distribution Utilities.
|
||||||
|
#
|
||||||
|
# PSF License (see licenses/PSF-license.txt or https://opensource.org/licenses/Python-2.0)
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Provides classes to represent module version numbers (one class for
|
||||||
|
each style of version numbering). There are currently two such classes
|
||||||
|
implemented: StrictVersion and LooseVersion.
|
||||||
|
|
||||||
|
Every version number class implements the following interface:
|
||||||
|
* the 'parse' method takes a string and parses it to some internal
|
||||||
|
representation; if the string is an invalid version number,
|
||||||
|
'parse' raises a ValueError exception
|
||||||
|
* the class constructor takes an optional string argument which,
|
||||||
|
if supplied, is passed to 'parse'
|
||||||
|
* __str__ reconstructs the string that was passed to 'parse' (or
|
||||||
|
an equivalent string -- ie. one that will generate an equivalent
|
||||||
|
version number instance)
|
||||||
|
* __repr__ generates Python code to recreate the version number instance
|
||||||
|
* _cmp compares the current instance with either another instance
|
||||||
|
of the same class or a string (which will be parsed to an instance
|
||||||
|
of the same class, thus must follow the same rules)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import (absolute_import, division, print_function)
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
try:
|
||||||
|
RE_FLAGS = re.VERBOSE | re.ASCII
|
||||||
|
except AttributeError:
|
||||||
|
RE_FLAGS = re.VERBOSE
|
||||||
|
|
||||||
|
|
||||||
|
class Version:
|
||||||
|
"""Abstract base class for version numbering classes. Just provides
|
||||||
|
constructor (__init__) and reproducer (__repr__), because those
|
||||||
|
seem to be the same for all version numbering classes; and route
|
||||||
|
rich comparisons to _cmp.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, vstring=None):
|
||||||
|
if vstring:
|
||||||
|
self.parse(vstring)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s ('%s')" % (self.__class__.__name__, str(self))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c == 0
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c < 0
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c <= 0
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c > 0
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
c = self._cmp(other)
|
||||||
|
if c is NotImplemented:
|
||||||
|
return c
|
||||||
|
return c >= 0
|
||||||
|
|
||||||
|
|
||||||
|
# Interface for version-number classes -- must be implemented
|
||||||
|
# by the following classes (the concrete ones -- Version should
|
||||||
|
# be treated as an abstract class).
|
||||||
|
# __init__ (string) - create and take same action as 'parse'
|
||||||
|
# (string parameter is optional)
|
||||||
|
# parse (string) - convert a string representation to whatever
|
||||||
|
# internal representation is appropriate for
|
||||||
|
# this style of version numbering
|
||||||
|
# __str__ (self) - convert back to a string; should be very similar
|
||||||
|
# (if not identical to) the string supplied to parse
|
||||||
|
# __repr__ (self) - generate Python code to recreate
|
||||||
|
# the instance
|
||||||
|
# _cmp (self, other) - compare two version numbers ('other' may
|
||||||
|
# be an unparsed version string, or another
|
||||||
|
# instance of your version class)
|
||||||
|
|
||||||
|
|
||||||
|
class StrictVersion(Version):
|
||||||
|
"""Version numbering for anal retentives and software idealists.
|
||||||
|
Implements the standard interface for version number classes as
|
||||||
|
described above. A version number consists of two or three
|
||||||
|
dot-separated numeric components, with an optional "pre-release" tag
|
||||||
|
on the end. The pre-release tag consists of the letter 'a' or 'b'
|
||||||
|
followed by a number. If the numeric components of two version
|
||||||
|
numbers are equal, then one with a pre-release tag will always
|
||||||
|
be deemed earlier (lesser) than one without.
|
||||||
|
|
||||||
|
The following are valid version numbers (shown in the order that
|
||||||
|
would be obtained by sorting according to the supplied cmp function):
|
||||||
|
|
||||||
|
0.4 0.4.0 (these two are equivalent)
|
||||||
|
0.4.1
|
||||||
|
0.5a1
|
||||||
|
0.5b3
|
||||||
|
0.5
|
||||||
|
0.9.6
|
||||||
|
1.0
|
||||||
|
1.0.4a3
|
||||||
|
1.0.4b1
|
||||||
|
1.0.4
|
||||||
|
|
||||||
|
The following are examples of invalid version numbers:
|
||||||
|
|
||||||
|
1
|
||||||
|
2.7.2.2
|
||||||
|
1.3.a4
|
||||||
|
1.3pl1
|
||||||
|
1.3c4
|
||||||
|
|
||||||
|
The rationale for this version numbering system will be explained
|
||||||
|
in the distutils documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
|
||||||
|
RE_FLAGS)
|
||||||
|
|
||||||
|
def parse(self, vstring):
|
||||||
|
match = self.version_re.match(vstring)
|
||||||
|
if not match:
|
||||||
|
raise ValueError("invalid version number '%s'" % vstring)
|
||||||
|
|
||||||
|
(major, minor, patch, prerelease, prerelease_num) = \
|
||||||
|
match.group(1, 2, 4, 5, 6)
|
||||||
|
|
||||||
|
if patch:
|
||||||
|
self.version = tuple(map(int, [major, minor, patch]))
|
||||||
|
else:
|
||||||
|
self.version = tuple(map(int, [major, minor])) + (0,)
|
||||||
|
|
||||||
|
if prerelease:
|
||||||
|
self.prerelease = (prerelease[0], int(prerelease_num))
|
||||||
|
else:
|
||||||
|
self.prerelease = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.version[2] == 0:
|
||||||
|
vstring = '.'.join(map(str, self.version[0:2]))
|
||||||
|
else:
|
||||||
|
vstring = '.'.join(map(str, self.version))
|
||||||
|
|
||||||
|
if self.prerelease:
|
||||||
|
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
|
||||||
|
|
||||||
|
return vstring
|
||||||
|
|
||||||
|
def _cmp(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
other = StrictVersion(other)
|
||||||
|
elif not isinstance(other, StrictVersion):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
if self.version != other.version:
|
||||||
|
# numeric versions don't match
|
||||||
|
# prerelease stuff doesn't matter
|
||||||
|
if self.version < other.version:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# have to compare prerelease
|
||||||
|
# case 1: neither has prerelease; they're equal
|
||||||
|
# case 2: self has prerelease, other doesn't; other is greater
|
||||||
|
# case 3: self doesn't have prerelease, other does: self is greater
|
||||||
|
# case 4: both have prerelease: must compare them!
|
||||||
|
|
||||||
|
if (not self.prerelease and not other.prerelease):
|
||||||
|
return 0
|
||||||
|
elif (self.prerelease and not other.prerelease):
|
||||||
|
return -1
|
||||||
|
elif (not self.prerelease and other.prerelease):
|
||||||
|
return 1
|
||||||
|
elif (self.prerelease and other.prerelease):
|
||||||
|
if self.prerelease == other.prerelease:
|
||||||
|
return 0
|
||||||
|
elif self.prerelease < other.prerelease:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
raise AssertionError("never get here")
|
||||||
|
|
||||||
|
# end class StrictVersion
|
||||||
|
|
||||||
|
# The rules according to Greg Stein:
|
||||||
|
# 1) a version number has 1 or more numbers separated by a period or by
|
||||||
|
# sequences of letters. If only periods, then these are compared
|
||||||
|
# left-to-right to determine an ordering.
|
||||||
|
# 2) sequences of letters are part of the tuple for comparison and are
|
||||||
|
# compared lexicographically
|
||||||
|
# 3) recognize the numeric components may have leading zeroes
|
||||||
|
#
|
||||||
|
# The LooseVersion class below implements these rules: a version number
|
||||||
|
# string is split up into a tuple of integer and string components, and
|
||||||
|
# comparison is a simple tuple comparison. This means that version
|
||||||
|
# numbers behave in a predictable and obvious way, but a way that might
|
||||||
|
# not necessarily be how people *want* version numbers to behave. There
|
||||||
|
# wouldn't be a problem if people could stick to purely numeric version
|
||||||
|
# numbers: just split on period and compare the numbers as tuples.
|
||||||
|
# However, people insist on putting letters into their version numbers;
|
||||||
|
# the most common purpose seems to be:
|
||||||
|
# - indicating a "pre-release" version
|
||||||
|
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
|
||||||
|
# - indicating a post-release patch ('p', 'pl', 'patch')
|
||||||
|
# but of course this can't cover all version number schemes, and there's
|
||||||
|
# no way to know what a programmer means without asking him.
|
||||||
|
#
|
||||||
|
# The problem is what to do with letters (and other non-numeric
|
||||||
|
# characters) in a version number. The current implementation does the
|
||||||
|
# obvious and predictable thing: keep them as strings and compare
|
||||||
|
# lexically within a tuple comparison. This has the desired effect if
|
||||||
|
# an appended letter sequence implies something "post-release":
|
||||||
|
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
|
||||||
|
#
|
||||||
|
# However, if letters in a version number imply a pre-release version,
|
||||||
|
# the "obvious" thing isn't correct. Eg. you would expect that
|
||||||
|
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
|
||||||
|
# implemented here, this just isn't so.
|
||||||
|
#
|
||||||
|
# Two possible solutions come to mind. The first is to tie the
|
||||||
|
# comparison algorithm to a particular set of semantic rules, as has
|
||||||
|
# been done in the StrictVersion class above. This works great as long
|
||||||
|
# as everyone can go along with bondage and discipline. Hopefully a
|
||||||
|
# (large) subset of Python module programmers will agree that the
|
||||||
|
# particular flavour of bondage and discipline provided by StrictVersion
|
||||||
|
# provides enough benefit to be worth using, and will submit their
|
||||||
|
# version numbering scheme to its domination. The free-thinking
|
||||||
|
# anarchists in the lot will never give in, though, and something needs
|
||||||
|
# to be done to accommodate them.
|
||||||
|
#
|
||||||
|
# Perhaps a "moderately strict" version class could be implemented that
|
||||||
|
# lets almost anything slide (syntactically), and makes some heuristic
|
||||||
|
# assumptions about non-digits in version number strings. This could
|
||||||
|
# sink into special-case-hell, though; if I was as talented and
|
||||||
|
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
|
||||||
|
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
|
||||||
|
# just as happy dealing with things like "2g6" and "1.13++". I don't
|
||||||
|
# think I'm smart enough to do it right though.
|
||||||
|
#
|
||||||
|
# In any case, I've coded the test suite for this module (see
|
||||||
|
# ../test/test_version.py) specifically to fail on things like comparing
|
||||||
|
# "1.2a2" and "1.2". That's not because the *code* is doing anything
|
||||||
|
# wrong, it's because the simple, obvious design doesn't match my
|
||||||
|
# complicated, hairy expectations for real-world version numbers. It
|
||||||
|
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
|
||||||
|
# the Right Thing" (ie. the code matches the conception). But I'd rather
|
||||||
|
# have a conception that matches common notions about version numbers.
|
||||||
|
|
||||||
|
|
||||||
|
class LooseVersion(Version):
|
||||||
|
"""Version numbering for anarchists and software realists.
|
||||||
|
Implements the standard interface for version number classes as
|
||||||
|
described above. A version number consists of a series of numbers,
|
||||||
|
separated by either periods or strings of letters. When comparing
|
||||||
|
version numbers, the numeric components will be compared
|
||||||
|
numerically, and the alphabetic components lexically. The following
|
||||||
|
are all valid version numbers, in no particular order:
|
||||||
|
|
||||||
|
1.5.1
|
||||||
|
1.5.2b2
|
||||||
|
161
|
||||||
|
3.10a
|
||||||
|
8.02
|
||||||
|
3.4j
|
||||||
|
1996.07.12
|
||||||
|
3.2.pl0
|
||||||
|
3.1.1.6
|
||||||
|
2g6
|
||||||
|
11g
|
||||||
|
0.960923
|
||||||
|
2.2beta29
|
||||||
|
1.13++
|
||||||
|
5.5.kw
|
||||||
|
2.0b1pl0
|
||||||
|
|
||||||
|
In fact, there is no such thing as an invalid version number under
|
||||||
|
this scheme; the rules for comparison are simple and predictable,
|
||||||
|
but may not always give the results you want (for some definition
|
||||||
|
of "want").
|
||||||
|
"""
|
||||||
|
|
||||||
|
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
|
||||||
|
|
||||||
|
def __init__(self, vstring=None):
|
||||||
|
if vstring:
|
||||||
|
self.parse(vstring)
|
||||||
|
|
||||||
|
def parse(self, vstring):
|
||||||
|
# I've given up on thinking I can reconstruct the version string
|
||||||
|
# from the parsed tuple -- so I just store the string here for
|
||||||
|
# use by __str__
|
||||||
|
self.vstring = vstring
|
||||||
|
components = [x for x in self.component_re.split(vstring) if x and x != '.']
|
||||||
|
for i, obj in enumerate(components):
|
||||||
|
try:
|
||||||
|
components[i] = int(obj)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.version = components
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.vstring
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "LooseVersion ('%s')" % str(self)
|
||||||
|
|
||||||
|
def _cmp(self, other):
|
||||||
|
if isinstance(other, str):
|
||||||
|
other = LooseVersion(other)
|
||||||
|
elif not isinstance(other, LooseVersion):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
if self.version == other.version:
|
||||||
|
return 0
|
||||||
|
if self.version < other.version:
|
||||||
|
return -1
|
||||||
|
if self.version > other.version:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
# end class LooseVersion
|
||||||
@@ -14,8 +14,9 @@ import json
|
|||||||
import locale
|
import locale
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.urls import fetch_url
|
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
from ansible.module_utils.urls import fetch_url
|
||||||
|
from ansible.module_utils.six import PY3
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
|
||||||
OpenSSLCLIBackend,
|
OpenSSLCLIBackend,
|
||||||
@@ -228,9 +229,14 @@ class ACMEClient(object):
|
|||||||
resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST')
|
resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST')
|
||||||
_assert_fetch_url_success(self.module, resp, info)
|
_assert_fetch_url_success(self.module, resp, info)
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# In Python 2, reading from a closed response yields a TypeError.
|
||||||
|
# In Python 3, read() simply returns ''
|
||||||
|
if PY3 and resp.closed:
|
||||||
|
raise TypeError
|
||||||
content = resp.read()
|
content = resp.read()
|
||||||
except AttributeError:
|
except (AttributeError, TypeError):
|
||||||
content = info.pop('body', None)
|
content = info.pop('body', None)
|
||||||
|
|
||||||
if content or not parse_json_result:
|
if content or not parse_json_result:
|
||||||
@@ -284,8 +290,12 @@ class ACMEClient(object):
|
|||||||
_assert_fetch_url_success(self.module, resp, info)
|
_assert_fetch_url_success(self.module, resp, info)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# In Python 2, reading from a closed response yields a TypeError.
|
||||||
|
# In Python 3, read() simply returns ''
|
||||||
|
if PY3 and resp.closed:
|
||||||
|
raise TypeError
|
||||||
content = resp.read()
|
content = resp.read()
|
||||||
except AttributeError:
|
except (AttributeError, TypeError):
|
||||||
content = info.pop('body', None)
|
content = info.pop('body', None)
|
||||||
|
|
||||||
# Process result
|
# Process result
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
|
||||||
CryptoBackend,
|
CryptoBackend,
|
||||||
@@ -41,6 +43,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
|||||||
cryptography_name_to_oid,
|
cryptography_name_to_oid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
|
extract_first_pem,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
import cryptography.hazmat.backends
|
import cryptography.hazmat.backends
|
||||||
@@ -53,7 +59,6 @@ try:
|
|||||||
import cryptography.hazmat.primitives.serialization
|
import cryptography.hazmat.primitives.serialization
|
||||||
import cryptography.x509
|
import cryptography.x509
|
||||||
import cryptography.x509.oid
|
import cryptography.x509.oid
|
||||||
from distutils.version import LooseVersion
|
|
||||||
CRYPTOGRAPHY_VERSION = cryptography.__version__
|
CRYPTOGRAPHY_VERSION = cryptography.__version__
|
||||||
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5'))
|
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5'))
|
||||||
if HAS_CURRENT_CRYPTOGRAPHY:
|
if HAS_CURRENT_CRYPTOGRAPHY:
|
||||||
@@ -357,6 +362,9 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
if cert_content is None:
|
if cert_content is None:
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
# Make sure we have at most one PEM. Otherwise cryptography 36.0.0 will barf.
|
||||||
|
cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend)
|
cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
from __future__ import absolute_import, division, print_function
|
from __future__ import absolute_import, division, print_function
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
from ansible.module_utils.six import binary_type
|
|
||||||
from ansible.module_utils.common.text.converters import to_text
|
from ansible.module_utils.common.text.converters import to_text
|
||||||
|
from ansible.module_utils.six import binary_type, PY3
|
||||||
|
|
||||||
|
|
||||||
def format_error_problem(problem, subproblem_prefix=''):
|
def format_error_problem(problem, subproblem_prefix=''):
|
||||||
@@ -52,8 +52,12 @@ class ACMEProtocolException(ModuleFailException):
|
|||||||
# Try to get hold of content, if response is given and content is not provided
|
# Try to get hold of content, if response is given and content is not provided
|
||||||
if content is None and content_json is None and response is not None:
|
if content is None and content_json is None and response is not None:
|
||||||
try:
|
try:
|
||||||
|
# In Python 2, reading from a closed response yields a TypeError.
|
||||||
|
# In Python 3, read() simply returns ''
|
||||||
|
if PY3 and response.closed:
|
||||||
|
raise TypeError
|
||||||
content = response.read()
|
content = response.read()
|
||||||
except AttributeError:
|
except (AttributeError, TypeError):
|
||||||
content = info.pop('body', None)
|
content = info.pop('body', None)
|
||||||
|
|
||||||
# Make sure that content_json is None or a dictionary
|
# Make sure that content_json is None or a dictionary
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
# WARNING: this function no longer works with cryptography 35.0.0 and newer!
|
||||||
|
# It must **ONLY** be used in compatibility code for older
|
||||||
|
# cryptography versions!
|
||||||
|
|
||||||
def obj2txt(openssl_lib, openssl_ffi, obj):
|
def obj2txt(openssl_lib, openssl_ffi, obj):
|
||||||
# Set to 80 on the recommendation of
|
# Set to 80 on the recommendation of
|
||||||
# https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
|
# https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from __future__ import absolute_import, division, print_function
|
|||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import OpenSSL # noqa
|
import OpenSSL # noqa
|
||||||
|
|||||||
@@ -26,15 +26,41 @@ import re
|
|||||||
from ansible.module_utils.common.text.converters import to_text, to_bytes
|
from ansible.module_utils.common.text.converters import to_text, to_bytes
|
||||||
from ._asn1 import serialize_asn1_string_as_der
|
from ._asn1 import serialize_asn1_string_as_der
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
|
from cryptography.exceptions import InvalidSignature
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
import ipaddress
|
import ipaddress
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Error handled in the calling module.
|
# Error handled in the calling module.
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.rsa
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.ec
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.dsa
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.ed25519
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
import cryptography.hazmat.primitives.asymmetric.ed448
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# This is a separate try/except since this is only present in cryptography 2.5 or newer
|
# This is a separate try/except since this is only present in cryptography 2.5 or newer
|
||||||
from cryptography.hazmat.primitives.serialization.pkcs12 import (
|
from cryptography.hazmat.primitives.serialization.pkcs12 import (
|
||||||
@@ -44,9 +70,23 @@ except ImportError:
|
|||||||
# Error handled in the calling module.
|
# Error handled in the calling module.
|
||||||
_load_key_and_certificates = None
|
_load_key_and_certificates = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# This is a separate try/except since this is only present in cryptography 36.0.0 or newer
|
||||||
|
from cryptography.hazmat.primitives.serialization.pkcs12 import (
|
||||||
|
load_pkcs12 as _load_pkcs12,
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
# Error handled in the calling module.
|
||||||
|
_load_pkcs12 = None
|
||||||
|
|
||||||
from .basic import (
|
from .basic import (
|
||||||
|
CRYPTOGRAPHY_HAS_DSA_SIGN,
|
||||||
|
CRYPTOGRAPHY_HAS_EC_SIGN,
|
||||||
CRYPTOGRAPHY_HAS_ED25519,
|
CRYPTOGRAPHY_HAS_ED25519,
|
||||||
|
CRYPTOGRAPHY_HAS_ED25519_SIGN,
|
||||||
CRYPTOGRAPHY_HAS_ED448,
|
CRYPTOGRAPHY_HAS_ED448,
|
||||||
|
CRYPTOGRAPHY_HAS_ED448_SIGN,
|
||||||
|
CRYPTOGRAPHY_HAS_RSA_SIGN,
|
||||||
OpenSSLObjectError,
|
OpenSSLObjectError,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -64,12 +104,23 @@ DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
|
|||||||
|
|
||||||
|
|
||||||
def cryptography_get_extensions_from_cert(cert):
|
def cryptography_get_extensions_from_cert(cert):
|
||||||
|
result = dict()
|
||||||
|
try:
|
||||||
# Since cryptography won't give us the DER value for an extension
|
# Since cryptography won't give us the DER value for an extension
|
||||||
# (that is only stored for unrecognized extensions), we have to re-do
|
# (that is only stored for unrecognized extensions), we have to re-do
|
||||||
# the extension parsing outselves.
|
# the extension parsing outselves.
|
||||||
result = dict()
|
backend = default_backend()
|
||||||
|
try:
|
||||||
|
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||||
|
# which has no _lib attribute. In that case, revert to the old approach.
|
||||||
|
backend._lib
|
||||||
|
except AttributeError:
|
||||||
backend = cert._backend
|
backend = cert._backend
|
||||||
|
|
||||||
x509_obj = cert._x509
|
x509_obj = cert._x509
|
||||||
|
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
||||||
|
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
||||||
|
exts = list(cert.extensions)
|
||||||
|
|
||||||
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
|
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
|
||||||
ext = backend._lib.X509_get_ext(x509_obj, i)
|
ext = backend._lib.X509_get_ext(x509_obj, i)
|
||||||
@@ -83,16 +134,39 @@ def cryptography_get_extensions_from_cert(cert):
|
|||||||
critical=(crit == 1),
|
critical=(crit == 1),
|
||||||
value=base64.b64encode(der),
|
value=base64.b64encode(der),
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||||
|
except AttributeError:
|
||||||
|
oid = exts[i].oid.dotted_string
|
||||||
result[oid] = entry
|
result[oid] = entry
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# In case the above method breaks, we likely have cryptography 36.0.0 or newer.
|
||||||
|
# Use it's public_bytes() feature in that case. We will later switch this around
|
||||||
|
# so that this code will be the default, but for now this will act as a fallback
|
||||||
|
# since it will re-serialize de-serialized data, which can be different (if the
|
||||||
|
# original data was not canonicalized) from what was contained in the certificate.
|
||||||
|
for ext in cert.extensions:
|
||||||
|
result[ext.oid.dotted_string] = dict(
|
||||||
|
critical=ext.critical,
|
||||||
|
value=base64.b64encode(ext.value.public_bytes()),
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def cryptography_get_extensions_from_csr(csr):
|
def cryptography_get_extensions_from_csr(csr):
|
||||||
|
result = dict()
|
||||||
|
try:
|
||||||
# Since cryptography won't give us the DER value for an extension
|
# Since cryptography won't give us the DER value for an extension
|
||||||
# (that is only stored for unrecognized extensions), we have to re-do
|
# (that is only stored for unrecognized extensions), we have to re-do
|
||||||
# the extension parsing outselves.
|
# the extension parsing outselves.
|
||||||
result = dict()
|
backend = default_backend()
|
||||||
|
try:
|
||||||
|
# For certain old versions of cryptography, backend is a MultiBackend object,
|
||||||
|
# which has no _lib attribute. In that case, revert to the old approach.
|
||||||
|
backend._lib
|
||||||
|
except AttributeError:
|
||||||
backend = csr._backend
|
backend = csr._backend
|
||||||
|
|
||||||
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
|
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
|
||||||
@@ -104,6 +178,10 @@ def cryptography_get_extensions_from_csr(csr):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
||||||
|
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
||||||
|
exts = list(csr.extensions)
|
||||||
|
|
||||||
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
|
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
|
||||||
ext = backend._lib.sk_X509_EXTENSION_value(extensions, i)
|
ext = backend._lib.sk_X509_EXTENSION_value(extensions, i)
|
||||||
if ext == backend._ffi.NULL:
|
if ext == backend._ffi.NULL:
|
||||||
@@ -116,8 +194,24 @@ def cryptography_get_extensions_from_csr(csr):
|
|||||||
critical=(crit == 1),
|
critical=(crit == 1),
|
||||||
value=base64.b64encode(der),
|
value=base64.b64encode(der),
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
|
||||||
|
except AttributeError:
|
||||||
|
oid = exts[i].oid.dotted_string
|
||||||
result[oid] = entry
|
result[oid] = entry
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# In case the above method breaks, we likely have cryptography 36.0.0 or newer.
|
||||||
|
# Use it's public_bytes() feature in that case. We will later switch this around
|
||||||
|
# so that this code will be the default, but for now this will act as a fallback
|
||||||
|
# since it will re-serialize de-serialized data, which can be different (if the
|
||||||
|
# original data was not canonicalized) from what was contained in the CSR.
|
||||||
|
for ext in csr.extensions:
|
||||||
|
result[ext.oid.dotted_string] = dict(
|
||||||
|
critical=ext.critical,
|
||||||
|
value=base64.b64encode(ext.value.public_bytes()),
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -280,11 +374,11 @@ def _dn_escape_value(value):
|
|||||||
'''
|
'''
|
||||||
Escape Distinguished Name's attribute value.
|
Escape Distinguished Name's attribute value.
|
||||||
'''
|
'''
|
||||||
value = value.replace('\\', '\\\\')
|
value = value.replace(u'\\', u'\\\\')
|
||||||
for ch in [',', '#', '+', '<', '>', ';', '"', '=', '/']:
|
for ch in [u',', u'#', u'+', u'<', u'>', u';', u'"', u'=', u'/']:
|
||||||
value = value.replace(ch, '\\%s' % ch)
|
value = value.replace(ch, u'\\%s' % ch)
|
||||||
if value.startswith(' '):
|
if value.startswith(u' '):
|
||||||
value = r'\ ' + value[1:]
|
value = u'\\ ' + value[1:]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@@ -294,24 +388,24 @@ def cryptography_decode_name(name):
|
|||||||
Raises an OpenSSLObjectError if the name is not supported.
|
Raises an OpenSSLObjectError if the name is not supported.
|
||||||
'''
|
'''
|
||||||
if isinstance(name, x509.DNSName):
|
if isinstance(name, x509.DNSName):
|
||||||
return 'DNS:{0}'.format(name.value)
|
return u'DNS:{0}'.format(name.value)
|
||||||
if isinstance(name, x509.IPAddress):
|
if isinstance(name, x509.IPAddress):
|
||||||
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
|
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
|
||||||
return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
|
return u'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
|
||||||
return 'IP:{0}'.format(name.value.compressed)
|
return u'IP:{0}'.format(name.value.compressed)
|
||||||
if isinstance(name, x509.RFC822Name):
|
if isinstance(name, x509.RFC822Name):
|
||||||
return 'email:{0}'.format(name.value)
|
return u'email:{0}'.format(name.value)
|
||||||
if isinstance(name, x509.UniformResourceIdentifier):
|
if isinstance(name, x509.UniformResourceIdentifier):
|
||||||
return 'URI:{0}'.format(name.value)
|
return u'URI:{0}'.format(name.value)
|
||||||
if isinstance(name, x509.DirectoryName):
|
if isinstance(name, x509.DirectoryName):
|
||||||
return 'dirName:' + ''.join([
|
return u'dirName:' + u''.join([
|
||||||
'/{0}={1}'.format(cryptography_oid_to_name(attribute.oid, short=True), _dn_escape_value(attribute.value))
|
u'/{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
|
||||||
for attribute in name.value
|
for attribute in name.value
|
||||||
])
|
])
|
||||||
if isinstance(name, x509.RegisteredID):
|
if isinstance(name, x509.RegisteredID):
|
||||||
return 'RID:{0}'.format(name.value.dotted_string)
|
return u'RID:{0}'.format(name.value.dotted_string)
|
||||||
if isinstance(name, x509.OtherName):
|
if isinstance(name, x509.OtherName):
|
||||||
return 'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value))
|
return u'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value))
|
||||||
raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name))
|
raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name))
|
||||||
|
|
||||||
|
|
||||||
@@ -442,17 +536,111 @@ def cryptography_serial_number_of_cert(cert):
|
|||||||
def parse_pkcs12(pkcs12_bytes, passphrase=None):
|
def parse_pkcs12(pkcs12_bytes, passphrase=None):
|
||||||
'''Returns a tuple (private_key, certificate, additional_certificates, friendly_name).
|
'''Returns a tuple (private_key, certificate, additional_certificates, friendly_name).
|
||||||
'''
|
'''
|
||||||
if _load_key_and_certificates is None:
|
if _load_pkcs12 is None and _load_key_and_certificates is None:
|
||||||
raise ValueError('load_key_and_certificates() not present in the current cryptography version')
|
raise ValueError('neither load_pkcs12() nor load_key_and_certificates() present in the current cryptography version')
|
||||||
private_key, certificate, additional_certificates = _load_key_and_certificates(
|
|
||||||
pkcs12_bytes, to_bytes(passphrase) if passphrase is not None else None)
|
if passphrase is not None:
|
||||||
|
passphrase = to_bytes(passphrase)
|
||||||
|
|
||||||
|
# Main code for cryptography 36.0.0 and forward
|
||||||
|
if _load_pkcs12 is not None:
|
||||||
|
return _parse_pkcs12_36_0_0(pkcs12_bytes, passphrase)
|
||||||
|
|
||||||
|
if LooseVersion(cryptography.__version__) >= LooseVersion('35.0'):
|
||||||
|
return _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase)
|
||||||
|
|
||||||
|
return _parse_pkcs12_legacy(pkcs12_bytes, passphrase)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_pkcs12_36_0_0(pkcs12_bytes, passphrase=None):
|
||||||
|
# Requires cryptography 36.0.0 or newer
|
||||||
|
pkcs12 = _load_pkcs12(pkcs12_bytes, passphrase)
|
||||||
|
additional_certificates = [cert.certificate for cert in pkcs12.additional_certs]
|
||||||
|
private_key = pkcs12.key
|
||||||
|
certificate = None
|
||||||
|
friendly_name = None
|
||||||
|
if pkcs12.cert:
|
||||||
|
certificate = pkcs12.cert.certificate
|
||||||
|
friendly_name = pkcs12.cert.friendly_name
|
||||||
|
return private_key, certificate, additional_certificates, friendly_name
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase=None):
|
||||||
|
# Backwards compatibility code for cryptography 35.x
|
||||||
|
private_key, certificate, additional_certificates = _load_key_and_certificates(pkcs12_bytes, passphrase)
|
||||||
|
|
||||||
friendly_name = None
|
friendly_name = None
|
||||||
if certificate:
|
if certificate:
|
||||||
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
||||||
maybe_name = certificate._backend._lib.X509_alias_get0(
|
backend = default_backend()
|
||||||
certificate._x509, certificate._backend._ffi.NULL)
|
|
||||||
if maybe_name != certificate._backend._ffi.NULL:
|
# This code basically does what load_key_and_certificates() does, but without error-checking.
|
||||||
friendly_name = certificate._backend._ffi.string(maybe_name)
|
# Since load_key_and_certificates succeeded, it should not fail.
|
||||||
|
pkcs12 = backend._ffi.gc(
|
||||||
|
backend._lib.d2i_PKCS12_bio(backend._bytes_to_bio(pkcs12_bytes).bio, backend._ffi.NULL),
|
||||||
|
backend._lib.PKCS12_free)
|
||||||
|
certificate_x509_ptr = backend._ffi.new("X509 **")
|
||||||
|
with backend._zeroed_null_terminated_buf(to_bytes(passphrase) if passphrase is not None else None) as passphrase_buffer:
|
||||||
|
backend._lib.PKCS12_parse(
|
||||||
|
pkcs12,
|
||||||
|
passphrase_buffer,
|
||||||
|
backend._ffi.new("EVP_PKEY **"),
|
||||||
|
certificate_x509_ptr,
|
||||||
|
backend._ffi.new("Cryptography_STACK_OF_X509 **"))
|
||||||
|
if certificate_x509_ptr[0] != backend._ffi.NULL:
|
||||||
|
maybe_name = backend._lib.X509_alias_get0(certificate_x509_ptr[0], backend._ffi.NULL)
|
||||||
|
if maybe_name != backend._ffi.NULL:
|
||||||
|
friendly_name = backend._ffi.string(maybe_name)
|
||||||
|
|
||||||
return private_key, certificate, additional_certificates, friendly_name
|
return private_key, certificate, additional_certificates, friendly_name
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_pkcs12_legacy(pkcs12_bytes, passphrase=None):
|
||||||
|
# Backwards compatibility code for cryptography < 35.0.0
|
||||||
|
private_key, certificate, additional_certificates = _load_key_and_certificates(pkcs12_bytes, passphrase)
|
||||||
|
|
||||||
|
friendly_name = None
|
||||||
|
if certificate:
|
||||||
|
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
||||||
|
backend = certificate._backend
|
||||||
|
maybe_name = backend._lib.X509_alias_get0(certificate._x509, backend._ffi.NULL)
|
||||||
|
if maybe_name != backend._ffi.NULL:
|
||||||
|
friendly_name = backend._ffi.string(maybe_name)
|
||||||
|
return private_key, certificate, additional_certificates, friendly_name
|
||||||
|
|
||||||
|
|
||||||
|
def cryptography_verify_signature(signature, data, hash_algorithm, signer_public_key):
|
||||||
|
'''
|
||||||
|
Check whether the given signature of the given data was signed by the given public key object.
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
if CRYPTOGRAPHY_HAS_RSA_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
|
||||||
|
signer_public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm)
|
||||||
|
return True
|
||||||
|
if CRYPTOGRAPHY_HAS_EC_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
|
||||||
|
signer_public_key.verify(signature, data, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hash_algorithm))
|
||||||
|
return True
|
||||||
|
if CRYPTOGRAPHY_HAS_DSA_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey):
|
||||||
|
signer_public_key.verify(signature, data, hash_algorithm)
|
||||||
|
return True
|
||||||
|
if CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
|
||||||
|
signer_public_key.verify(signature, data)
|
||||||
|
return True
|
||||||
|
if CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
|
||||||
|
signer_public_key.verify(signature, data)
|
||||||
|
return True
|
||||||
|
raise OpenSSLObjectError(u'Unsupported public key type {0}'.format(type(signer_public_key)))
|
||||||
|
except InvalidSignature:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def cryptography_verify_certificate_signature(certificate, signer_public_key):
|
||||||
|
'''
|
||||||
|
Check whether the given X509 certificate object was signed by the given public key object.
|
||||||
|
'''
|
||||||
|
return cryptography_verify_signature(
|
||||||
|
certificate.signature,
|
||||||
|
certificate.tbs_certificate_bytes,
|
||||||
|
certificate.signature_hash_algorithm,
|
||||||
|
signer_public_key
|
||||||
|
)
|
||||||
|
|||||||
@@ -11,11 +11,11 @@ __metaclass__ = type
|
|||||||
import abc
|
import abc
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import datetime
|
|||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
load_certificate,
|
load_certificate,
|
||||||
get_fingerprint_of_bytes,
|
get_fingerprint_of_bytes,
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from random import randrange
|
from random import randrange
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
OpenSSLBadPassphraseError,
|
OpenSSLBadPassphraseError,
|
||||||
)
|
)
|
||||||
@@ -27,8 +28,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
|||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
|
cryptography_compare_public_keys,
|
||||||
cryptography_key_needs_digest_for_signing,
|
cryptography_key_needs_digest_for_signing,
|
||||||
cryptography_serial_number_of_cert,
|
cryptography_serial_number_of_cert,
|
||||||
|
cryptography_verify_certificate_signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||||
@@ -106,6 +109,9 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
module.fail_json(msg=str(exc))
|
module.fail_json(msg=str(exc))
|
||||||
|
|
||||||
|
if not cryptography_compare_public_keys(self.ca_cert.public_key(), self.ca_private_key.public_key()):
|
||||||
|
raise CertificateError('The CA private key does not belong to the CA certificate')
|
||||||
|
|
||||||
if cryptography_key_needs_digest_for_signing(self.ca_private_key):
|
if cryptography_key_needs_digest_for_signing(self.ca_private_key):
|
||||||
if self.digest is None:
|
if self.digest is None:
|
||||||
raise CertificateError(
|
raise CertificateError(
|
||||||
@@ -172,6 +178,16 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
|
if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
self._ensure_existing_certificate_loaded()
|
||||||
|
|
||||||
|
# Check whether certificate is signed by CA certificate
|
||||||
|
if not cryptography_verify_certificate_signature(self.existing_certificate, self.ca_cert.public_key()):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check subject
|
||||||
|
if self.ca_cert.subject != self.existing_certificate.issuer:
|
||||||
|
return True
|
||||||
|
|
||||||
# Check AuthorityKeyIdentifier
|
# Check AuthorityKeyIdentifier
|
||||||
if self.create_authority_key_identifier:
|
if self.create_authority_key_identifier:
|
||||||
try:
|
try:
|
||||||
@@ -184,7 +200,6 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
except cryptography.x509.ExtensionNotFound:
|
except cryptography.x509.ExtensionNotFound:
|
||||||
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key())
|
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key())
|
||||||
|
|
||||||
self._ensure_existing_certificate_loaded()
|
|
||||||
try:
|
try:
|
||||||
ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
|
||||||
if ext.value != expected_ext:
|
if ext.value != expected_ext:
|
||||||
@@ -296,6 +311,18 @@ class OwnCACertificateBackendPyOpenSSL(CertificateBackend):
|
|||||||
"""Return bytes for self.cert."""
|
"""Return bytes for self.cert."""
|
||||||
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
|
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
|
||||||
|
|
||||||
|
def needs_regeneration(self):
|
||||||
|
if super(OwnCACertificateBackendPyOpenSSL, self).needs_regeneration():
|
||||||
|
return True
|
||||||
|
|
||||||
|
self._ensure_existing_certificate_loaded()
|
||||||
|
|
||||||
|
# Check subject
|
||||||
|
if self.ca_cert.get_subject() != self.existing_certificate.get_issuer():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def dump(self, include_certificate):
|
def dump(self, include_certificate):
|
||||||
result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate)
|
result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate)
|
||||||
result.update({
|
result.update({
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
|||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
cryptography_key_needs_digest_for_signing,
|
cryptography_key_needs_digest_for_signing,
|
||||||
cryptography_serial_number_of_cert,
|
cryptography_serial_number_of_cert,
|
||||||
|
cryptography_verify_certificate_signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
|
||||||
@@ -134,6 +135,18 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
"""Return bytes for self.cert."""
|
"""Return bytes for self.cert."""
|
||||||
return self.cert.public_bytes(Encoding.PEM)
|
return self.cert.public_bytes(Encoding.PEM)
|
||||||
|
|
||||||
|
def needs_regeneration(self):
|
||||||
|
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration():
|
||||||
|
return True
|
||||||
|
|
||||||
|
self._ensure_existing_certificate_loaded()
|
||||||
|
|
||||||
|
# Check whether certificate is signed by private key
|
||||||
|
if not cryptography_verify_certificate_signature(self.existing_certificate, self.privatekey.public_key()):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def dump(self, include_certificate):
|
def dump(self, include_certificate):
|
||||||
result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)
|
result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
cryptography_oid_to_name,
|
cryptography_oid_to_name,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ import abc
|
|||||||
import binascii
|
import binascii
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
OpenSSLObjectError,
|
OpenSSLObjectError,
|
||||||
OpenSSLBadPassphraseError,
|
OpenSSLBadPassphraseError,
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ import abc
|
|||||||
import binascii
|
import binascii
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
|
||||||
load_certificate_request,
|
load_certificate_request,
|
||||||
get_fingerprint_of_bytes,
|
get_fingerprint_of_bytes,
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ import abc
|
|||||||
import base64
|
import base64
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
CRYPTOGRAPHY_HAS_X25519,
|
CRYPTOGRAPHY_HAS_X25519,
|
||||||
CRYPTOGRAPHY_HAS_X25519_FULL,
|
CRYPTOGRAPHY_HAS_X25519_FULL,
|
||||||
|
|||||||
@@ -12,12 +12,12 @@ __metaclass__ = type
|
|||||||
import abc
|
import abc
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
CRYPTOGRAPHY_HAS_ED25519,
|
CRYPTOGRAPHY_HAS_ED25519,
|
||||||
CRYPTOGRAPHY_HAS_ED448,
|
CRYPTOGRAPHY_HAS_ED448,
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ __metaclass__ = type
|
|||||||
import abc
|
import abc
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
|
||||||
CRYPTOGRAPHY_HAS_X25519,
|
CRYPTOGRAPHY_HAS_X25519,
|
||||||
CRYPTOGRAPHY_HAS_X448,
|
CRYPTOGRAPHY_HAS_X448,
|
||||||
|
|||||||
@@ -72,3 +72,13 @@ def split_pem_list(text, keep_inbetween=False):
|
|||||||
result.append(''.join(current))
|
result.append(''.join(current))
|
||||||
current = [] if keep_inbetween else None
|
current = [] if keep_inbetween else None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def extract_first_pem(text):
|
||||||
|
'''
|
||||||
|
Given one PEM or multiple concatenated PEM objects, return only the first one, or None if there is none.
|
||||||
|
'''
|
||||||
|
all_pems = split_pem_list(text)
|
||||||
|
if not all_pems:
|
||||||
|
return None
|
||||||
|
return all_pems[0]
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
|
||||||
|
|
||||||
|
from ._objects import OID_LOOKUP
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -87,6 +89,7 @@ def pyopenssl_get_extensions_from_cert(cert):
|
|||||||
critical=bool(ext.get_critical()),
|
critical=bool(ext.get_critical()),
|
||||||
value=base64.b64encode(ext.get_data()),
|
value=base64.b64encode(ext.get_data()),
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
oid = obj2txt(
|
oid = obj2txt(
|
||||||
OpenSSL._util.lib,
|
OpenSSL._util.lib,
|
||||||
OpenSSL._util.ffi,
|
OpenSSL._util.ffi,
|
||||||
@@ -99,6 +102,12 @@ def pyopenssl_get_extensions_from_cert(cert):
|
|||||||
# Unfortunately this gives the wrong result in case the linked OpenSSL
|
# 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
|
# doesn't know the OID. That's why we have to get the OID dotted string
|
||||||
# similarly to how cryptography does it.
|
# similarly to how cryptography does it.
|
||||||
|
except AttributeError:
|
||||||
|
# When PyOpenSSL is used with cryptography >= 35.0.0, obj2txt cannot be used.
|
||||||
|
# We try to figure out the OID with our internal lookup table, and if we fail,
|
||||||
|
# we use the short name OpenSSL returns.
|
||||||
|
oid = to_native(ext.get_short_name())
|
||||||
|
oid = OID_LOOKUP.get(oid, oid)
|
||||||
result[oid] = entry
|
result[oid] = entry
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -113,6 +122,7 @@ def pyopenssl_get_extensions_from_csr(csr):
|
|||||||
critical=bool(ext.get_critical()),
|
critical=bool(ext.get_critical()),
|
||||||
value=base64.b64encode(ext.get_data()),
|
value=base64.b64encode(ext.get_data()),
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
oid = obj2txt(
|
oid = obj2txt(
|
||||||
OpenSSL._util.lib,
|
OpenSSL._util.lib,
|
||||||
OpenSSL._util.ffi,
|
OpenSSL._util.ffi,
|
||||||
@@ -125,6 +135,12 @@ def pyopenssl_get_extensions_from_csr(csr):
|
|||||||
# Unfortunately this gives the wrong result in case the linked OpenSSL
|
# 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
|
# doesn't know the OID. That's why we have to get the OID dotted string
|
||||||
# similarly to how cryptography does it.
|
# similarly to how cryptography does it.
|
||||||
|
except AttributeError:
|
||||||
|
# When PyOpenSSL is used with cryptography >= 35.0.0, obj2txt cannot be used.
|
||||||
|
# We try to figure out the OID with our internal lookup table, and if we fail,
|
||||||
|
# we use the short name OpenSSL returns.
|
||||||
|
oid = to_native(ext.get_short_name())
|
||||||
|
oid = OID_LOOKUP.get(oid, oid)
|
||||||
result[oid] = entry
|
result[oid] = entry
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,13 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import os
|
import os
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils import six
|
from ansible.module_utils import six
|
||||||
from ansible.module_utils.basic import missing_required_lib
|
from ansible.module_utils.basic import missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptography import (
|
from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptography import (
|
||||||
HAS_OPENSSH_SUPPORT,
|
HAS_OPENSSH_SUPPORT,
|
||||||
HAS_OPENSSH_PRIVATE_FORMAT,
|
HAS_OPENSSH_PRIVATE_FORMAT,
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ __metaclass__ = type
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from getpass import getuser
|
from getpass import getuser
|
||||||
from socket import gethostname
|
from socket import gethostname
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cryptography import __version__ as CRYPTOGRAPHY_VERSION
|
from cryptography import __version__ as CRYPTOGRAPHY_VERSION
|
||||||
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||||
|
|||||||
17
plugins/module_utils/version.py
Normal file
17
plugins/module_utils/version.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
"""Provide version object to compare version numbers."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
|
||||||
|
# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
|
||||||
|
# remove the _version.py file, and replace the following import by
|
||||||
|
#
|
||||||
|
# from ansible.module_utils.compat.version import LooseVersion
|
||||||
|
|
||||||
|
from ._version import LooseVersion
|
||||||
@@ -145,6 +145,8 @@ import traceback
|
|||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
from ansible.module_utils.common.text.converters import to_bytes, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
|
||||||
@@ -164,7 +166,6 @@ try:
|
|||||||
import cryptography.x509
|
import cryptography.x509
|
||||||
import cryptography.x509.oid
|
import cryptography.x509.oid
|
||||||
import ipaddress
|
import ipaddress
|
||||||
from distutils.version import LooseVersion
|
|
||||||
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3'))
|
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3'))
|
||||||
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||||
except ImportError as dummy:
|
except ImportError as dummy:
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ import traceback
|
|||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
split_pem_list,
|
split_pem_list,
|
||||||
)
|
)
|
||||||
@@ -140,7 +142,6 @@ try:
|
|||||||
import cryptography.hazmat.primitives.asymmetric.utils
|
import cryptography.hazmat.primitives.asymmetric.utils
|
||||||
import cryptography.x509
|
import cryptography.x509
|
||||||
import cryptography.x509.oid
|
import cryptography.x509.oid
|
||||||
from distutils.version import LooseVersion
|
|
||||||
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5'))
|
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5'))
|
||||||
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
_cryptography_backend = cryptography.hazmat.backends.default_backend()
|
||||||
except ImportError as dummy:
|
except ImportError as dummy:
|
||||||
@@ -236,13 +237,17 @@ class CertificateSet(object):
|
|||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
self.module = module
|
self.module = module
|
||||||
self.certificates = set()
|
self.certificates = set()
|
||||||
self.certificate_by_issuer = dict()
|
self.certificates_by_issuer = dict()
|
||||||
|
self.certificate_by_cert = dict()
|
||||||
|
|
||||||
def _load_file(self, path):
|
def _load_file(self, path):
|
||||||
certs = load_PEM_list(self.module, path, fail_on_error=False)
|
certs = load_PEM_list(self.module, path, fail_on_error=False)
|
||||||
for cert in certs:
|
for cert in certs:
|
||||||
self.certificates.add(cert)
|
self.certificates.add(cert)
|
||||||
self.certificate_by_issuer[cert.cert.subject] = cert
|
if cert.cert.subject not in self.certificates_by_issuer:
|
||||||
|
self.certificates_by_issuer[cert.cert.subject] = []
|
||||||
|
self.certificates_by_issuer[cert.cert.subject].append(cert)
|
||||||
|
self.certificate_by_cert[cert.cert] = cert
|
||||||
|
|
||||||
def load(self, path):
|
def load(self, path):
|
||||||
'''
|
'''
|
||||||
@@ -260,8 +265,8 @@ class CertificateSet(object):
|
|||||||
'''
|
'''
|
||||||
Search for the parent (issuer) of a certificate. Return ``None`` if none was found.
|
Search for the parent (issuer) of a certificate. Return ``None`` if none was found.
|
||||||
'''
|
'''
|
||||||
potential_parent = self.certificate_by_issuer.get(cert.cert.issuer)
|
potential_parents = self.certificates_by_issuer.get(cert.cert.issuer, [])
|
||||||
if potential_parent is not None:
|
for potential_parent in potential_parents:
|
||||||
if is_parent(self.module, cert, potential_parent):
|
if is_parent(self.module, cert, potential_parent):
|
||||||
return potential_parent
|
return potential_parent
|
||||||
return None
|
return None
|
||||||
@@ -274,6 +279,16 @@ def format_cert(cert):
|
|||||||
return str(cert.cert)
|
return str(cert.cert)
|
||||||
|
|
||||||
|
|
||||||
|
def check_cycle(module, occured_certificates, next):
|
||||||
|
'''
|
||||||
|
Make sure that next is not in occured_certificates so far, and add it.
|
||||||
|
'''
|
||||||
|
next_cert = next.cert
|
||||||
|
if next_cert in occured_certificates:
|
||||||
|
module.fail_json(msg='Found cycle while building certificate chain')
|
||||||
|
occured_certificates.add(next_cert)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
@@ -312,13 +327,19 @@ def main():
|
|||||||
# Try to complete chain
|
# Try to complete chain
|
||||||
current = chain[-1]
|
current = chain[-1]
|
||||||
completed = []
|
completed = []
|
||||||
|
occured_certificates = set([cert.cert for cert in chain])
|
||||||
|
if current.cert in roots.certificate_by_cert:
|
||||||
|
# Don't try to complete the chain when it's already ending with a root certificate
|
||||||
|
current = None
|
||||||
while current:
|
while current:
|
||||||
root = roots.find_parent(current)
|
root = roots.find_parent(current)
|
||||||
if root:
|
if root:
|
||||||
|
check_cycle(module, occured_certificates, root)
|
||||||
completed.append(root)
|
completed.append(root)
|
||||||
break
|
break
|
||||||
intermediate = intermediates.find_parent(current)
|
intermediate = intermediates.find_parent(current)
|
||||||
if intermediate:
|
if intermediate:
|
||||||
|
check_cycle(module, occured_certificates, intermediate)
|
||||||
completed.append(intermediate)
|
completed.append(intermediate)
|
||||||
current = intermediate
|
current = intermediate
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -519,11 +519,11 @@ import re
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
from ansible.module_utils.common.text.converters import to_native, to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
write_file,
|
write_file,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -167,7 +167,6 @@ import base64
|
|||||||
import datetime
|
import datetime
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from os.path import isfile
|
from os.path import isfile
|
||||||
from socket import create_connection, setdefaulttimeout, socket
|
from socket import create_connection, setdefaulttimeout, socket
|
||||||
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
|
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
|
||||||
@@ -175,6 +174,8 @@ from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_RE
|
|||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes
|
from ansible.module_utils.common.text.converters import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
|
||||||
cryptography_oid_to_name,
|
cryptography_oid_to_name,
|
||||||
cryptography_get_extensions_from_cert,
|
cryptography_get_extensions_from_cert,
|
||||||
|
|||||||
@@ -356,6 +356,34 @@ LUKS_NAME_REGEX = re.compile(r'\s*crypt\s+([^\s]*)\s*')
|
|||||||
LUKS_DEVICE_REGEX = re.compile(r'\s*device:\s+([^\s]*)\s*')
|
LUKS_DEVICE_REGEX = re.compile(r'\s*device:\s+([^\s]*)\s*')
|
||||||
|
|
||||||
|
|
||||||
|
# See https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf
|
||||||
|
LUKS_HEADER = b'LUKS\xba\xbe'
|
||||||
|
LUKS_HEADER_L = 6
|
||||||
|
# See https://gitlab.com/cryptsetup/LUKS2-docs/-/blob/master/luks2_doc_wip.pdf
|
||||||
|
LUKS2_HEADER_OFFSETS = [0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000]
|
||||||
|
LUKS2_HEADER2 = b'SKUL\xba\xbe'
|
||||||
|
|
||||||
|
|
||||||
|
def wipe_luks_headers(device):
|
||||||
|
wipe_offsets = []
|
||||||
|
with open(device, 'rb') as f:
|
||||||
|
# f.seek(0)
|
||||||
|
data = f.read(LUKS_HEADER_L)
|
||||||
|
if data == LUKS_HEADER:
|
||||||
|
wipe_offsets.append(0)
|
||||||
|
for offset in LUKS2_HEADER_OFFSETS:
|
||||||
|
f.seek(offset)
|
||||||
|
data = f.read(LUKS_HEADER_L)
|
||||||
|
if data == LUKS2_HEADER2:
|
||||||
|
wipe_offsets.append(offset)
|
||||||
|
|
||||||
|
if wipe_offsets:
|
||||||
|
with open(device, 'wb') as f:
|
||||||
|
for offset in wipe_offsets:
|
||||||
|
f.seek(offset)
|
||||||
|
f.write(b'\x00\x00\x00\x00\x00\x00')
|
||||||
|
|
||||||
|
|
||||||
class Handler(object):
|
class Handler(object):
|
||||||
|
|
||||||
def __init__(self, module):
|
def __init__(self, module):
|
||||||
@@ -515,9 +543,17 @@ class CryptHandler(Handler):
|
|||||||
self.run_luks_close(name)
|
self.run_luks_close(name)
|
||||||
result = self._run_command([wipefs_bin, '--all', device])
|
result = self._run_command([wipefs_bin, '--all', device])
|
||||||
if result[RETURN_CODE] != 0:
|
if result[RETURN_CODE] != 0:
|
||||||
raise ValueError('Error while wiping luks container %s: %s'
|
raise ValueError('Error while wiping LUKS container signatures for %s: %s'
|
||||||
% (device, result[STDERR]))
|
% (device, result[STDERR]))
|
||||||
|
|
||||||
|
# For LUKS2, sometimes both `cryptsetup erase` and `wipefs` do **not**
|
||||||
|
# erase all LUKS signatures (they seem to miss the second header). That's
|
||||||
|
# why we do it ourselves here.
|
||||||
|
try:
|
||||||
|
wipe_luks_headers(device)
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValueError('Error while wiping LUKS container signatures for %s: %s' % (device, exc))
|
||||||
|
|
||||||
def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile,
|
def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile,
|
||||||
new_passphrase, pbkdf):
|
new_passphrase, pbkdf):
|
||||||
''' Add new key from a keyfile or passphrase to given 'device';
|
''' Add new key from a keyfile or passphrase to given 'device';
|
||||||
@@ -785,6 +821,7 @@ def run_module():
|
|||||||
module = AnsibleModule(argument_spec=module_args,
|
module = AnsibleModule(argument_spec=module_args,
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
mutually_exclusive=mutually_exclusive)
|
mutually_exclusive=mutually_exclusive)
|
||||||
|
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
|
||||||
|
|
||||||
if module.params['device'] is not None:
|
if module.params['device'] is not None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -258,11 +258,12 @@ info:
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.openssh.backends.common import (
|
from ansible_collections.community.crypto.plugins.module_utils.openssh.backends.common import (
|
||||||
KeygenCommand,
|
KeygenCommand,
|
||||||
OpensshModule,
|
OpensshModule,
|
||||||
@@ -378,7 +379,7 @@ class Certificate(OpensshModule):
|
|||||||
|
|
||||||
def _is_fully_valid(self):
|
def _is_fully_valid(self):
|
||||||
return self._is_partially_valid() and all([
|
return self._is_partially_valid() and all([
|
||||||
self._compare_options(),
|
self._compare_options() if self.original_data.type == 'user' else True,
|
||||||
self.original_data.key_id == self.identifier,
|
self.original_data.key_id == self.identifier,
|
||||||
self.original_data.public_key == self._get_key_fingerprint(self.public_key),
|
self.original_data.public_key == self._get_key_fingerprint(self.public_key),
|
||||||
self.original_data.signing_key == self._get_key_fingerprint(self.signing_key),
|
self.original_data.signing_key == self._get_key_fingerprint(self.signing_key),
|
||||||
|
|||||||
@@ -128,11 +128,11 @@ import re
|
|||||||
import tempfile
|
import tempfile
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
load_file_if_exists,
|
load_file_if_exists,
|
||||||
write_file,
|
write_file,
|
||||||
|
|||||||
@@ -239,11 +239,11 @@ import os
|
|||||||
import stat
|
import stat
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
load_file_if_exists,
|
load_file_if_exists,
|
||||||
write_file,
|
write_file,
|
||||||
|
|||||||
@@ -182,11 +182,11 @@ publickey:
|
|||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native
|
from ansible.module_utils.common.text.converters import to_native
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
load_file_if_exists,
|
load_file_if_exists,
|
||||||
write_file,
|
write_file,
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ EXAMPLES = r'''
|
|||||||
path: /etc/ssl/private/ansible.com.pem
|
path: /etc/ssl/private/ansible.com.pem
|
||||||
|
|
||||||
- name: Create public key from private key
|
- name: Create public key from private key
|
||||||
community.crypto.openssl_privatekey:
|
community.crypto.openssl_publickey:
|
||||||
privatekey_path: /etc/ssl/private/ansible.com.pem
|
privatekey_path: /etc/ssl/private/ansible.com.pem
|
||||||
path: /etc/ssl/ansible.com.pub
|
path: /etc/ssl/ansible.com.pub
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,10 @@ signature:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
from distutils.version import LooseVersion
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
MINIMAL_PYOPENSSL_VERSION = '0.11'
|
MINIMAL_PYOPENSSL_VERSION = '0.11'
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,10 @@ valid:
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
from distutils.version import LooseVersion
|
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
MINIMAL_PYOPENSSL_VERSION = '0.11'
|
MINIMAL_PYOPENSSL_VERSION = '0.11'
|
||||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
|
||||||
|
|
||||||
|
|||||||
@@ -367,11 +367,11 @@ import base64
|
|||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from distutils.version import LooseVersion
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
from ansible_collections.community.crypto.plugins.module_utils.io import (
|
||||||
write_file,
|
write_file,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
|
- prepare_jinja2_compat
|
||||||
- setup_openssl
|
- setup_openssl
|
||||||
- setup_remote_tmp_dir
|
- setup_remote_tmp_dir
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
- name: Generate CSR for {{ certificate.name }}
|
||||||
|
openssl_csr:
|
||||||
|
path: '{{ remote_tmp_dir }}/{{ certificate.name }}.csr'
|
||||||
|
privatekey_path: '{{ remote_tmp_dir }}/{{ certificate.name }}.key'
|
||||||
|
subject: '{{ certificate.subject }}'
|
||||||
|
useCommonNameForSAN: false
|
||||||
|
|
||||||
|
- name: Generate certificate for {{ certificate.name }}
|
||||||
|
x509_certificate:
|
||||||
|
path: '{{ remote_tmp_dir }}/{{ certificate.name }}.pem'
|
||||||
|
csr_path: '{{ remote_tmp_dir }}/{{ certificate.name }}.csr'
|
||||||
|
privatekey_path: '{{ remote_tmp_dir }}/{{ certificate.name }}.key'
|
||||||
|
provider: '{{ "selfsigned" if certificate.parent is not defined else "ownca" }}'
|
||||||
|
ownca_path: '{{ (remote_tmp_dir ~ "/" ~ certificate.parent ~ ".pem") if certificate.parent is defined else omit }}'
|
||||||
|
ownca_privatekey_path: '{{ (remote_tmp_dir ~ "/" ~ certificate.parent ~ ".key") if certificate.parent is defined else omit }}'
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Create private keys
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ remote_tmp_dir }}/{{ item.name }}.key'
|
||||||
|
size: '{{ default_rsa_key_size_certifiates }}'
|
||||||
|
loop: '{{ certificates }}'
|
||||||
|
|
||||||
|
- name: Generate certificates
|
||||||
|
include_tasks: create-single-certificate.yml
|
||||||
|
loop: '{{ certificates }}'
|
||||||
|
loop_control:
|
||||||
|
loop_var: certificate
|
||||||
|
|
||||||
|
- name: Read certificates
|
||||||
|
slurp:
|
||||||
|
src: '{{ remote_tmp_dir }}/{{ item.name }}.pem'
|
||||||
|
loop: '{{ certificates }}'
|
||||||
|
register: certificates_read
|
||||||
|
|
||||||
|
- name: Store read certificates
|
||||||
|
set_fact:
|
||||||
|
read_certificates: >-
|
||||||
|
{{ certificates_read.results | map(attribute='content') | map('b64decode')
|
||||||
|
| zip(certificates | map(attribute='name'))
|
||||||
|
| list
|
||||||
|
| items2dict(key_name=1, value_name=0) }}
|
||||||
|
|
||||||
|
vars:
|
||||||
|
certificates:
|
||||||
|
- name: a-root
|
||||||
|
subject:
|
||||||
|
commonName: root common name
|
||||||
|
- name: b-intermediate
|
||||||
|
subject:
|
||||||
|
commonName: intermediate common name
|
||||||
|
parent: a-root
|
||||||
|
- name: c-intermediate
|
||||||
|
subject:
|
||||||
|
commonName: intermediate common name
|
||||||
|
parent: a-root
|
||||||
|
- name: d-leaf
|
||||||
|
subject:
|
||||||
|
commonName: leaf certificate
|
||||||
|
parent: b-intermediate
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
- name: Case A => works
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: "{{ read_certificates['d-leaf'] }}"
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/b-intermediate.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/a-root.pem'
|
||||||
|
|
||||||
|
- name: Case B => doesn't work, but this is expected
|
||||||
|
failed_when: no
|
||||||
|
register: caseb
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: "{{ read_certificates['d-leaf'] }}"
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/c-intermediate.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/a-root.pem'
|
||||||
|
|
||||||
|
- name: Assert that case B failed
|
||||||
|
assert:
|
||||||
|
that: "'Cannot complete chain' in caseb.msg"
|
||||||
|
|
||||||
|
- name: Case C => works
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: "{{ read_certificates['d-leaf'] }}"
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/c-intermediate.pem'
|
||||||
|
- '{{ remote_tmp_dir }}/b-intermediate.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/a-root.pem'
|
||||||
|
|
||||||
|
- name: Case D => works as well after PR 403
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: "{{ read_certificates['d-leaf'] }}"
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/b-intermediate.pem'
|
||||||
|
- '{{ remote_tmp_dir }}/c-intermediate.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/a-root.pem'
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
####################################################################
|
||||||
|
# WARNING: These are designed specifically for Ansible tests #
|
||||||
|
# and should not be used as examples of how to write Ansible roles #
|
||||||
|
####################################################################
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Find root for cert 1 using directory
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ fullchain | trim }}'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots/'
|
||||||
|
register: cert1_root
|
||||||
|
- name: Verify root for cert 1
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert1_root.complete_chain | join('') == (fullchain ~ root)
|
||||||
|
- cert1_root.root == root
|
||||||
|
vars:
|
||||||
|
fullchain: "{{ lookup('file', 'cert1-fullchain.pem', rstrip=False) }}"
|
||||||
|
root: "{{ lookup('file', 'cert1-root.pem', rstrip=False) }}"
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Find rootchain for cert 1 using intermediate and root PEM
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ cert }}'
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots.pem'
|
||||||
|
register: cert1_rootchain
|
||||||
|
- name: Verify rootchain for cert 1
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert1_rootchain.complete_chain | join('') == (cert ~ chain ~ root)
|
||||||
|
- cert1_rootchain.chain[:-1] | join('') == chain
|
||||||
|
- cert1_rootchain.root == root
|
||||||
|
vars:
|
||||||
|
cert: "{{ lookup('file', 'cert1.pem', rstrip=False) }}"
|
||||||
|
chain: "{{ lookup('file', 'cert1-chain.pem', rstrip=False) }}"
|
||||||
|
root: "{{ lookup('file', 'cert1-root.pem', rstrip=False) }}"
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Find root for cert 2 using directory
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: "{{ fullchain | trim }}"
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots/'
|
||||||
|
register: cert2_root
|
||||||
|
- name: Verify root for cert 2
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert2_root.complete_chain | join('') == (fullchain ~ root)
|
||||||
|
- cert2_root.root == root
|
||||||
|
vars:
|
||||||
|
fullchain: "{{ lookup('file', 'cert2-fullchain.pem', rstrip=False) }}"
|
||||||
|
root: "{{ lookup('file', 'cert2-root.pem', rstrip=False) }}"
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Find rootchain for cert 2 using intermediate and root PEM
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ cert }}'
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/cert2-chain.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots.pem'
|
||||||
|
register: cert2_rootchain
|
||||||
|
- name: Verify rootchain for cert 2
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert2_rootchain.complete_chain | join('') == (cert ~ chain ~ root)
|
||||||
|
- cert2_rootchain.chain[:-1] | join('') == chain
|
||||||
|
- cert2_rootchain.root == root
|
||||||
|
vars:
|
||||||
|
cert: "{{ lookup('file', 'cert2.pem', rstrip=False) }}"
|
||||||
|
chain: "{{ lookup('file', 'cert2-chain.pem', rstrip=False) }}"
|
||||||
|
root: "{{ lookup('file', 'cert2-root.pem', rstrip=False) }}"
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Find alternate rootchain for cert 2 using intermediate and root PEM
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ cert }}'
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/cert2-altchain.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots.pem'
|
||||||
|
register: cert2_rootchain_alt
|
||||||
|
- name: Verify rootchain for cert 2
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert2_rootchain_alt.complete_chain | join('') == (cert ~ chain ~ root)
|
||||||
|
- cert2_rootchain_alt.chain[:-1] | join('') == chain
|
||||||
|
- cert2_rootchain_alt.root == root
|
||||||
|
vars:
|
||||||
|
cert: "{{ lookup('file', 'cert2.pem', rstrip=False) }}"
|
||||||
|
chain: "{{ lookup('file', 'cert2-altchain.pem', rstrip=False) }}"
|
||||||
|
root: "{{ lookup('file', 'cert2-altroot.pem', rstrip=False) }}"
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Find alternate rootchain for cert 2 when complete chain is already presented to the module
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ cert ~ chain ~ root }}'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots.pem'
|
||||||
|
register: cert2_complete_chain
|
||||||
|
- name: Verify rootchain for cert 2
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert2_complete_chain.complete_chain | join('') == (cert ~ chain ~ root)
|
||||||
|
- cert2_complete_chain.chain == []
|
||||||
|
- cert2_complete_chain.root == root
|
||||||
|
vars:
|
||||||
|
cert: "{{ lookup('file', 'cert2.pem', rstrip=False) }}"
|
||||||
|
chain: "{{ lookup('file', 'cert2-altchain.pem', rstrip=False) }}"
|
||||||
|
root: "{{ lookup('file', 'cert2-altroot.pem', rstrip=False) }}"
|
||||||
|
|
||||||
|
- name: Check failure when no intermediate certificate can be found
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ lookup("file", "cert2.pem", rstrip=True) }}'
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots.pem'
|
||||||
|
register: cert2_no_intermediate
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify failure
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert2_no_intermediate is failed
|
||||||
|
- "cert2_no_intermediate.msg.startswith('Cannot complete chain. Stuck at certificate ')"
|
||||||
|
|
||||||
|
- name: Check failure when infinite loop is found
|
||||||
|
certificate_complete_chain:
|
||||||
|
input_chain: '{{ lookup("file", "cert2-fullchain.pem", rstrip=True) }}'
|
||||||
|
intermediate_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/roots.pem'
|
||||||
|
root_certificates:
|
||||||
|
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
|
||||||
|
register: cert2_infinite_loop
|
||||||
|
ignore_errors: true
|
||||||
|
- name: Verify failure
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- cert2_infinite_loop is failed
|
||||||
|
- "cert2_infinite_loop.msg == 'Found cycle while building certificate chain'"
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
|
|
||||||
- name: Make sure testhost directory exists
|
- name: Make sure testhost directory exists
|
||||||
file:
|
file:
|
||||||
path: '{{ remote_tmp_dir }}/files/'
|
path: '{{ remote_tmp_dir }}/files/'
|
||||||
@@ -13,68 +14,14 @@
|
|||||||
copy:
|
copy:
|
||||||
src: '{{ role_path }}/files/'
|
src: '{{ role_path }}/files/'
|
||||||
dest: '{{ remote_tmp_dir }}/files/'
|
dest: '{{ remote_tmp_dir }}/files/'
|
||||||
- name: Find root for cert 1
|
|
||||||
certificate_complete_chain:
|
- name: Run tests with copied certificates
|
||||||
input_chain: '{{ lookup("file", "cert1-fullchain.pem", rstrip=False) }}'
|
import_tasks: existing.yml
|
||||||
root_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/roots/'
|
- name: Create more certificates
|
||||||
register: cert1_root
|
import_tasks: create.yml
|
||||||
- name: Verify root for cert 1
|
|
||||||
assert:
|
- name: Run tests with created certificates
|
||||||
that:
|
import_tasks: created.yml
|
||||||
- cert1_root.complete_chain | join('') == (lookup('file', 'cert1.pem', rstrip=False) ~ lookup('file', 'cert1-chain.pem', rstrip=False) ~ lookup('file', 'cert1-root.pem', rstrip=False))
|
|
||||||
- cert1_root.root == lookup('file', 'cert1-root.pem', rstrip=False)
|
|
||||||
- name: Find rootchain for cert 1
|
|
||||||
certificate_complete_chain:
|
|
||||||
input_chain: '{{ lookup("file", "cert1.pem", rstrip=False) }}'
|
|
||||||
intermediate_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
|
|
||||||
root_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/roots.pem'
|
|
||||||
register: cert1_rootchain
|
|
||||||
- name: Verify rootchain for cert 1
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cert1_rootchain.complete_chain | join('') == (lookup('file', 'cert1.pem', rstrip=False) ~ lookup('file', 'cert1-chain.pem', rstrip=False) ~ lookup('file', 'cert1-root.pem', rstrip=False))
|
|
||||||
- cert1_rootchain.chain[:-1] | join('') == lookup('file', 'cert1-chain.pem', rstrip=False)
|
|
||||||
- cert1_rootchain.root == lookup('file', 'cert1-root.pem', rstrip=False)
|
|
||||||
- name: Find root for cert 2
|
|
||||||
certificate_complete_chain:
|
|
||||||
input_chain: '{{ lookup("file", "cert2-fullchain.pem", rstrip=False) }}'
|
|
||||||
root_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/roots/'
|
|
||||||
register: cert2_root
|
|
||||||
- name: Verify root for cert 2
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cert2_root.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-chain.pem', rstrip=False) ~ lookup('file', 'cert2-root.pem', rstrip=False))
|
|
||||||
- cert2_root.root == lookup('file', 'cert2-root.pem', rstrip=False)
|
|
||||||
- name: Find rootchain for cert 2
|
|
||||||
certificate_complete_chain:
|
|
||||||
input_chain: '{{ lookup("file", "cert2.pem", rstrip=False) }}'
|
|
||||||
intermediate_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/cert2-chain.pem'
|
|
||||||
root_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/roots.pem'
|
|
||||||
register: cert2_rootchain
|
|
||||||
- name: Verify rootchain for cert 2
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cert2_rootchain.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-chain.pem', rstrip=False) ~ lookup('file', 'cert2-root.pem', rstrip=False))
|
|
||||||
- cert2_rootchain.chain[:-1] | join('') == lookup('file', 'cert2-chain.pem', rstrip=False)
|
|
||||||
- cert2_rootchain.root == lookup('file', 'cert2-root.pem', rstrip=False)
|
|
||||||
- name: Find alternate rootchain for cert 2
|
|
||||||
certificate_complete_chain:
|
|
||||||
input_chain: '{{ lookup("file", "cert2.pem", rstrip=True) }}'
|
|
||||||
intermediate_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/cert2-altchain.pem'
|
|
||||||
root_certificates:
|
|
||||||
- '{{ remote_tmp_dir }}/files/roots.pem'
|
|
||||||
register: cert2_rootchain_alt
|
|
||||||
- name: Verify rootchain for cert 2
|
|
||||||
assert:
|
|
||||||
that:
|
|
||||||
- cert2_rootchain_alt.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-altchain.pem', rstrip=False) ~ lookup('file', 'cert2-altroot.pem', rstrip=False))
|
|
||||||
- cert2_rootchain_alt.chain[:-1] | join('') == lookup('file', 'cert2-altchain.pem', rstrip=False)
|
|
||||||
- cert2_rootchain_alt.root == lookup('file', 'cert2-altroot.pem', rstrip=False)
|
|
||||||
when: cryptography_version.stdout is version('1.5', '>=')
|
when: cryptography_version.stdout is version('1.5', '>=')
|
||||||
|
|||||||
@@ -86,6 +86,27 @@
|
|||||||
regenerate: full_idempotence
|
regenerate: full_idempotence
|
||||||
register: default_options
|
register: default_options
|
||||||
|
|
||||||
|
- name: Generate host cert full_idempotence
|
||||||
|
openssh_cert:
|
||||||
|
type: host
|
||||||
|
path: "{{ certificate_path }}"
|
||||||
|
public_key: "{{ public_key }}"
|
||||||
|
signing_key: "{{ signing_key }}"
|
||||||
|
valid_from: always
|
||||||
|
valid_to: forever
|
||||||
|
regenerate: full_idempotence
|
||||||
|
|
||||||
|
- name: Generate host cert full_idempotence again
|
||||||
|
openssh_cert:
|
||||||
|
type: host
|
||||||
|
path: "{{ certificate_path }}"
|
||||||
|
public_key: "{{ public_key }}"
|
||||||
|
signing_key: "{{ signing_key }}"
|
||||||
|
valid_from: always
|
||||||
|
valid_to: forever
|
||||||
|
regenerate: full_idempotence
|
||||||
|
register: host_cert_full_idempotence
|
||||||
|
|
||||||
- name: Assert options results
|
- name: Assert options results
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
@@ -95,6 +116,7 @@
|
|||||||
- explicit_extension_after is not changed
|
- explicit_extension_after is not changed
|
||||||
- explicit_extension_and_directive is changed
|
- explicit_extension_and_directive is changed
|
||||||
- default_options is not changed
|
- default_options is not changed
|
||||||
|
- host_cert_full_idempotence is not changed
|
||||||
|
|
||||||
- name: Remove certificate
|
- name: Remove certificate
|
||||||
openssh_cert:
|
openssh_cert:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Check whether subject behaves as expected"
|
- name: "({{ select_crypto_backend }}) Check whether subject and extensions behaves as expected"
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- result.subject.organizationalUnitName == 'ACME Department'
|
- result.subject.organizationalUnitName == 'ACME Department'
|
||||||
@@ -16,6 +16,21 @@
|
|||||||
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
|
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
|
||||||
- result.public_key_type == 'RSA'
|
- result.public_key_type == 'RSA'
|
||||||
- result.public_key_data.size == default_rsa_key_size
|
- result.public_key_data.size == default_rsa_key_size
|
||||||
|
# TLS Feature
|
||||||
|
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].critical == false
|
||||||
|
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].value == 'MAMCAQU='
|
||||||
|
# Key Usage
|
||||||
|
- result.extensions_by_oid['2.5.29.15'].critical == true
|
||||||
|
- result.extensions_by_oid['2.5.29.15'].value in ['AwMA/4A=', 'AwMH/4A=']
|
||||||
|
# Subject Alternative Names
|
||||||
|
- result.extensions_by_oid['2.5.29.17'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.17'].value == 'MGCCD3d3dy5hbnNpYmxlLmNvbYcEAQIDBIcQAAAAAAAAAAAAAAAAAAAAAYEQdGVzdEBleGFtcGxlLm9yZ4YjaHR0cHM6Ly9leGFtcGxlLm9yZy90ZXN0L2luZGV4Lmh0bWw='
|
||||||
|
# Basic Constraints
|
||||||
|
- result.extensions_by_oid['2.5.29.19'].critical == true
|
||||||
|
- result.extensions_by_oid['2.5.29.19'].value == 'MAYBAf8CARc='
|
||||||
|
# Extended Key Usage
|
||||||
|
- result.extensions_by_oid['2.5.29.37'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.37'].value == 'MHQGCCsGAQUFBwMBBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUFBwMJBgRVHSUABggrBgEFBQcBAwYIKwYBBQUHAwoGCCsGAQUFBwMHBggrBgEFBQcBAg=='
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier"
|
- name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier"
|
||||||
assert:
|
assert:
|
||||||
@@ -24,6 +39,10 @@
|
|||||||
- result.authority_key_identifier == "44:55:66:77"
|
- result.authority_key_identifier == "44:55:66:77"
|
||||||
- result.authority_cert_issuer == expected_authority_cert_issuer
|
- result.authority_cert_issuer == expected_authority_cert_issuer
|
||||||
- result.authority_cert_serial_number == 12345
|
- result.authority_cert_serial_number == 12345
|
||||||
|
# Subject Key Identifier
|
||||||
|
- result.extensions_by_oid['2.5.29.14'].critical == false
|
||||||
|
# Authority Key Identifier
|
||||||
|
- result.extensions_by_oid['2.5.29.35'].critical == false
|
||||||
vars:
|
vars:
|
||||||
expected_authority_cert_issuer:
|
expected_authority_cert_issuer:
|
||||||
- "DNS:ca.example.org"
|
- "DNS:ca.example.org"
|
||||||
|
|||||||
@@ -303,11 +303,18 @@
|
|||||||
size: '{{ default_rsa_key_size }}'
|
size: '{{ default_rsa_key_size }}'
|
||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
register: privatekey_mode_1
|
register: privatekey_mode_1
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
|
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
|
||||||
stat:
|
stat:
|
||||||
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
||||||
register: privatekey_mode_1_stat
|
register: privatekey_mode_1_stat
|
||||||
|
|
||||||
|
- name: "({{ select_crypto_backend }}) Collect file information"
|
||||||
|
community.internal_test_tools.files_collect:
|
||||||
|
files:
|
||||||
|
- path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
||||||
|
register: privatekey_mode_1_fileinfo
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, idempotency)"
|
- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, idempotency)"
|
||||||
openssl_privatekey:
|
openssl_privatekey:
|
||||||
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
||||||
@@ -316,13 +323,6 @@
|
|||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
register: privatekey_mode_2
|
register: privatekey_mode_2
|
||||||
|
|
||||||
- name: Make sure that mtime actually changes.
|
|
||||||
# The "privatekey_mode_1_stat.stat.mtime != privatekey_mode_3_stat.stat.mtime" test should be
|
|
||||||
# changed to compare content instead of mtime. On macOS 10.15, mtime resolution is one second,
|
|
||||||
# and the machine (VM) is fast enough that both modifications can happen in the same second.
|
|
||||||
pause:
|
|
||||||
seconds: 1
|
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, force)"
|
- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, force)"
|
||||||
openssl_privatekey:
|
openssl_privatekey:
|
||||||
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
||||||
@@ -331,11 +331,17 @@
|
|||||||
size: '{{ default_rsa_key_size }}'
|
size: '{{ default_rsa_key_size }}'
|
||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
register: privatekey_mode_3
|
register: privatekey_mode_3
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
|
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
|
||||||
stat:
|
stat:
|
||||||
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
|
||||||
register: privatekey_mode_3_stat
|
register: privatekey_mode_3_stat
|
||||||
|
|
||||||
|
- name: "({{ select_crypto_backend }}) Make sure that file changed"
|
||||||
|
community.internal_test_tools.files_diff:
|
||||||
|
state: '{{ privatekey_mode_1_fileinfo }}'
|
||||||
|
register: privatekey_mode_3_file_change
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
- name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format"
|
- name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format"
|
||||||
openssl_privatekey:
|
openssl_privatekey:
|
||||||
|
|||||||
@@ -186,7 +186,7 @@
|
|||||||
- privatekey_mode_2 is not changed
|
- privatekey_mode_2 is not changed
|
||||||
- privatekey_mode_3 is changed
|
- privatekey_mode_3 is changed
|
||||||
- privatekey_mode_3_stat.stat.mode == '0400'
|
- privatekey_mode_3_stat.stat.mode == '0400'
|
||||||
- privatekey_mode_1_stat.stat.mtime != privatekey_mode_3_stat.stat.mtime
|
- privatekey_mode_3_file_change is changed
|
||||||
|
|
||||||
- name: "({{ select_crypto_backend }}) Validate format 1"
|
- name: "({{ select_crypto_backend }}) Validate format 1"
|
||||||
assert:
|
assert:
|
||||||
|
|||||||
@@ -32,11 +32,15 @@ system_python_version_data:
|
|||||||
- '3.8'
|
- '3.8'
|
||||||
'11.1':
|
'11.1':
|
||||||
- '3.9'
|
- '3.9'
|
||||||
|
'12.0':
|
||||||
|
- '3.10'
|
||||||
FreeBSD:
|
FreeBSD:
|
||||||
'12.1':
|
'12.1':
|
||||||
- '3.6'
|
- '3.6'
|
||||||
'12.2':
|
'12.2':
|
||||||
- '3.7'
|
- '3.7'
|
||||||
|
'12.3':
|
||||||
|
- '3.8'
|
||||||
'13.0':
|
'13.0':
|
||||||
- '3.7'
|
- '3.7'
|
||||||
RedHat:
|
RedHat:
|
||||||
|
|||||||
@@ -14,14 +14,20 @@
|
|||||||
|
|
||||||
- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR
|
- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR
|
||||||
openssl_csr:
|
openssl_csr:
|
||||||
path: '{{ remote_tmp_dir }}/ca_csr.csr'
|
path: '{{ item.path }}'
|
||||||
privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
subject:
|
subject: '{{ item.subject }}'
|
||||||
commonName: Example CA
|
|
||||||
useCommonNameForSAN: no
|
useCommonNameForSAN: no
|
||||||
basic_constraints:
|
basic_constraints:
|
||||||
- 'CA:TRUE'
|
- 'CA:TRUE'
|
||||||
basic_constraints_critical: yes
|
basic_constraints_critical: yes
|
||||||
|
loop:
|
||||||
|
- path: '{{ remote_tmp_dir }}/ca_csr.csr'
|
||||||
|
subject:
|
||||||
|
commonName: Example CA
|
||||||
|
- path: '{{ remote_tmp_dir }}/ca_csr2.csr'
|
||||||
|
subject:
|
||||||
|
commonName: Example CA 2
|
||||||
|
|
||||||
- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR (privatekey passphrase)
|
- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR (privatekey passphrase)
|
||||||
openssl_csr:
|
openssl_csr:
|
||||||
@@ -62,6 +68,15 @@
|
|||||||
- result_check_mode is changed
|
- result_check_mode is changed
|
||||||
- result is changed
|
- result is changed
|
||||||
|
|
||||||
|
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate with different commonName
|
||||||
|
x509_certificate:
|
||||||
|
path: '{{ remote_tmp_dir }}/ca_cert2.pem'
|
||||||
|
csr_path: '{{ remote_tmp_dir }}/ca_csr2.csr'
|
||||||
|
privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
|
provider: selfsigned
|
||||||
|
selfsigned_digest: sha256
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
|
||||||
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase)
|
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase)
|
||||||
x509_certificate:
|
x509_certificate:
|
||||||
path: '{{ remote_tmp_dir }}/ca_cert_pw.pem'
|
path: '{{ remote_tmp_dir }}/ca_cert_pw.pem'
|
||||||
@@ -110,6 +125,54 @@
|
|||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
check_mode: yes
|
check_mode: yes
|
||||||
|
|
||||||
|
- name: (OwnCA, {{select_crypto_backend}}) Copy ownca certificate to new file to check regeneration
|
||||||
|
copy:
|
||||||
|
src: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
||||||
|
dest: '{{ item }}'
|
||||||
|
remote_src: true
|
||||||
|
loop:
|
||||||
|
- '{{ remote_tmp_dir }}/ownca_cert_ca_cn.pem'
|
||||||
|
- '{{ remote_tmp_dir }}/ownca_cert_ca_key.pem'
|
||||||
|
|
||||||
|
- name: (OwnCA, {{select_crypto_backend}}) Regenerate ownca certificate with different CA subject
|
||||||
|
x509_certificate:
|
||||||
|
path: '{{ remote_tmp_dir }}/ownca_cert_ca_cn.pem'
|
||||||
|
csr_path: '{{ remote_tmp_dir }}/csr.csr'
|
||||||
|
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||||
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert2.pem'
|
||||||
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
|
provider: ownca
|
||||||
|
ownca_digest: sha256
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
return_content: yes
|
||||||
|
register: ownca_certificate_ca_subject_changed
|
||||||
|
|
||||||
|
- name: (OwnCA, {{select_crypto_backend}}) Regenerate ownca certificate with different CA key
|
||||||
|
x509_certificate:
|
||||||
|
path: '{{ remote_tmp_dir }}/ownca_cert_ca_key.pem'
|
||||||
|
csr_path: '{{ remote_tmp_dir }}/csr.csr'
|
||||||
|
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||||
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert_pw.pem'
|
||||||
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey_pw.pem'
|
||||||
|
ownca_privatekey_passphrase: hunter2
|
||||||
|
provider: ownca
|
||||||
|
ownca_digest: sha256
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
return_content: yes
|
||||||
|
register: ownca_certificate_ca_key_changed
|
||||||
|
|
||||||
|
- name: (OwnCA, {{select_crypto_backend}}) Get certificate information
|
||||||
|
community.crypto.x509_certificate_info:
|
||||||
|
path: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: (OwnCA, {{select_crypto_backend}}) Get private key information
|
||||||
|
community.crypto.openssl_privatekey_info:
|
||||||
|
path: '{{ remote_tmp_dir }}/privatekey.pem'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
register: result_privatekey
|
||||||
|
|
||||||
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
|
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
|
||||||
x509_certificate:
|
x509_certificate:
|
||||||
path: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
||||||
@@ -285,7 +348,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
backup: yes
|
backup: yes
|
||||||
@@ -296,7 +359,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
backup: yes
|
backup: yes
|
||||||
@@ -307,7 +370,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
backup: yes
|
backup: yes
|
||||||
@@ -335,7 +398,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_subject_key_identifier: always_create
|
ownca_create_subject_key_identifier: always_create
|
||||||
@@ -348,7 +411,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_subject_key_identifier: always_create
|
ownca_create_subject_key_identifier: always_create
|
||||||
@@ -361,7 +424,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_subject_key_identifier: never_create
|
ownca_create_subject_key_identifier: never_create
|
||||||
@@ -374,7 +437,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_subject_key_identifier: never_create
|
ownca_create_subject_key_identifier: never_create
|
||||||
@@ -387,7 +450,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_subject_key_identifier: always_create
|
ownca_create_subject_key_identifier: always_create
|
||||||
@@ -400,7 +463,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_authority_key_identifier: yes
|
ownca_create_authority_key_identifier: yes
|
||||||
@@ -413,7 +476,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_authority_key_identifier: yes
|
ownca_create_authority_key_identifier: yes
|
||||||
@@ -426,7 +489,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_authority_key_identifier: no
|
ownca_create_authority_key_identifier: no
|
||||||
@@ -439,7 +502,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_authority_key_identifier: no
|
ownca_create_authority_key_identifier: no
|
||||||
@@ -452,7 +515,7 @@
|
|||||||
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
|
||||||
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
|
||||||
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
|
||||||
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
|
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
|
||||||
provider: ownca
|
provider: ownca
|
||||||
ownca_digest: sha256
|
ownca_digest: sha256
|
||||||
ownca_create_authority_key_identifier: yes
|
ownca_create_authority_key_identifier: yes
|
||||||
|
|||||||
@@ -31,6 +31,14 @@
|
|||||||
- ownca_certificate.notBefore == ownca_certificate_idempotence.notBefore
|
- ownca_certificate.notBefore == ownca_certificate_idempotence.notBefore
|
||||||
- ownca_certificate.notAfter == ownca_certificate_idempotence.notAfter
|
- ownca_certificate.notAfter == ownca_certificate_idempotence.notAfter
|
||||||
|
|
||||||
|
- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate regeneration
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- ownca_certificate_ca_subject_changed is changed
|
||||||
|
# ownca_certificate_ca_key_changed is not changed for the pyopenssl backend,
|
||||||
|
# see https://github.com/ansible-collections/community.crypto/pull/406
|
||||||
|
- ownca_certificate_ca_key_changed is changed or select_crypto_backend == 'pyopenssl'
|
||||||
|
|
||||||
- name: (OwnCA validation, {{select_crypto_backend}}) Read certificate
|
- name: (OwnCA validation, {{select_crypto_backend}}) Read certificate
|
||||||
slurp:
|
slurp:
|
||||||
src: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
src: '{{ remote_tmp_dir }}/ownca_cert.pem'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Check whether issuer and subject behave as expected
|
- name: Check whether issuer and subject and extensions behave as expected
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- result.issuer.organizationalUnitName == 'ACME Department'
|
- result.issuer.organizationalUnitName == 'ACME Department'
|
||||||
@@ -19,6 +19,28 @@
|
|||||||
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
|
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
|
||||||
- result.public_key_type == 'RSA'
|
- result.public_key_type == 'RSA'
|
||||||
- result.public_key_data.size == (default_rsa_key_size_certifiates | int)
|
- result.public_key_data.size == (default_rsa_key_size_certifiates | int)
|
||||||
|
- "result.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'
|
||||||
|
]"
|
||||||
|
# TLS Feature
|
||||||
|
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].critical == false
|
||||||
|
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].value == 'MAMCAQU='
|
||||||
|
# Key Usage
|
||||||
|
- result.extensions_by_oid['2.5.29.15'].critical == true
|
||||||
|
- result.extensions_by_oid['2.5.29.15'].value in ['AwMA/4A=', 'AwMH/4A=']
|
||||||
|
# Subject Alternative Names
|
||||||
|
- result.extensions_by_oid['2.5.29.17'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.17'].value == 'MGCCD3d3dy5hbnNpYmxlLmNvbYcEAQIDBIcQAAAAAAAAAAAAAAAAAAAAAYEQdGVzdEBleGFtcGxlLm9yZ4YjaHR0cHM6Ly9leGFtcGxlLm9yZy90ZXN0L2luZGV4Lmh0bWw='
|
||||||
|
# Basic Constraints
|
||||||
|
- result.extensions_by_oid['2.5.29.19'].critical == true
|
||||||
|
- result.extensions_by_oid['2.5.29.19'].value == 'MAYBAf8CARc='
|
||||||
|
# Extended Key Usage
|
||||||
|
- result.extensions_by_oid['2.5.29.37'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.37'].value == 'MHQGCCsGAQUFBwMBBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUFBwMJBgRVHSUABggrBgEFBQcBAwYIKwYBBQUHAwoGCCsGAQUFBwMHBggrBgEFBQcBAg=='
|
||||||
|
|
||||||
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
|
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
|
||||||
assert:
|
assert:
|
||||||
@@ -27,6 +49,10 @@
|
|||||||
- result.authority_key_identifier == "44:55:66:77"
|
- result.authority_key_identifier == "44:55:66:77"
|
||||||
- result.authority_cert_issuer == expected_authority_cert_issuer
|
- result.authority_cert_issuer == expected_authority_cert_issuer
|
||||||
- result.authority_cert_serial_number == 12345
|
- result.authority_cert_serial_number == 12345
|
||||||
|
# Subject Key Identifier
|
||||||
|
- result.extensions_by_oid['2.5.29.14'].critical == false
|
||||||
|
# Authority Key Identifier
|
||||||
|
- result.extensions_by_oid['2.5.29.35'].critical == false
|
||||||
vars:
|
vars:
|
||||||
expected_authority_cert_issuer:
|
expected_authority_cert_issuer:
|
||||||
- "DNS:ca.example.org"
|
- "DNS:ca.example.org"
|
||||||
@@ -122,10 +148,39 @@
|
|||||||
path: '{{ remote_tmp_dir }}/packed-cert-1.pem'
|
path: '{{ remote_tmp_dir }}/packed-cert-1.pem'
|
||||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
register: result
|
register: result
|
||||||
- assert:
|
- name: Check extensions
|
||||||
|
assert:
|
||||||
that:
|
that:
|
||||||
- "'ocsp_uri' in result"
|
- "'ocsp_uri' in result"
|
||||||
- "result.ocsp_uri == 'http://ocsp.int-x3.letsencrypt.org'"
|
- "result.ocsp_uri == 'http://ocsp.int-x3.letsencrypt.org'"
|
||||||
|
- result.extensions_by_oid | length == 9
|
||||||
|
# Precert Signed Certificate Timestamps
|
||||||
|
- result.extensions_by_oid['1.3.6.1.4.1.11129.2.4.2'].critical == false
|
||||||
|
- result.extensions_by_oid['1.3.6.1.4.1.11129.2.4.2'].value == 'BIHyAPAAdgDBFkrgp3LS1DktyArBB3DU8MSb3pkaSEDB+gdRZPYzYAAAAWTdAoU6AAAEAwBHMEUCIG5WpfKF536KKa9fnVlYbwcfrKh09Hi2MSRwU2kad49UAiEA4RUKjJOgw11IHFNdit+sy1RcCU3QCSOEQYrJ1/oPltAAdgApPFGWVMg5ZbqqUPxYB9S3b79Yeily3KTDDPTlRUf0eAAAAWTdAoc+AAAEAwBHMEUCIQCJjo75K4rVDSiWQe3XFLY6MiG3zcHQrKb0YhM17r1UKAIgGa8qMoN03DLp+Rm9nRJ9XLbTJz1vbuu9PyXUY741P8E='
|
||||||
|
# Authority Information Access
|
||||||
|
- result.extensions_by_oid['1.3.6.1.5.5.7.1.1'].critical == false
|
||||||
|
- result.extensions_by_oid['1.3.6.1.5.5.7.1.1'].value == 'MGEwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv'
|
||||||
|
# Subject Key Identifier
|
||||||
|
- result.extensions_by_oid['2.5.29.14'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.14'].value == 'BBRtcOI/yg62Ehbu5vQzxMUUdBOYMw=='
|
||||||
|
# Key Usage (The certificate has 'AwIFoA==', while de-serializing and re-serializing yields 'AwIAoA=='!)
|
||||||
|
- result.extensions_by_oid['2.5.29.15'].critical == true
|
||||||
|
- result.extensions_by_oid['2.5.29.15'].value in ['AwIFoA==', 'AwIAoA==']
|
||||||
|
# Subject Alternative Names
|
||||||
|
- result.extensions_by_oid['2.5.29.17'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.17'].value == 'MIIB5IIbY2VydC5pbnQteDEubGV0c2VuY3J5cHQub3JnghtjZXJ0LmludC14Mi5sZXRzZW5jcnlwdC5vcmeCG2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9yZ4IbY2VydC5pbnQteDQubGV0c2VuY3J5cHQub3JnghxjZXJ0LnJvb3QteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0YWdpbmcteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JngiBjZXJ0LnN0Zy1yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ISY3AubGV0c2VuY3J5cHQub3JnghpjcC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ITY3BzLmxldHNlbmNyeXB0Lm9yZ4IbY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3Jnghtjcmwucm9vdC14MS5sZXRzZW5jcnlwdC5vcmeCD2xldHNlbmNyeXB0Lm9yZ4IWb3JpZ2luLmxldHNlbmNyeXB0Lm9yZ4IXb3JpZ2luMi5sZXRzZW5jcnlwdC5vcmeCFnN0YXR1cy5sZXRzZW5jcnlwdC5vcmeCE3d3dy5sZXRzZW5jcnlwdC5vcmc='
|
||||||
|
# Basic Constraints
|
||||||
|
- result.extensions_by_oid['2.5.29.19'].critical == true
|
||||||
|
- result.extensions_by_oid['2.5.29.19'].value == 'MAA='
|
||||||
|
# Certificate Policies
|
||||||
|
- result.extensions_by_oid['2.5.29.32'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.32'].value == 'MIHzMAgGBmeBDAECATCB5gYLKwYBBAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkgYmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkv'
|
||||||
|
# Authority Key Identifier
|
||||||
|
- result.extensions_by_oid['2.5.29.35'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.35'].value == 'MBaAFKhKamMEfd265tE5t6ZFZe/zqOyh'
|
||||||
|
# Extended Key Usage
|
||||||
|
- result.extensions_by_oid['2.5.29.37'].critical == false
|
||||||
|
- result.extensions_by_oid['2.5.29.37'].value == 'MBQGCCsGAQUFBwMBBggrBgEFBQcDAg=='
|
||||||
- name: Check fingerprints
|
- name: Check fingerprints
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
integration_tests_dependencies:
|
integration_tests_dependencies:
|
||||||
- community.general
|
- community.general
|
||||||
|
- community.internal_test_tools
|
||||||
unit_tests_dependencies: []
|
unit_tests_dependencies: []
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||||
plugins/module_utils/compat/ipaddress.py no-assert
|
plugins/module_utils/compat/ipaddress.py no-assert
|
||||||
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
plugins/module_utils/compat/ipaddress.py no-unicode-literals
|
||||||
plugins/module_utils/crypto/__init__.py empty-init
|
plugins/module_utils/crypto/__init__.py empty-init
|
||||||
plugins/modules/acme_account_info.py validate-modules:return-syntax-error
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
||||||
|
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
||||||
plugins/module_utils/acme/__init__.py empty-init
|
plugins/module_utils/acme/__init__.py empty-init
|
||||||
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
plugins/module_utils/compat/ipaddress.py future-import-boilerplate
|
||||||
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
-----BEGIN NEW CERTIFICATE REQUEST-----
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG
|
MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG
|
||||||
CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs
|
CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs
|
||||||
4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE
|
4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE
|
||||||
@@ -6,4 +6,4 @@ MCMGA1UdEQQcMBqCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZzAMBgNVHRMBAf8E
|
|||||||
AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl
|
AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl
|
||||||
FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr
|
FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr
|
||||||
URnCJfTLr2T3
|
URnCJfTLr2T3
|
||||||
-----END NEW CERTIFICATE REQUEST-----
|
-----END CERTIFICATE REQUEST-----
|
||||||
|
|||||||
12
tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem.old
Normal file
12
tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem.old
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
cryptography 35.0.0 does not support the 'NEW' in there; so to fix tests we removed it.
|
||||||
|
Once cryptography is fixed we should revert to the old version.
|
||||||
|
|
||||||
|
-----BEGIN NEW CERTIFICATE REQUEST-----
|
||||||
|
MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG
|
||||||
|
CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs
|
||||||
|
4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE
|
||||||
|
MCMGA1UdEQQcMBqCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZzAMBgNVHRMBAf8E
|
||||||
|
AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl
|
||||||
|
FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr
|
||||||
|
URnCJfTLr2T3
|
||||||
|
-----END NEW CERTIFICATE REQUEST-----
|
||||||
@@ -115,12 +115,14 @@ def test_format_error_problem(problem, subproblem_prefix, result):
|
|||||||
def create_regular_response(response_text):
|
def create_regular_response(response_text):
|
||||||
response = MagicMock()
|
response = MagicMock()
|
||||||
response.read = MagicMock(return_value=response_text.encode('utf-8'))
|
response.read = MagicMock(return_value=response_text.encode('utf-8'))
|
||||||
|
response.closed = False
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def create_error_response():
|
def create_error_response():
|
||||||
response = MagicMock()
|
response = MagicMock()
|
||||||
response.read = MagicMock(side_effect=AttributeError('read'))
|
response.read = MagicMock(side_effect=AttributeError('read'))
|
||||||
|
response.closed = True
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ def test_run_luks_remove(monkeypatch):
|
|||||||
monkeypatch.setattr(luks_device.Handler,
|
monkeypatch.setattr(luks_device.Handler,
|
||||||
"_run_command",
|
"_run_command",
|
||||||
run_command_check)
|
run_command_check)
|
||||||
|
monkeypatch.setattr(luks_device,
|
||||||
|
"wipe_luks_headers",
|
||||||
|
lambda device: True)
|
||||||
crypt = luks_device.CryptHandler(module)
|
crypt = luks_device.CryptHandler(module)
|
||||||
crypt.run_luks_remove("dummy")
|
crypt.run_luks_remove("dummy")
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ idna < 2.6, >= 2.5 # linode requires idna < 2.9, >= 2.5, requests requires idna
|
|||||||
requests < 2.20.0 ; python_version < '2.7' # requests 2.20.0 drops support for python 2.6
|
requests < 2.20.0 ; python_version < '2.7' # requests 2.20.0 drops support for python 2.6
|
||||||
virtualenv < 16.0.0 ; python_version < '2.7' # virtualenv 16.0.0 and later require python 2.7 or later
|
virtualenv < 16.0.0 ; python_version < '2.7' # virtualenv 16.0.0 and later require python 2.7 or later
|
||||||
pyopenssl < 18.0.0 ; python_version < '2.7' # pyOpenSSL 18.0.0 and later require python 2.7 or later
|
pyopenssl < 18.0.0 ; python_version < '2.7' # pyOpenSSL 18.0.0 and later require python 2.7 or later
|
||||||
|
pyopenssl < 22.0.0 ; python_version < '3.6' # pyOpenSSL 22.0.0 and later require python 3.6 or later
|
||||||
setuptools < 45 ; python_version <= '2.7' # setuptools 45 and later require python 3.5 or later
|
setuptools < 45 ; python_version <= '2.7' # setuptools 45 and later require python 3.5 or later
|
||||||
cffi >= 1.14.2, != 1.14.3 # Yanked version which older versions of pip will still install:
|
cffi >= 1.14.2, != 1.14.3 # Yanked version which older versions of pip will still install:
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ fi
|
|||||||
stage="${S:-prod}"
|
stage="${S:-prod}"
|
||||||
provider="${P:-default}"
|
provider="${P:-default}"
|
||||||
|
|
||||||
|
if [ "${platform}/${version}" == "freebsd/13.0" ]; then
|
||||||
|
# On FreeBSD 13.0, installing PyOpenSSL 22.0.0 tries to upgrade cryptography, which
|
||||||
|
# will fail due to missing Rust compiler.
|
||||||
|
echo "pyopenssl < 22.0.0 ; python_version >= '3.8'" >> tests/utils/constraints.txt
|
||||||
|
fi
|
||||||
|
|
||||||
# shellcheck disable=SC2086
|
# shellcheck disable=SC2086
|
||||||
ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \
|
ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \
|
||||||
--remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}"
|
--remote "${platform}/${version}" --remote-terminate always --remote-stage "${stage}" --remote-provider "${provider}"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ function retry
|
|||||||
echo "@* -> ${result}"
|
echo "@* -> ${result}"
|
||||||
done
|
done
|
||||||
echo "Command '@*' failed 3 times!"
|
echo "Command '@*' failed 3 times!"
|
||||||
exit -1
|
exit 255
|
||||||
}
|
}
|
||||||
|
|
||||||
command -v pip
|
command -v pip
|
||||||
@@ -89,7 +89,7 @@ if [ "${test}" == "sanity/extra" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# START: HACK install integration test dependencies
|
# START: HACK install integration test dependencies
|
||||||
if [ "${test}" == "sanity/extra" ]; then
|
if [ "${script}" != "units" ] && [ "${script}" != "sanity" ] || [ "${test}" == "sanity/extra" ]; then
|
||||||
# Nothing further should be added to this list.
|
# Nothing further should be added to this list.
|
||||||
# This is to prevent modules or plugins in this collection having a runtime dependency on other collections.
|
# This is to prevent modules or plugins in this collection having a runtime dependency on other collections.
|
||||||
retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/internal_test_tools"
|
retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/internal_test_tools"
|
||||||
@@ -97,7 +97,7 @@ if [ "${test}" == "sanity/extra" ]; then
|
|||||||
# retry ansible-galaxy -vvv collection install community.internal_test_tools
|
# retry ansible-galaxy -vvv collection install community.internal_test_tools
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${script}" != "units" ] && [ "${script}" != "sanity" ] && [ "${ansible_version}" != "2.9" ]; then
|
if [ "${script}" != "units" ] && [ "${script}" != "sanity" ] && [ "${test}" != "sanity/extra" ] && [ "${ansible_version}" != "2.9" ]; then
|
||||||
retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.general.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general"
|
retry git clone --depth=1 --single-branch https://github.com/ansible-collections/community.general.git "${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/community/general"
|
||||||
# NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429)
|
# NOTE: we're installing with git to work around Galaxy being a huge PITA (https://github.com/ansible/galaxy/issues/2429)
|
||||||
# retry ansible-galaxy -vvv collection install community.general
|
# retry ansible-galaxy -vvv collection install community.general
|
||||||
|
|||||||
Reference in New Issue
Block a user